diff --git a/server/routes/businesses.js b/server/routes/businesses.js index 53497eb..2d29f33 100644 --- a/server/routes/businesses.js +++ b/server/routes/businesses.js @@ -18,7 +18,9 @@ const axios = require('axios'); const MERCHANT_ID = () => process.env.MERCHANT_ID; function normalizeScopeId(value) { - return typeof value === 'string' ? value.trim() : ''; + if (typeof value === 'string') return value.trim(); + if (typeof value === 'number' && Number.isFinite(value)) return String(value); + return ''; } function getCompanyId(req) { @@ -102,6 +104,34 @@ async function findBusinessByApplicationId(merchantId, applicationId) { return brandMatches[0] || null; } +async function findBusinessByBrandName(merchantId, brandName) { + const normalizedBrandName = normalizeText(brandName).toLowerCase(); + if (!normalizedBrandName) return null; + + const businesses = await getIndex(merchantId); + const brandMatches = businesses.filter((business) => normalizeText(business.brandName).toLowerCase() === normalizedBrandName); + + if (brandMatches.length > 1) { + throw createHttpError( + 409, + 'Multiple businesses matched the provided brand name', + { + code: 'AMBIGUOUS_BUSINESS_MATCH', + details: { + companyId: merchantId, + brandName: normalizedBrandName, + matchedBusinesses: brandMatches.map((business) => ({ + businessId: business.businessId, + brandName: business.brandName, + })), + }, + } + ); + } + + return brandMatches[0] || null; +} + const PROVIDER_FIELDS = ['providerName', 'senderId', 'dltEntityId', 'authKey']; function createHttpError(status, message, extra = {}) { @@ -152,6 +182,54 @@ function normalizeProvider(provider = {}, fallbackUpdatedAt = null) { }; } +function getShipmentPayload(body) { + return body?.payload?.shipment && typeof body.payload.shipment === 'object' + ? body.payload.shipment + : null; +} + +function getShipmentBrandName(body) { + return normalizeText(body?.payload?.shipment?.bags?.[0]?.brand?.brand_name); +} + +function getShipmentEventKey(body) { + return normalizeText(body?.payload?.shipment?.status); +} + +function getShipmentToNumber(body) { + return normalizeText(body?.payload?.shipment?.user?.mobile); +} + +async function sendResolveTemplateWorkflow({ template, toNumber, sourcePayload }) { + const workflowUrl = normalizeText(process.env.WORKFLOW_URL_RESOLVE_TEMPLATE); + if (!workflowUrl) { + throw createHttpError(500, 'WORKFLOW_URL_RESOLVE_TEMPLATE is not configured'); + } + + const response = await axios.post( + workflowUrl, + { template, toNumber, sourcePayload }, + { + timeout: 30000, + headers: { 'Content-Type': 'application/json' }, + validateStatus: () => true, + } + ); + + if (response.status < 200 || response.status >= 300) { + throw createHttpError( + 502, + `Resolve-template workflow failed with status ${response.status}`, + { details: response.data } + ); + } + + return { + statusCode: response.status, + response: response.data, + }; +} + function getProviderPatch(input) { if (!input || typeof input !== 'object') return null; @@ -418,16 +496,20 @@ router.post('/resolve-template', async (req, res) => { console.log('[ResolveTemplate] Incoming payload:', JSON.stringify(req.body, null, 2)); const companyId = getCompanyId(req); - const applicationId = getApplicationId(req); - const event = normalizeText(req.body?.event); + const shipment = getShipmentPayload(req.body); + const brandName = getShipmentBrandName(req.body); + const event = getShipmentEventKey(req.body); + const toNumber = getShipmentToNumber(req.body); if (!companyId) return res.status(400).json({ error: 'companyId is required' }); - if (!applicationId) return res.status(400).json({ error: 'applicationId is required' }); - if (!event) return res.status(400).json({ error: 'event is required' }); + if (!shipment) return res.status(400).json({ error: 'payload.shipment is required' }); + if (!brandName) return res.status(400).json({ error: 'payload.shipment.bags[0].brand.brand_name is required' }); + if (!event) return res.status(400).json({ error: 'payload.shipment.status is required' }); + if (!toNumber) return res.status(400).json({ error: 'payload.shipment.user.mobile is required' }); - const business = await findBusinessByApplicationId(companyId, applicationId); + const business = await findBusinessByBrandName(companyId, brandName); if (!business) { - return res.status(404).json({ error: 'Business not found for applicationId' }); + return res.status(404).json({ error: 'Business not found for brand name' }); } const eventSlug = slugify(event); @@ -438,13 +520,22 @@ router.post('/resolve-template', async (req, res) => { return res.status(404).json({ error: 'Whitelisted template not found' }); } + const workflowResult = await sendResolveTemplateWorkflow({ + template: tmpl.selectedTemplate, + toNumber, + sourcePayload: req.body, + }); + res.json({ success: true, companyId, - applicationId, + businessId: business.businessId, + brandName, event: eventSlug, templateId: normalizeText(tmpl.templateId), template: tmpl.selectedTemplate, + toNumber, + workflowResult, }); } catch (err) { sendRouteError(res, err);