Race-Nation-Shopify-Backend/fulfillmentService.js
MOHAN 2ceb4b0d90 fix(auth): fetch existing fulfillment service locationId when create fails
When the fulfillment service already exists the create mutation returns
userErrors. Now queries existing fulfillmentServices to get the location
ID instead of falling through with null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-22 00:31:15 +05:30

166 lines
4.6 KiB
JavaScript

const axios = require("axios");
const { log } = require("./logger");
const getLocationQuery = `
query {
locations(first: 1, query: "name:'Shop location'") {
nodes {
id
name
address {
address1
address2
city
province
provinceCode
country
countryCode
zip
phone
}
}
}
}
`;
async function getStoreAddress(client) {
const response = await client.post("", { query: getLocationQuery });
const location = response.data?.data?.locations?.nodes?.[0];
return location?.address || null;
}
function createLocationMutation(address) {
return `
mutation {
locationAdd(input: {
name: "(App) Race Nation Distribution"
address: {
address1: "${address?.address1 || ""}"
address2: "${address?.address2 || ""}"
city: "${address?.city || ""}"
provinceCode: "${address?.provinceCode || "ON"}"
countryCode: ${JSON.stringify(address?.countryCode || "US")}
zip: "${address?.zip || ""}"
phone: "${address?.phone || ""}"
}
fulfillsOnlineOrders: true
}) {
location {
id
name
}
userErrors {
code
field
message
}
}
}
`;
}
async function createCustomLocation(address, client) {
const response = await client.post("", { query: createLocationMutation(address) });
return response.data?.data?.locationAdd || { location: null, userErrors: [] };
}
async function createFulfillmentService(shop, accessToken) {
const client = axios.create({
baseURL: `https://${shop}/admin/api/2025-10/graphql.json`,
headers: {
"X-Shopify-Access-Token": accessToken,
"Content-Type": "application/json",
},
});
const mutation = `
mutation {
fulfillmentServiceCreate(
name: "Race Nation Distribution"
callbackUrl: "https://backend.race-nation.com/fulfillment"
) {
fulfillmentService {
id
serviceName
callbackUrl
handle
location {
id
}
}
userErrors {
field
message
}
}
}
`;
try {
log(shop, "Creating fulfillment service...");
const response = await client.post("", { query: mutation });
const data = response.data?.data?.fulfillmentServiceCreate;
let fulfillmentService = data?.fulfillmentService || null;
let locationId = fulfillmentService?.location?.id || null;
if (data?.userErrors?.length) {
// Service already exists — query it to get its locationId
log(shop, `Fulfillment service already exists, querying existing...`);
try {
const existingResp = await client.post("", {
query: `{ fulfillmentServices { id serviceName location { id name } } }`,
});
const existing = existingResp.data?.data?.fulfillmentServices || [];
const ours = existing.find((s) => s.serviceName === "Race Nation Distribution") || existing[0];
if (ours?.location?.id) {
locationId = ours.location.id;
fulfillmentService = ours;
log(shop, `Found existing fulfillment service location: ${ours.location.name} (${locationId})`);
}
} catch (queryErr) {
log(shop, `Could not query existing fulfillment services: ${queryErr.message}`);
}
}
if (!locationId) {
// Last resort: try to get shop's primary location
try {
const locResp = await client.post("", {
query: `{ locations(first: 1) { nodes { id name } } }`,
});
locationId = locResp.data?.data?.locations?.nodes?.[0]?.id || null;
if (locationId) log(shop, `Using primary store location: ${locationId}`);
} catch {}
}
if (!locationId && data?.userErrors?.length) {
return { success: false, errors: data.userErrors };
}
const address = await getStoreAddress(client).catch(() => null);
if (address && !locationId) {
const customLocation = await createCustomLocation(address, client);
if (!customLocation?.userErrors?.length && customLocation?.location?.id) {
locationId = customLocation.location.id;
}
}
return {
success: true,
fulfillmentService,
locationId,
};
} catch (error) {
log(shop, `Fulfillment service request failed: ${error.response ? JSON.stringify(error.response.data) : error.message}`);
return {
success: false,
error: error.response ? error.response.data : error.message,
};
}
}
module.exports = {
createFulfillmentService,
};