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 const serviceFallbackLocationId = fulfillmentService?.location?.id || null; 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 };