158 lines
6.7 KiB
JavaScript
Executable File
158 lines
6.7 KiB
JavaScript
Executable File
const axios = require('axios');
|
|
const { log } = require('./logger');
|
|
|
|
/**
|
|
* Create (or retrieve existing) fulfillment service + location for a shop.
|
|
* Returns { fulfillmentService, locationId } — locationId is NEVER null if a
|
|
* fulfillment service exists, because we fall back to the service's own location.
|
|
*/
|
|
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',
|
|
},
|
|
});
|
|
|
|
try {
|
|
log(shop, `🚀 [FulfillmentService] Creating fulfillment service...`);
|
|
|
|
// ── Step 1: Create fulfillment service (or retrieve existing) ────────────
|
|
const createResp = await client.post('', {
|
|
query: `
|
|
mutation {
|
|
fulfillmentServiceCreate(
|
|
name: "Data4Autos Distribution",
|
|
callbackUrl: "https://backend.data4autos.com/fulfillment"
|
|
) {
|
|
fulfillmentService {
|
|
id serviceName callbackUrl handle
|
|
location { id name }
|
|
}
|
|
userErrors { field message }
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
|
|
let fulfillmentService = createResp.data?.data?.fulfillmentServiceCreate?.fulfillmentService;
|
|
const createErrors = createResp.data?.data?.fulfillmentServiceCreate?.userErrors || [];
|
|
|
|
if (createErrors.length > 0) {
|
|
log(shop, `⚠️ [FulfillmentService] Create errors: ${JSON.stringify(createErrors)}`);
|
|
// Service likely already exists — query it instead
|
|
log(shop, `🔍 [FulfillmentService] Querying existing fulfillment service...`);
|
|
const existingResp = await client.post('', {
|
|
query: `
|
|
query {
|
|
fulfillmentServices(type: THIRD_PARTY) {
|
|
id serviceName callbackUrl handle
|
|
location { id name }
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
const services = existingResp.data?.data?.fulfillmentServices || [];
|
|
fulfillmentService = services.find(s => s.handle === 'data4autos-distribution') || services[0] || null;
|
|
|
|
if (!fulfillmentService) {
|
|
log(shop, `❌ [FulfillmentService] Could not create or find existing service`);
|
|
return { fulfillmentService: null, locationId: null };
|
|
}
|
|
log(shop, `✅ [FulfillmentService] Found existing: ${fulfillmentService.id}`);
|
|
} else {
|
|
log(shop, `✅ [FulfillmentService] Created: ${fulfillmentService.id}`);
|
|
}
|
|
|
|
// The fulfillment service has its own Shopify-managed location — use it as fallback
|
|
// Shopify sometimes returns location: null from the create mutation — re-query if so
|
|
let serviceFallbackLocationId = fulfillmentService?.location?.id || null;
|
|
if (!serviceFallbackLocationId) {
|
|
try {
|
|
log(shop, `🔍 [FulfillmentService] location null from mutation — re-querying fulfillment services...`);
|
|
const reQueryResp = await client.post('', {
|
|
query: `query { fulfillmentServices(type: THIRD_PARTY) { id handle location { id name } } }`,
|
|
});
|
|
const svcs = reQueryResp.data?.data?.fulfillmentServices || [];
|
|
const matched = svcs.find(s => s.handle === 'data4autos-distribution') || svcs[0] || null;
|
|
serviceFallbackLocationId = matched?.location?.id || null;
|
|
log(shop, `📍 [FulfillmentService] Re-query location: ${serviceFallbackLocationId}`);
|
|
} catch (reQErr) {
|
|
log(shop, `⚠️ [FulfillmentService] Re-query failed: ${reQErr.message}`);
|
|
}
|
|
}
|
|
log(shop, `📍 [FulfillmentService] Service location (fallback): ${serviceFallbackLocationId}`);
|
|
|
|
// ── Step 2: Try to create a custom named location ────────────────────────
|
|
// First get the store's primary location address (search broadly, not by exact name)
|
|
let storeAddress = null;
|
|
try {
|
|
const locResp = await client.post('', {
|
|
query: `query { locations(first: 1) { nodes { id name address { address1 address2 city province provinceCode country countryCode zip phone } } } }`,
|
|
});
|
|
const locNode = locResp.data?.data?.locations?.nodes?.[0];
|
|
if (locNode) {
|
|
storeAddress = locNode.address;
|
|
log(shop, `📍 [FulfillmentService] Store address from "${locNode.name}": ${JSON.stringify(storeAddress)}`);
|
|
}
|
|
} catch (addrErr) {
|
|
log(shop, `⚠️ [FulfillmentService] Could not fetch store address: ${addrErr.message}`);
|
|
}
|
|
|
|
let customLocationId = null;
|
|
if (storeAddress) {
|
|
try {
|
|
const provinceCode = storeAddress.provinceCode || storeAddress.province || '';
|
|
const countryCode = storeAddress.countryCode || 'US';
|
|
|
|
const locAddResp = await client.post('', {
|
|
query: `
|
|
mutation {
|
|
locationAdd(input: {
|
|
name: "(App) Data4Autos Distribution API",
|
|
address: {
|
|
address1: ${JSON.stringify(storeAddress.address1 || '')},
|
|
address2: ${JSON.stringify(storeAddress.address2 || '')},
|
|
city: ${JSON.stringify(storeAddress.city || '')},
|
|
provinceCode: ${JSON.stringify(provinceCode)},
|
|
countryCode: ${countryCode},
|
|
zip: ${JSON.stringify(storeAddress.zip || '')},
|
|
phone: ${JSON.stringify(storeAddress.phone || '')}
|
|
},
|
|
fulfillsOnlineOrders: true
|
|
}) {
|
|
location { id name }
|
|
userErrors { code field message }
|
|
}
|
|
}
|
|
`,
|
|
});
|
|
|
|
const locAddData = locAddResp.data?.data?.locationAdd;
|
|
if (locAddData?.userErrors?.length > 0) {
|
|
log(shop, `⚠️ [FulfillmentService] locationAdd errors: ${JSON.stringify(locAddData.userErrors)}`);
|
|
} else if (locAddData?.location?.id) {
|
|
customLocationId = locAddData.location.id;
|
|
log(shop, `✅ [FulfillmentService] Custom location created: ${customLocationId}`);
|
|
}
|
|
} catch (locErr) {
|
|
log(shop, `⚠️ [FulfillmentService] locationAdd threw: ${locErr.message}`);
|
|
}
|
|
}
|
|
|
|
// Use custom location if created, otherwise fall back to fulfillment service location
|
|
const locationId = customLocationId || serviceFallbackLocationId;
|
|
log(shop, `📍 [FulfillmentService] Final locationId: ${locationId} (${customLocationId ? 'custom' : 'service fallback'})`);
|
|
|
|
return { fulfillmentService, locationId };
|
|
|
|
} catch (error) {
|
|
const errDetail = error.response ? JSON.stringify(error.response.data) : error.message;
|
|
log(shop, `💥 [FulfillmentService] Request failed: ${errDetail}`);
|
|
return { fulfillmentService: null, locationId: null };
|
|
}
|
|
}
|
|
|
|
module.exports = { createFulfillmentService };
|