squareData.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. const Merchant = require("../models/merchant.js");
  2. const Recipe = require("../models/recipe.js");
  3. const Transaction = require("../models/transaction.js");
  4. const axios = require("axios");
  5. const helper = require("./helper.js");
  6. module.exports = {
  7. //GET - Redirects user to Square OAuth page
  8. redirect: function(req, res){
  9. return res.redirect(`${process.env.SQUARE_ADDRESS}/oauth2/authorize?client_id=${process.env.SUBLINE_SQUARE_APPID}&scope=INVENTORY_READ+ITEMS_READ+MERCHANT_PROFILE_READ+ORDERS_READ+PAYMENTS_READ`);
  10. },
  11. //GET: Used by square. This route is used for the authentication code
  12. //Redirects to either dashboard or new merchant creation
  13. authorize: function(req, res){
  14. const code = req.url.slice(req.url.indexOf("code=") + 5, req.url.indexOf("&"));
  15. const url = `${process.env.SQUARE_ADDRESS}/oauth2/token`;
  16. let data = {
  17. client_id: process.env.SUBLINE_SQUARE_APPID,
  18. client_secret: process.env.SUBLINE_SQUARE_APPSECRET,
  19. grant_type: "authorization_code",
  20. code: code
  21. };
  22. axios.post(url, data)
  23. .then((response)=>{
  24. data = response.data;
  25. return Merchant.findOne({posId: data.merchant_id});
  26. })
  27. .then((merchant)=>{
  28. if(merchant){
  29. merchant.posAccessToken = data.access_token;
  30. return merchant.save()
  31. .then((merchant)=>{
  32. req.session.user = merchant.session.sessionId;
  33. return res.redirect("/dashboard");
  34. })
  35. .catch((err)=>{
  36. req.session.error = "ERROR: UNABLE TO CREATE NEW USER";
  37. return res.redirect("/");
  38. })
  39. }else{
  40. req.session.merchantId = data.merchant_id;
  41. req.session.accessToken = data.access_token;
  42. return res.redirect("/merchant/create/square");
  43. }
  44. })
  45. .catch((err)=>{
  46. req.session.error = "ERROR: UNABLE TO RETRIEVE DATA FROM SQUARE";
  47. return res.redirect("/");
  48. });
  49. },
  50. //GET: Gathers all data from square to create our merchant
  51. //Redirects to the dashboard
  52. createMerchant: function(req, res){
  53. let merchant = {}
  54. axios.get(`${process.env.SQUARE_ADDRESS}/v2/merchants/${req.session.merchantId}`, {
  55. headers: {
  56. Authorization: `Bearer ${req.session.accessToken}`
  57. }
  58. })
  59. .then((response)=>{
  60. req.session.merchantId = undefined;
  61. let expirationDate = new Date();
  62. expirationDate.setDate(expirationDate.getDate() + 90);
  63. merchant = new Merchant({
  64. name: response.data.merchant.business_name,
  65. pos: "square",
  66. posId: response.data.merchant.id,
  67. posAccessToken: req.session.accessToken,
  68. lastUpdatedTime: new Date(),
  69. createdAt: new Date(),
  70. squareLocation: response.data.merchant.main_location_id,
  71. status: [],
  72. inventory: [],
  73. recipes: [],
  74. session: {
  75. sessionId: helper.generateId(25),
  76. expiration: expirationDate
  77. }
  78. });
  79. req.session.accessToken = undefined;
  80. let items = axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search`, {
  81. object_types: ["ITEM"]
  82. }, {
  83. headers: {
  84. Authorization: `Bearer ${merchant.posAccessToken}`
  85. }
  86. });
  87. let location = axios.get(`${process.env.SQUARE_ADDRESS}/v2/locations/${response.data.merchant.main_location_id}`, {
  88. headers: {
  89. Authorization: `Bearer ${merchant.posAccessToken}`
  90. }
  91. });
  92. return Promise.all([items, location]);
  93. })
  94. .then((response)=>{
  95. merchant.email = response[1].data.location.business_email;
  96. let recipes = [];
  97. for(let i = 0; i < response[0].data.objects.length; i++){
  98. if(response[0].data.objects[i].item_data.variations.length > 1){
  99. for(let j = 0; j < response[0].data.objects[i].item_data.variations.length; j++){
  100. let item = response[0].data.objects[i].item_data.variations[j];
  101. let price = 0;
  102. if(item.item_variation_data.price_money !== undefined) price = item.item_variation_data.price_money.amount;
  103. let recipe = new Recipe({
  104. posId: item.id,
  105. merchant: merchant._id,
  106. name: `${response[0].data.objects[i].item_data.name} '${item.item_variation_data.name}'`,
  107. price: price
  108. });
  109. recipes.push(recipe);
  110. merchant.recipes.push(recipe);
  111. }
  112. }else{
  113. let recipe = new Recipe({
  114. posId: response[0].data.objects[i].item_data.variations[0].id,
  115. merchant: merchant._id,
  116. name: response[0].data.objects[i].item_data.name,
  117. price: response[0].data.objects[i].item_data.variations[0].item_variation_data.price_money.amount,
  118. ingredients: []
  119. });
  120. recipes.push(recipe);
  121. merchant.recipes.push(recipe);
  122. }
  123. }
  124. return Promise.all([Recipe.create(recipes), merchant.save()]);
  125. })
  126. .then((response)=>{
  127. req.session.user = response[1].session.sessionId;
  128. res.redirect("/dashboard");
  129. let body = {
  130. location_ids: [merchant.squareLocation],
  131. limit: 10000,
  132. query: {}
  133. };
  134. let options = {
  135. headers: {
  136. Authorization: `Bearer ${merchant.posAccessToken}`,
  137. "Content-Type": "application/json"
  138. }
  139. };
  140. return axios.post(`${process.env.SQUARE_ADDRESS}/v2/orders/search`, body, options);
  141. })
  142. .then(async (response)=>{
  143. let transactions = [];
  144. for(let i = 0; i < response.data.orders.length; i++){
  145. let transaction = new Transaction({
  146. merchant: merchant._id,
  147. date: new Date(response.data.orders[i].created_at),
  148. posId: response.data.orders[i].id,
  149. recipes: []
  150. });
  151. if(response.data.orders[i].line_items === undefined) continue;
  152. for(let j = 0; j < response.data.orders[i].line_items.length; j++){
  153. let item = response.data.orders[i].line_items[j];
  154. for(let k = 0; k < merchant.recipes.length; k++){
  155. if(merchant.recipes[k].posId === item.catalog_object_id){
  156. transaction.recipes.push({
  157. recipe: merchant.recipes[k]._id,
  158. quantity: parseInt(item.quantity)
  159. })
  160. }
  161. }
  162. }
  163. transactions.push(transaction);
  164. }
  165. let body = {
  166. location_ids: [merchant.squareLocation],
  167. limit: 10000,
  168. cursor: response.data.cursor,
  169. query: {}
  170. };
  171. let options = {
  172. headers: {
  173. Authorization: `Bearer ${merchant.posAccessToken}`,
  174. "Content-Type": "application/json"
  175. }
  176. };
  177. while(body.cursor !== undefined){
  178. let response = await axios.post(`${process.env.SQUARE_ADDRESS}/v2/orders/search`, body, options);
  179. body.cursor = response.data.cursor;
  180. for(let i = 0; i < response.data.orders.length; i++){
  181. let transaction = new Transaction({
  182. merchant: merchant._id,
  183. date: new Date(response.data.orders[i].created_at),
  184. posId: response.data.orders[i].id,
  185. recipes: []
  186. });
  187. if(responose.data.orders[i].line_items === undefined) continue;
  188. for(let j = 0; j < response.data.orders[i].line_items.length; j++){
  189. let item = response.data.orders[i].line_items[j];
  190. for(let k = 0; k < merchant.recipes.length; k++){
  191. if(merchant.recipes[k].posId === item.catalog_object_id){
  192. transaction.recipes.push({
  193. recipe: merchant.recipes[k]._id,
  194. quantity: parseInt(item.quantity)
  195. })
  196. }
  197. }
  198. }
  199. transactions.push(transaction);
  200. }
  201. }
  202. return Transaction.create(transactions);
  203. })
  204. .catch((err)=>{
  205. if(typeof(err) === "string"){
  206. req.session.error = err;
  207. }else if(err.name === "ValidationError"){
  208. req.session.error = err.errors[Object.keys(err.errors)[0]].properties.message;
  209. }else{
  210. req.session.error = "ERROR: UNABLE TO CREATE NEW USER";
  211. }
  212. return res.redirect("/");
  213. });
  214. },
  215. updateRecipes: function(req, res){
  216. let merchant = {};
  217. let merchantRecipes = [];
  218. let newRecipes = [];
  219. res.locals.merchant
  220. .populate("recipes")
  221. .execPopulate()
  222. .then((fetchedMerchant)=>{
  223. merchant = fetchedMerchant;
  224. return axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search`, {
  225. object_types: ["ITEM"]
  226. }, {
  227. headers: {
  228. Authorization: `Bearer ${merchant.posAccessToken}`
  229. }
  230. });
  231. })
  232. .then((response)=>{
  233. merchantRecipes = merchant.recipes.slice();
  234. for(let i = 0; i < response.data.objects.length; i++){
  235. let itemData = response.data.objects[i].item_data;
  236. for(let j = 0; j < itemData.variations.length; j++){
  237. let isFound = false;
  238. for(let k = 0; k < merchantRecipes.length; k++){
  239. if(itemData.variations[j].id === merchantRecipes[k].posId){
  240. merchantRecipes.splice(k, 1);
  241. k--;
  242. isFound = true;
  243. break;
  244. }
  245. }
  246. if(!isFound){
  247. let newRecipe = new Recipe({
  248. posId: itemData.variations[j].id,
  249. merchant: merchant._id,
  250. name: "",
  251. price: itemData.variations[j].item_variation_data.price_money.amount,
  252. ingredients: []
  253. });
  254. if(itemData.variations.length > 1){
  255. newRecipe.name = `${itemData.name} '${itemData.variations[j].item_variation_data.name}'`;
  256. }else{
  257. newRecipe.name = itemData.name;
  258. }
  259. newRecipes.push(newRecipe);
  260. merchant.recipes.push(newRecipe);
  261. }
  262. }
  263. }
  264. let ids = [];
  265. for(let i = 0; i < merchantRecipes.length; i++){
  266. ids.push(merchantRecipes[i]._id);
  267. for(let j = 0; j < merchant.recipes.length; j++){
  268. if(merchantRecipes[i]._id.toString() === merchant.recipes[j]._id.toString()){
  269. merchant.recipes.splice(j, 1);
  270. j--;
  271. break;
  272. }
  273. }
  274. }
  275. if(newRecipes.length > 0) Recipe.create(newRecipes);
  276. if(merchantRecipes.length > 0) Recipe.deleteMany({_id: {$in: ids}});
  277. return merchant.save();
  278. })
  279. .then((merchant)=>{
  280. return res.json({new: newRecipes, removed: merchantRecipes});
  281. })
  282. .catch((err)=>{
  283. if(typeof(err) === "string"){
  284. return res.json(err);
  285. }
  286. if(err.name === "ValidationError"){
  287. return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
  288. }
  289. return res.json("ERROR: UNABLE TO RETRIEVE RECIPE DATA FROM SQUARE");
  290. });
  291. }
  292. }