127 lines
4.6 KiB
JavaScript
Executable File
127 lines
4.6 KiB
JavaScript
Executable File
// 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;
|