// auth.js const express = require('express'); const axios = require('axios'); const { log } = require('./logger'); const { saveToken } = require('./tokenStore'); const { createFulfillmentService } = require('./fulfillmentService'); const router = express.Router(); // replace these with environment vars or config const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID; const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET; // optional defaults if not passed during install const DEFAULT_PRICE_TYPE = (process.env.PRICING_PRICE_TYPE || 'map').toLowerCase(); // "map" | "percentage" const DEFAULT_PERCENTAGE = Number(process.env.PRICING_PERCENTAGE || 0); router.get('/auth/callback', async (req, res) => { const { shop, code } = req.query; if (!shop || !code) { log('general', `โš ๏ธ Missing shop or code in callback: ${JSON.stringify(req.query)}`); return res.status(400).send('Missing shop or code parameter.'); } log(shop, `๐Ÿ”” Received OAuth callback (code=${code})`); try { // 1) Exchange code for token log(shop, '๐Ÿš€ Exchanging code for access token'); const resp = await axios.post( `https://${shop}/admin/oauth/access_token`, { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code }, { headers: { 'Content-Type': 'application/json' } } ); const { access_token, scope } = resp.data; log(shop, `โœ… Token received (scopes=${scope})`); saveToken(shop, access_token, scope); log(shop, '๐Ÿ’พ Token saved to data/tokens.json'); // 2) Create fulfillment service (existing step) const { fulfillmentService, locationId } = await createFulfillmentService(shop, access_token); console.log(`Custom Location created: ${locationId}`); saveToken(shop, access_token, scope, fulfillmentService, locationId); console.log(`Fulfillment Service created neww: ${JSON.stringify(fulfillmentService)}`); log(shop, 'โœ… Fulfillment service created successfully'); // 3) Prepare Admin GraphQL const adminUrl = `https://${shop}/admin/api/2025-10/graphql.json`; // use your target API version const headers = { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': access_token, }; // 4) Get Shop GID (required for ownerId) log(shop, '๐Ÿ”Ž Fetching shop GID'); const shopIdQuery = `{ shop { id name } }`; const shopIdResp = await axios.post(adminUrl, { query: shopIdQuery }, { headers }); const shopId = shopIdResp.data?.data?.shop?.id; if (!shopId) { throw new Error(`Could not fetch shop id: ${JSON.stringify(shopIdResp.data)}`); } log(shop, `๐Ÿท๏ธ Shop GID: ${shopId}`); // 5) Build pricing config from query/body/env // example install URL can pass: ?priceType=percentage&percentage=15 const inputPriceType = (req.query.priceType || req.body?.priceType || DEFAULT_PRICE_TYPE || 'map') .toString() .toLowerCase(); const inputPercentage = Number(req.query.percentage ?? req.body?.percentage ?? DEFAULT_PERCENTAGE) || 0; const priceType = ['map', 'percentage'].includes(inputPriceType) ? inputPriceType : 'map'; const percentage = Number.isFinite(inputPercentage) ? inputPercentage : 0; const cfg = { priceType, percentage }; log(shop, `๐Ÿงพ Saving pricing_config: ${cfg.priceType}/${cfg.percentage}%`); // 6) Save JSON metafield (turn14.pricing_config) on the Shop // using variables to avoid escaping headaches const setMfMutation = ` mutation metafieldsSet($ownerId: ID!, $cfg: String!) { metafieldsSet(metafields: [{ namespace: "turn14", key: "pricing_config", type: "json", ownerId: $ownerId, value: $cfg }]) { metafields { id key } userErrors { field message } } } `; const mfResp = await axios.post( adminUrl, { query: setMfMutation, variables: { ownerId: shopId, cfg: JSON.stringify(cfg), // JSON metafield expects a stringified JSON blob }, }, { headers } ); const userErrors = mfResp.data?.data?.metafieldsSet?.userErrors || []; if (userErrors.length) { log(shop, `โš ๏ธ metafieldsSet errors: ${JSON.stringify(userErrors)}`); } else { log(shop, 'โœ… pricing_config metafield saved'); } res.redirect(`https://admin.shopify.com`); res.send('Access token saved. You may close this window.'); } catch (err) { const errMsg = err.response?.data || err.message; log(shop, `โŒ OAuth error: ${JSON.stringify(errMsg)}`); res.status(500).send('Failed to get access token'); } }); module.exports = router;