@aglyn/plugins-commerce 1.0.0-beta.249 → 1.0.0-beta.252
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/commerce-console-page.d.ts +1 -1
- package/src/lib/components/commerce-console-page.js +139 -175
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/commerce-console-sections.d.ts +30 -2
- package/src/lib/components/commerce-console-sections.js +82 -7
- package/src/lib/components/commerce-console-sections.js.map +1 -1
- package/src/lib/components/console/discounts-card.component.js +36 -3
- package/src/lib/components/console/discounts-card.component.js.map +1 -1
- package/src/lib/components/console/host-coupons-card.component.js +1 -1
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-pairing.component.d.ts +59 -0
- package/src/lib/components/console/pos/pos-pairing.component.js +174 -0
- package/src/lib/components/console/pos/pos-pairing.component.js.map +1 -0
- package/src/lib/components/console/pos/use-pos-display.d.ts +0 -8
- package/src/lib/components/console/pos/use-pos-display.js +2 -16
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-devices-card.component.d.ts +12 -1
- package/src/lib/components/console/pos-devices-card.component.js +132 -59
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.d.ts +1 -1
- package/src/lib/components/console/pos-page.component.js +37 -86
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/registers-card.component.js +13 -0
- package/src/lib/components/console/registers-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +6 -11
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/store-zones.d.ts +15 -0
- package/src/lib/components/console/store-zones.js +36 -4
- package/src/lib/components/console/store-zones.js.map +1 -1
- package/src/lib/components/console/suppliers-card.component.js +2 -2
- package/src/lib/components/console/suppliers-card.component.js.map +1 -1
- package/src/lib/model/commerce-record-routes.js +10 -3
- package/src/lib/model/commerce-record-routes.js.map +1 -1
- package/src/lib/model/commerce-shipping.d.ts +16 -0
- package/src/lib/model/commerce-shipping.js +21 -0
- package/src/lib/model/commerce-shipping.js.map +1 -1
- package/src/lib/model/commerce-tax-decision.js +2 -2
- package/src/lib/model/commerce-tax-decision.js.map +1 -1
- package/src/lib/plugin.js +4 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +13 -0
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/order-webhooks.js +1 -1
- package/src/lib/server/order-webhooks.js.map +1 -1
- package/src/lib/server/shippingeasy.js +1 -1
- package/src/lib/server/shippingeasy.js.map +1 -1
- package/src/lib/server/store-setup-ai-capability.js +10 -2
- package/src/lib/server/store-setup-ai-capability.js.map +1 -1
- package/src/lib/server/supplier-routing.d.ts +86 -0
- package/src/lib/server/supplier-routing.js +175 -0
- package/src/lib/server/supplier-routing.js.map +1 -0
|
@@ -269,7 +269,7 @@ function expiresAt(now) {
|
|
|
269
269
|
type: 'content.order',
|
|
270
270
|
title: 'An order webhook could not be delivered',
|
|
271
271
|
body: `${envelope.event} never reached ${safeHost(endpoint.url)} on {site} after ` + `${envelope.attempt} attempts (${(_settled_attempt_error = settled.attempt.error) != null ? _settled_attempt_error : 'no answer'}). ` + 'Resend it from the delivery log once the endpoint is fixed.',
|
|
272
|
-
link: `/${envelope.hostId}/products/
|
|
272
|
+
link: `/${envelope.hostId}/products/notifications`
|
|
273
273
|
}).catch(()=>undefined);
|
|
274
274
|
}
|
|
275
275
|
return status;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-webhooks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport type { PluginDomainEventEnvelope } from '@aglyn/aglyn/plugin-manager/plugin-domain-events'\nimport {\n describeConfiguredUrlRefusal,\n configuredUrlRefusal,\n fetchConfiguredPublicUrl,\n firebaseAdmin,\n notifyHostManagers,\n} from '@aglyn/tenant-data-admin'\nimport { PLUGIN_EVENT_MAX_ATTEMPTS } from '@aglyn/tenant-data-admin/server/plugin-event-outbox'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n createSecretBoxKey,\n openSecret,\n parseSecretBoxKeyring,\n sealSecret,\n SECRET_BOX_KEY_BYTES,\n type SecretBoxKey,\n type SecretBoxKeyring,\n} from '@aglyn/shared-util-tools/secret-box'\nimport { createHmac, hkdfSync, randomBytes } from 'node:crypto'\nimport { COMMERCE_EVENT_DECLARATIONS } from '../model/order-events'\nimport * as Webhooks from '../model/order-webhooks'\n\n/**\n * Merchant order webhooks (AGL-3611): the console's door and the delivery\n * behind it. The model (`model/order-webhooks.ts`) says what is stored where.\n *\n * DELIVERY RIDES THE PLUGIN EVENT OUTBOX. Commerce subscribes to its own\n * order and return events under the name `webhooks`; the subscriber posts the\n * event to every endpoint that takes it and records each attempt in the\n * delivery log. An endpoint that already took an event is not posted it\n * again, so when one of three endpoints fails, the subscriber throws, the\n * outbox retries it with its backoff (30s, 2m, 10m, 30m, 1h, 6h, 12h), and\n * only the failing endpoint is called again. On the last attempt the delivery\n * is marked failed and the site's managers are told.\n *\n * EVERY CALL GOES THROUGH `fetchConfiguredPublicUrl`: https on the default\n * port, a public address pinned at connect time, no redirect followed. A\n * merchant's endpoint is a stranger's server to us.\n *\n * THE SECRET IS SEALED with the shared secret box, under the keyring\n * `readCommerceSecretKeyring` answers, bound to its endpoint by the seal's\n * context. The plaintext leaves the server once, in the answer to the request\n * that made or rolled it.\n */\n\nexport const COMMERCE_SECRET_KEY_ENV = 'COMMERCE_SECRET_KEY'\n\nconst SECRETS = 'orderWebhookSecrets'\n\n/** Delivery rows live this long, then Firestore's TTL removes them. */\nexport const ORDER_WEBHOOK_LOG_RETENTION_MS = 30 * 86_400_000\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The id of the key derived from `TOKEN_SIGNING_SECRET`. */\nexport const DERIVED_COMMERCE_KEY_ID = 'tss1'\n\n/**\n * A secret-box key derived from `TOKEN_SIGNING_SECRET` with HKDF, under its\n * own label, so the commerce tokens and the sealed secrets never share key\n * material. `null` when that secret is unset.\n */\nfunction derivedCommerceKey(): SecretBoxKey | null {\n const base = String(process.env['TOKEN_SIGNING_SECRET'] ?? '')\n if (!base) return null\n const material = new Uint8Array(hkdfSync('sha256', base, 'aglyn-commerce', 'secret-box:v1', SECRET_BOX_KEY_BYTES))\n return createSecretBoxKey(material, DERIVED_COMMERCE_KEY_ID)\n}\n\n/**\n * The keyring commerce seals its stored secrets with. `COMMERCE_SECRET_KEY`\n * (a secret-box keyring) when it is set, its first key sealing; otherwise the\n * key derived from `TOKEN_SIGNING_SECRET`, which every deployment that sells\n * already has. The derived key stays in the ring after a dedicated key is\n * configured, so what it sealed still opens. `null` when neither is usable.\n * Never throws.\n */\nexport function readCommerceSecretKeyring(): SecretBoxKeyring | null {\n let derived: SecretBoxKey | null = null\n try {\n derived = derivedCommerceKey()\n } catch {\n derived = null\n }\n const raw = String(process.env[COMMERCE_SECRET_KEY_ENV] ?? '').trim()\n if (raw) {\n try {\n const ring = parseSecretBoxKeyring(raw)\n const keys = derived && !ring.keys.some((key) => key.id === derived!.id) ? [...ring.keys, derived] : ring.keys\n return { current: ring.current, keys }\n } catch {\n // An unusable dedicated key falls back to the derived one rather than\n // sealing nothing.\n }\n }\n return derived ? { current: derived, keys: [derived] } : null\n}\n\n/** Whether this deployment can sign webhooks at all. */\nexport function orderWebhooksConfigured(): boolean {\n return readCommerceSecretKeyring() !== null\n}\n\nconst sealContext = (hostId: string, endpointId: string) => `hosts/${hostId}/${SECRETS}/${endpointId}#secret`\n\n/** A fresh signing secret: `whsec_` and 32 random bytes. */\nexport function mintOrderWebhookSecret(): string {\n return `whsec_${randomBytes(32).toString('base64url')}`\n}\n\n/** The HMAC-SHA256 of `{t}.{body}` under the secret, as hex. */\nexport function signOrderWebhook(secret: string, timestampSeconds: number, body: string): string {\n return createHmac('sha256', secret).update(Webhooks.orderWebhookSignedPayload(timestampSeconds, body)).digest('hex')\n}\n\nconst hostRefFor = (firestore: Firestore, hostId: string) => firestore.collection('hosts').doc(hostId)\n\nasync function readSecret(\n firestore: Firestore,\n hostId: string,\n endpointId: string,\n keyring: SecretBoxKeyring,\n): Promise<string> {\n const hostRef = hostRefFor(firestore, hostId)\n const snapshot = await hostRef.collection('orderWebhookSecrets').doc(endpointId).get()\n const sealed = String(snapshot.get('sealedSecret') ?? '')\n if (!sealed) throw new Error('This endpoint has no signing secret')\n return openSecret(sealed, keyring, { context: sealContext(hostId, endpointId) }).plaintext\n}\n\nfunction sealedSecretRecord(\n hostId: string,\n endpointId: string,\n secret: string,\n keyring: SecretBoxKeyring,\n now: number,\n): Webhooks.OrderWebhookSecretRecord {\n return {\n sealedSecret: sealSecret(secret, keyring.current, { context: sealContext(hostId, endpointId) }),\n secretKeyId: keyring.current.id,\n updatedAtMs: now,\n }\n}\n\n/** What one POST came to. */\nexport interface OrderWebhookPostResult {\n attempt: Webhooks.OrderWebhookAttempt\n delivered: boolean\n}\n\n/**\n * Posts one body to one endpoint, signed. Never throws: a refusal, a timeout\n * or a reset is an attempt that failed, with the reason in words.\n */\nexport async function postOrderWebhook(input: {\n url: string\n secret: string\n event: string\n eventId: string\n body: string\n now?: () => number\n fetchImpl?: typeof fetchConfiguredPublicUrl\n}): Promise<OrderWebhookPostResult> {\n const clock = input.now ?? Date.now\n const startedAt = clock()\n const timestamp = Math.floor(startedAt / 1000)\n const signature = Webhooks.formatOrderWebhookSignature(timestamp, signOrderWebhook(input.secret, timestamp, input.body))\n let httpStatus: number | null = null\n let error: string | undefined\n try {\n const result = await (input.fetchImpl ?? fetchConfiguredPublicUrl)(input.url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'user-agent': 'Aglyn-Webhooks/1.0',\n [Webhooks.ORDER_WEBHOOK_SIGNATURE_HEADER]: signature,\n [Webhooks.ORDER_WEBHOOK_EVENT_HEADER]: input.event,\n [Webhooks.ORDER_WEBHOOK_EVENT_ID_HEADER]: input.eventId,\n },\n body: input.body,\n signal: AbortSignal.timeout(Webhooks.ORDER_WEBHOOK_TIMEOUT_MS),\n })\n if ('refusal' in result) {\n error = `Not sent: ${describeConfiguredUrlRefusal(result.refusal)}`\n } else {\n httpStatus = result.status\n if (!Webhooks.orderWebhookAccepted(httpStatus)) {\n error =\n httpStatus >= 300 && httpStatus < 400\n ? `The endpoint answered ${httpStatus}, a redirect; redirects are not followed`\n : `The endpoint answered ${httpStatus}`\n }\n }\n } catch (caught) {\n const name = (caught as Error)?.name\n error =\n name === 'TimeoutError' || name === 'AbortError'\n ? `No answer within ${Webhooks.ORDER_WEBHOOK_TIMEOUT_MS / 1000} seconds`\n : `The connection failed: ${String((caught as Error)?.message ?? caught).slice(0, 200)}`\n }\n const attempt: Webhooks.OrderWebhookAttempt = {\n atMs: startedAt,\n httpStatus,\n ...(error ? { error } : {}),\n durationMs: Math.max(0, clock() - startedAt),\n }\n return { attempt, delivered: !error }\n}\n\nfunction expiresAt(now: number) {\n return firebaseAdmin.firestore.Timestamp.fromMillis(now + ORDER_WEBHOOK_LOG_RETENTION_MS)\n}\n\n/** Records an attempt on the delivery row and on the endpoint's summary. */\nasync function recordAttempt(\n firestore: Firestore,\n hostId: string,\n endpointId: string,\n delivery: {\n id: string\n eventId: string\n event: string\n orderId: string | null\n body: string\n test?: boolean\n prior: Webhooks.OrderWebhookDelivery | null\n },\n result: OrderWebhookPostResult,\n status: Webhooks.OrderWebhookDeliveryStatus,\n now: number,\n): Promise<void> {\n const hostRef = hostRefFor(firestore, hostId)\n const row: Webhooks.OrderWebhookDelivery & { expiresAt: unknown } = {\n endpointId,\n eventId: delivery.eventId,\n event: delivery.event,\n orderId: delivery.orderId,\n status,\n attempts: Webhooks.appendOrderWebhookAttempt(delivery.prior?.attempts, result.attempt),\n body: delivery.body.slice(0, Webhooks.ORDER_WEBHOOK_BODY_MAX),\n ...(delivery.test ? { test: true } : {}),\n createdAtMs: delivery.prior?.createdAtMs ?? now,\n updatedAtMs: now,\n expiresAt: expiresAt(now),\n }\n await hostRef.collection('orderWebhookDeliveries').doc(delivery.id).set(row)\n await hostRef\n .collection('orderWebhooks')\n .doc(endpointId)\n .update({\n lastDeliveryAtMs: now,\n lastDeliveryStatus: status,\n consecutiveFailures: result.delivered ? 0 : firebaseAdmin.firestore.FieldValue.increment(1),\n })\n .catch(() => undefined)\n}\n\n/**\n * The subscriber: posts one event to every endpoint of the site that takes\n * it and has not taken it already. Throws when any endpoint still owes it, so\n * the outbox retries; on the outbox's last attempt it records the failure and\n * tells the site's managers instead.\n */\nexport async function deliverOrderEventToWebhooks(\n envelope: PluginDomainEventEnvelope,\n deps: { fetchImpl?: typeof fetchConfiguredPublicUrl; now?: () => number } = {},\n): Promise<{ delivered: number; failed: number }> {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = hostRefFor(firestore, envelope.hostId)\n const endpoints = await hostRef.collection('orderWebhooks').where('enabled', '==', true).get()\n const taking = endpoints.docs.filter((doc) =>\n Webhooks.orderWebhookTakes(doc.data() as Webhooks.OrderWebhookEndpoint, envelope.event, envelope.occurredAtMs),\n )\n if (taking.length === 0) return { delivered: 0, failed: 0 }\n const keyring = readCommerceSecretKeyring()\n const clock = deps.now ?? Date.now\n const final = envelope.attempt >= PLUGIN_EVENT_MAX_ATTEMPTS\n const body = Webhooks.orderWebhookBody({\n eventId: envelope.id,\n event: envelope.event,\n occurredAtMs: envelope.occurredAtMs,\n hostId: envelope.hostId,\n payload: envelope.payload,\n })\n const orderId = String(((envelope.payload ?? {}) as { order?: { id?: unknown } }).order?.id ?? '') || null\n const outcomes = await Promise.all(\n taking.map(async (doc) => {\n const endpoint = doc.data() as Webhooks.OrderWebhookEndpoint\n const deliveryId = Webhooks.orderWebhookDeliveryId(envelope.id, doc.id)\n const priorSnapshot = await hostRef.collection('orderWebhookDeliveries').doc(deliveryId).get()\n const prior = priorSnapshot.exists ? (priorSnapshot.data() as Webhooks.OrderWebhookDelivery) : null\n // Taken already, on an earlier pass or by a resend from the console.\n if (prior?.status === 'delivered') return 'delivered' as const\n const settled = await attemptEndpoint({\n firestore,\n hostId: envelope.hostId,\n endpointId: doc.id,\n url: endpoint.url,\n keyring,\n event: envelope.event,\n eventId: envelope.id,\n body,\n clock,\n fetchImpl: deps.fetchImpl,\n })\n const status: Webhooks.OrderWebhookDeliveryStatus = settled.delivered ? 'delivered' : final ? 'failed' : 'retrying'\n await recordAttempt(\n firestore,\n envelope.hostId,\n doc.id,\n { id: deliveryId, eventId: envelope.id, event: envelope.event, orderId, body, prior },\n settled,\n status,\n clock(),\n )\n if (status === 'failed') {\n await notifyHostManagers(envelope.hostId, {\n type: 'content.order',\n title: 'An order webhook could not be delivered',\n body:\n `${envelope.event} never reached ${safeHost(endpoint.url)} on {site} after ` +\n `${envelope.attempt} attempts (${settled.attempt.error ?? 'no answer'}). ` +\n 'Resend it from the delivery log once the endpoint is fixed.',\n link: `/${envelope.hostId}/products/settings`,\n }).catch(() => undefined)\n }\n return status\n }),\n )\n const delivered = outcomes.filter((status) => status === 'delivered').length\n const failed = outcomes.length - delivered\n if (failed > 0 && !final) {\n throw new Error(`${failed} of ${outcomes.length} order webhook endpoints did not take ${envelope.event}`)\n }\n return { delivered, failed }\n}\n\n/** One signed attempt at one endpoint; a missing key or secret is a failed attempt. */\nasync function attemptEndpoint(input: {\n firestore: Firestore\n hostId: string\n endpointId: string\n url: string\n keyring: SecretBoxKeyring | null\n event: string\n eventId: string\n body: string\n clock: () => number\n fetchImpl?: typeof fetchConfiguredPublicUrl\n}): Promise<OrderWebhookPostResult> {\n const failed = (error: string): OrderWebhookPostResult => ({\n delivered: false,\n attempt: { atMs: input.clock(), httpStatus: null, error, durationMs: 0 },\n })\n if (!input.keyring) return failed('Webhook signing is not configured')\n let secret: string\n try {\n secret = await readSecret(input.firestore, input.hostId, input.endpointId, input.keyring)\n } catch (error) {\n return failed(`The signing secret could not be read: ${String((error as Error)?.message ?? error).slice(0, 120)}`)\n }\n return postOrderWebhook({\n url: input.url,\n secret,\n event: input.event,\n eventId: input.eventId,\n body: input.body,\n fetchImpl: input.fetchImpl,\n now: input.clock,\n })\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).hostname\n } catch {\n return 'the endpoint'\n }\n}\n\n// ---------------------------------------------------------------------------\n// The console's door\n// ---------------------------------------------------------------------------\n\nconst ACTIONS = ['status', 'create', 'update', 'roll-secret', 'delete', 'test', 'resend'] as const\ntype Action = (typeof ACTIONS)[number]\n\nconst isId = (value: string) => Boolean(value) && value.length <= 1500 && !/^__.*__$/.test(value) && !value.includes('/')\n\nfunction cleanUrl(raw: unknown): { url: string } | { error: string } {\n const url = String(raw ?? '').trim()\n if (!url) return { error: 'Enter the endpoint’s address' }\n if (url.length > 2048) return { error: 'That address is too long' }\n const refusal = configuredUrlRefusal(url)\n if (refusal) return { error: `This endpoint cannot be used: ${describeConfiguredUrlRefusal(refusal)}` }\n return { url }\n}\n\nconst cleanDescription = (raw: unknown) => String(raw ?? '').trim().slice(0, 120)\n\n/**\n * `POST /api/commerce/order-webhooks` with `{ hostId, action, … }`. Endpoints\n * post the order book to an outside server, so only an admin of the whole\n * workspace manages them — the bar a refund sets.\n *\n * - `status` → `{ configured, events: [{ event, label, description }] }`\n * - `create` `{ url, events, description? }` → `{ id, secret }` (shown once)\n * - `update` `{ endpointId, url?, events?, enabled?, description? }`\n * - `roll-secret` `{ endpointId }` → `{ secret }`\n * - `delete` `{ endpointId }`; the delivery log stays until it expires\n * - `test` `{ endpointId }` → posts a `webhook.test` event now\n * - `resend` `{ deliveryId }` → posts a logged delivery again now\n */\nexport const orderWebhooksHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let body: Record<string, any>\n try {\n body = (typeof req.body === 'string' ? JSON.parse(req.body) : req.body) ?? {}\n } catch {\n return res.status(400).json({ error: 'Invalid JSON' })\n }\n const hostId = String(body.hostId ?? '')\n const action = String(body.action ?? '') as Action\n if (!isId(hostId)) return res.status(400).json({ error: 'Missing hostId' })\n if (!ACTIONS.includes(action)) return res.status(400).json({ error: 'Unknown action' })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = hostRefFor(firestore, hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) return res.status(404).json({ error: 'Unknown site' })\n if ((hostSnapshot.get('memberRoles') ?? {})[decoded.uid] !== 'admin') {\n return res.status(403).json({ error: 'Webhooks are managed by a site admin' })\n }\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.orgWide || membership.hostRole !== 'admin') {\n return res.status(403).json({ error: 'Webhooks are managed by an admin of the whole workspace' })\n }\n const keyring = readCommerceSecretKeyring()\n if (action === 'status') {\n return res.status(200).json({\n configured: Boolean(keyring),\n maxEndpoints: Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS,\n events: COMMERCE_EVENT_DECLARATIONS.map((entry) => ({\n event: entry.event.id,\n label: entry.label,\n description: entry.description,\n })),\n })\n }\n if (!keyring) {\n return res.status(503).json({ error: 'Order webhooks are not configured on this deployment' })\n }\n const now = Date.now()\n const endpoints = hostRef.collection('orderWebhooks')\n\n if (action === 'create') {\n const url = cleanUrl(body.url)\n if ('error' in url) return res.status(400).json({ error: url.error })\n const events = Webhooks.normalizeOrderWebhookEvents(body.events)\n if (events.length === 0) return res.status(400).json({ error: 'Choose at least one event' })\n const secret = mintOrderWebhookSecret()\n const ref = endpoints.doc(createResourceUid())\n const created = await firestore.runTransaction(async (transaction) => {\n const existing = await transaction.get(endpoints.limit(Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS))\n if (existing.size >= Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS) return false\n const endpoint: Webhooks.OrderWebhookEndpoint = {\n url: url.url,\n events,\n enabled: true,\n ...(cleanDescription(body.description) ? { description: cleanDescription(body.description) } : {}),\n secretHint: secret.slice(-4),\n consecutiveFailures: 0,\n createdAtMs: now,\n updatedAtMs: now,\n createdBy: decoded.uid,\n }\n transaction.create(ref, endpoint)\n transaction.create(hostRef.collection('orderWebhookSecrets').doc(ref.id), sealedSecretRecord(hostId, ref.id, secret, keyring, now))\n return true\n })\n if (!created) {\n return res.status(409).json({ error: `A store can keep ${Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS} webhook endpoints` })\n }\n return res.status(200).json({ ok: true, id: ref.id, secret })\n }\n\n if (action === 'resend') {\n const deliveryId = String(body.deliveryId ?? '')\n if (!isId(deliveryId)) return res.status(400).json({ error: 'Missing deliveryId' })\n const deliveryRef = hostRef.collection('orderWebhookDeliveries').doc(deliveryId)\n const deliverySnapshot = await deliveryRef.get()\n if (!deliverySnapshot.exists) return res.status(404).json({ error: 'Unknown delivery' })\n const delivery = deliverySnapshot.data() as Webhooks.OrderWebhookDelivery\n const endpointSnapshot = await endpoints.doc(delivery.endpointId).get()\n if (!endpointSnapshot.exists) return res.status(404).json({ error: 'That endpoint was deleted' })\n if (delivery.body.length >= Webhooks.ORDER_WEBHOOK_BODY_MAX) {\n return res.status(409).json({ error: 'This delivery was too large to keep, so it cannot be resent' })\n }\n const endpoint = endpointSnapshot.data() as Webhooks.OrderWebhookEndpoint\n const secret = await readSecret(firestore, hostId, delivery.endpointId, keyring)\n const result = await postOrderWebhook({ url: endpoint.url, secret, event: delivery.event, eventId: delivery.eventId, body: delivery.body })\n const status: Webhooks.OrderWebhookDeliveryStatus = result.delivered\n ? 'delivered'\n : delivery.status === 'delivered'\n ? 'delivered'\n : delivery.status\n await recordAttempt(\n firestore,\n hostId,\n delivery.endpointId,\n { id: deliveryId, eventId: delivery.eventId, event: delivery.event, orderId: delivery.orderId, body: delivery.body, test: delivery.test, prior: delivery },\n result,\n status,\n Date.now(),\n )\n return res.status(200).json({ ok: true, delivered: result.delivered, attempt: result.attempt })\n }\n\n const endpointId = String(body.endpointId ?? '')\n if (!isId(endpointId)) return res.status(400).json({ error: 'Missing endpointId' })\n const endpointRef = endpoints.doc(endpointId)\n const endpointSnapshot = await endpointRef.get()\n if (!endpointSnapshot.exists) return res.status(404).json({ error: 'Unknown endpoint' })\n const endpoint = endpointSnapshot.data() as Webhooks.OrderWebhookEndpoint\n\n if (action === 'update') {\n const patch: Partial<Webhooks.OrderWebhookEndpoint> = { updatedAtMs: now }\n if (body.url !== undefined) {\n const url = cleanUrl(body.url)\n if ('error' in url) return res.status(400).json({ error: url.error })\n patch.url = url.url\n }\n if (body.events !== undefined) {\n const events = Webhooks.normalizeOrderWebhookEvents(body.events)\n if (events.length === 0) return res.status(400).json({ error: 'Choose at least one event' })\n patch.events = events\n }\n if (body.enabled !== undefined) {\n patch.enabled = Boolean(body.enabled)\n if (patch.enabled) patch.consecutiveFailures = 0\n }\n if (body.description !== undefined) patch.description = cleanDescription(body.description)\n await endpointRef.update(patch)\n return res.status(200).json({ ok: true })\n }\n\n if (action === 'roll-secret') {\n const secret = mintOrderWebhookSecret()\n const batch = firestore.batch()\n batch.set(hostRef.collection('orderWebhookSecrets').doc(endpointId), sealedSecretRecord(hostId, endpointId, secret, keyring, now))\n batch.update(endpointRef, { secretHint: secret.slice(-4), updatedAtMs: now })\n await batch.commit()\n return res.status(200).json({ ok: true, secret })\n }\n\n if (action === 'delete') {\n const batch = firestore.batch()\n batch.delete(endpointRef)\n batch.delete(hostRef.collection('orderWebhookSecrets').doc(endpointId))\n await batch.commit()\n return res.status(200).json({ ok: true })\n }\n\n // test\n const eventId = `test_${randomBytes(9).toString('base64url')}`\n const testBody = Webhooks.orderWebhookBody({\n eventId,\n event: Webhooks.ORDER_WEBHOOK_TEST_EVENT,\n occurredAtMs: now,\n hostId,\n payload: { message: `A test event from ${PLATFORM_BRAND_NAME}. Answer with any 2xx status.` },\n })\n const secret = await readSecret(firestore, hostId, endpointId, keyring)\n const result = await postOrderWebhook({ url: endpoint.url, secret, event: Webhooks.ORDER_WEBHOOK_TEST_EVENT, eventId, body: testBody })\n await recordAttempt(\n firestore,\n hostId,\n endpointId,\n { id: Webhooks.orderWebhookDeliveryId(eventId, endpointId), eventId, event: Webhooks.ORDER_WEBHOOK_TEST_EVENT, orderId: null, body: testBody, test: true, prior: null },\n result,\n result.delivered ? 'delivered' : 'failed',\n Date.now(),\n )\n return res.status(200).json({ ok: true, delivered: result.delivered, attempt: result.attempt })\n } catch (error) {\n console.error('order webhooks action failed', action, error)\n return res.status(500).json({ error: 'The webhook could not be updated. Please try again.' })\n }\n}\n"],"names":["createResourceUid","PLATFORM_BRAND_NAME","describeConfiguredUrlRefusal","configuredUrlRefusal","fetchConfiguredPublicUrl","firebaseAdmin","notifyHostManagers","PLUGIN_EVENT_MAX_ATTEMPTS","resolveOrgPermissions","createSecretBoxKey","openSecret","parseSecretBoxKeyring","sealSecret","SECRET_BOX_KEY_BYTES","createHmac","hkdfSync","randomBytes","COMMERCE_EVENT_DECLARATIONS","Webhooks","COMMERCE_SECRET_KEY_ENV","SECRETS","ORDER_WEBHOOK_LOG_RETENTION_MS","DERIVED_COMMERCE_KEY_ID","derivedCommerceKey","process","base","String","env","material","Uint8Array","readCommerceSecretKeyring","derived","raw","trim","ring","keys","some","key","id","current","orderWebhooksConfigured","sealContext","hostId","endpointId","mintOrderWebhookSecret","toString","signOrderWebhook","secret","timestampSeconds","body","update","orderWebhookSignedPayload","digest","hostRefFor","firestore","collection","doc","readSecret","keyring","snapshot","hostRef","get","sealed","Error","context","plaintext","sealedSecretRecord","now","sealedSecret","secretKeyId","updatedAtMs","postOrderWebhook","input","clock","Date","startedAt","timestamp","Math","floor","signature","formatOrderWebhookSignature","httpStatus","error","result","fetchImpl","url","method","headers","ORDER_WEBHOOK_SIGNATURE_HEADER","ORDER_WEBHOOK_EVENT_HEADER","event","ORDER_WEBHOOK_EVENT_ID_HEADER","eventId","signal","AbortSignal","timeout","ORDER_WEBHOOK_TIMEOUT_MS","refusal","status","orderWebhookAccepted","caught","name","message","slice","attempt","atMs","durationMs","max","delivered","expiresAt","Timestamp","fromMillis","recordAttempt","delivery","row","orderId","attempts","appendOrderWebhookAttempt","prior","ORDER_WEBHOOK_BODY_MAX","test","createdAtMs","set","lastDeliveryAtMs","lastDeliveryStatus","consecutiveFailures","FieldValue","increment","catch","undefined","deliverOrderEventToWebhooks","envelope","deps","app","endpoints","where","taking","docs","filter","orderWebhookTakes","data","occurredAtMs","length","failed","final","orderWebhookBody","payload","order","outcomes","Promise","all","map","endpoint","deliveryId","orderWebhookDeliveryId","priorSnapshot","exists","settled","attemptEndpoint","type","title","safeHost","link","URL","hostname","ACTIONS","isId","value","Boolean","includes","cleanUrl","cleanDescription","orderWebhooksHandler","req","res","json","authorization","idToken","startsWith","JSON","parse","action","hostSnapshot","decoded","auth","verifyIdToken","uid","membership","orgWide","hostRole","configured","maxEndpoints","ORDER_WEBHOOK_MAX_ENDPOINTS","events","entry","label","description","normalizeOrderWebhookEvents","ref","created","runTransaction","transaction","existing","limit","size","enabled","secretHint","createdBy","create","ok","deliveryRef","deliverySnapshot","endpointSnapshot","endpointRef","patch","batch","commit","delete","testBody","ORDER_WEBHOOK_TEST_EVENT","console"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,mBAAmB,QAAQ,wCAAuC;AAG3E,SACEC,4BAA4B,EAC5BC,oBAAoB,EACpBC,wBAAwB,EACxBC,aAAa,EACbC,kBAAkB,QACb,2BAA0B;AACjC,SAASC,yBAAyB,QAAQ,sDAAqD;AAC/F,SAASC,qBAAqB,QAAQ,wCAAuC;AAC7E,SACEC,kBAAkB,EAClBC,UAAU,EACVC,qBAAqB,EACrBC,UAAU,EACVC,oBAAoB,QAGf,sCAAqC;AAC5C,SAASC,UAAU,EAAEC,QAAQ,EAAEC,WAAW,QAAQ,cAAa;AAC/D,SAASC,2BAA2B,QAAQ,2BAAuB;AACnE,YAAYC,cAAc,6BAAyB;AAEnD;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,OAAO,MAAMC,0BAA0B,sBAAqB;AAE5D,MAAMC,UAAU;AAEhB,qEAAqE,GACrE,OAAO,MAAMC,iCAAiC,KAAK,SAAU;AAI7D,2DAA2D,GAC3D,OAAO,MAAMC,0BAA0B,OAAM;AAE7C;;;;CAIC,GACD,SAASC;QACaC;IAApB,MAAMC,OAAOC,QAAOF,oCAAAA,QAAQG,GAAG,CAAC,uBAAuB,YAAnCH,oCAAuC;IAC3D,IAAI,CAACC,MAAM,OAAO;IAClB,MAAMG,WAAW,IAAIC,WAAWd,SAAS,UAAUU,MAAM,kBAAkB,iBAAiBZ;IAC5F,OAAOJ,mBAAmBmB,UAAUN;AACtC;AAEA;;;;;;;CAOC,GACD,OAAO,SAASQ;QAOKN;IANnB,IAAIO,UAA+B;IACnC,IAAI;QACFA,UAAUR;IACZ,EAAE,eAAM;QACNQ,UAAU;IACZ;IACA,MAAMC,MAAMN,QAAOF,uCAAAA,QAAQG,GAAG,CAACR,wBAAwB,YAApCK,uCAAwC,IAAIS,IAAI;IACnE,IAAID,KAAK;QACP,IAAI;YACF,MAAME,OAAOvB,sBAAsBqB;YACnC,MAAMG,OAAOJ,WAAW,CAACG,KAAKC,IAAI,CAACC,IAAI,CAAC,CAACC,MAAQA,IAAIC,EAAE,KAAKP,QAASO,EAAE,IAAI;mBAAIJ,KAAKC,IAAI;gBAAEJ;aAAQ,GAAGG,KAAKC,IAAI;YAC9G,OAAO;gBAAEI,SAASL,KAAKK,OAAO;gBAAEJ;YAAK;QACvC,EAAE,eAAM;QACN,sEAAsE;QACtE,mBAAmB;QACrB;IACF;IACA,OAAOJ,UAAU;QAAEQ,SAASR;QAASI,MAAM;YAACJ;SAAQ;IAAC,IAAI;AAC3D;AAEA,sDAAsD,GACtD,OAAO,SAASS;IACd,OAAOV,gCAAgC;AACzC;AAEA,MAAMW,cAAc,CAACC,QAAgBC,aAAuB,CAAC,MAAM,EAAED,OAAO,CAAC,EAAEtB,QAAQ,CAAC,EAAEuB,WAAW,OAAO,CAAC;AAE7G,0DAA0D,GAC1D,OAAO,SAASC;IACd,OAAO,CAAC,MAAM,EAAE5B,YAAY,IAAI6B,QAAQ,CAAC,cAAc;AACzD;AAEA,8DAA8D,GAC9D,OAAO,SAASC,iBAAiBC,MAAc,EAAEC,gBAAwB,EAAEC,IAAY;IACrF,OAAOnC,WAAW,UAAUiC,QAAQG,MAAM,CAAChC,SAASiC,yBAAyB,CAACH,kBAAkBC,OAAOG,MAAM,CAAC;AAChH;AAEA,MAAMC,aAAa,CAACC,WAAsBZ,SAAmBY,UAAUC,UAAU,CAAC,SAASC,GAAG,CAACd;AAE/F,eAAee,WACbH,SAAoB,EACpBZ,MAAc,EACdC,UAAkB,EAClBe,OAAyB;QAIHC;IAFtB,MAAMC,UAAUP,WAAWC,WAAWZ;IACtC,MAAMiB,WAAW,MAAMC,QAAQL,UAAU,CAAC,uBAAuBC,GAAG,CAACb,YAAYkB,GAAG;IACpF,MAAMC,SAASpC,QAAOiC,gBAAAA,SAASE,GAAG,CAAC,2BAAbF,gBAAgC;IACtD,IAAI,CAACG,QAAQ,MAAM,IAAIC,MAAM;IAC7B,OAAOrD,WAAWoD,QAAQJ,SAAS;QAAEM,SAASvB,YAAYC,QAAQC;IAAY,GAAGsB,SAAS;AAC5F;AAEA,SAASC,mBACPxB,MAAc,EACdC,UAAkB,EAClBI,MAAc,EACdW,OAAyB,EACzBS,GAAW;IAEX,OAAO;QACLC,cAAcxD,WAAWmC,QAAQW,QAAQnB,OAAO,EAAE;YAAEyB,SAASvB,YAAYC,QAAQC;QAAY;QAC7F0B,aAAaX,QAAQnB,OAAO,CAACD,EAAE;QAC/BgC,aAAaH;IACf;AACF;AAQA;;;CAGC,GACD,OAAO,eAAeI,iBAAiBC,KAQtC;QACeA;IAAd,MAAMC,SAAQD,aAAAA,MAAML,GAAG,YAATK,aAAaE,KAAKP,GAAG;IACnC,MAAMQ,YAAYF;IAClB,MAAMG,YAAYC,KAAKC,KAAK,CAACH,YAAY;IACzC,MAAMI,YAAY7D,SAAS8D,2BAA2B,CAACJ,WAAW9B,iBAAiB0B,MAAMzB,MAAM,EAAE6B,WAAWJ,MAAMvB,IAAI;IACtH,IAAIgC,aAA4B;IAChC,IAAIC;IACJ,IAAI;YACoBV;QAAtB,MAAMW,SAAS,MAAM,EAACX,mBAAAA,MAAMY,SAAS,YAAfZ,mBAAmBpE,0BAA0BoE,MAAMa,GAAG,EAAE;YAC5EC,QAAQ;YACRC,SAAS;gBACP,gBAAgB;gBAChB,cAAc;gBACd,CAACrE,SAASsE,8BAA8B,CAAC,EAAET;gBAC3C,CAAC7D,SAASuE,0BAA0B,CAAC,EAAEjB,MAAMkB,KAAK;gBAClD,CAACxE,SAASyE,6BAA6B,CAAC,EAAEnB,MAAMoB,OAAO;YACzD;YACA3C,MAAMuB,MAAMvB,IAAI;YAChB4C,QAAQC,YAAYC,OAAO,CAAC7E,SAAS8E,wBAAwB;QAC/D;QACA,IAAI,aAAab,QAAQ;YACvBD,QAAQ,CAAC,UAAU,EAAEhF,6BAA6BiF,OAAOc,OAAO,GAAG;QACrE,OAAO;YACLhB,aAAaE,OAAOe,MAAM;YAC1B,IAAI,CAAChF,SAASiF,oBAAoB,CAAClB,aAAa;gBAC9CC,QACED,cAAc,OAAOA,aAAa,MAC9B,CAAC,sBAAsB,EAAEA,WAAW,wCAAwC,CAAC,GAC7E,CAAC,sBAAsB,EAAEA,YAAY;YAC7C;QACF;IACF,EAAE,OAAOmB,QAAQ;;QACf,MAAMC,OAAQD,0BAAD,AAACA,OAAkBC,IAAI;QACpCnB,QACEmB,SAAS,kBAAkBA,SAAS,eAChC,CAAC,iBAAiB,EAAEnF,SAAS8E,wBAAwB,GAAG,KAAK,QAAQ,CAAC,GACtE,CAAC,uBAAuB,EAAEtE,eAAQ0E,0BAAD,AAACA,OAAkBE,OAAO,mBAAIF,QAAQG,KAAK,CAAC,GAAG,MAAM;IAC9F;IACA,MAAMC,UAAwC;QAC5CC,MAAM9B;QACNM;OACIC,QAAQ;QAAEA;IAAM,IAAI,CAAC;QACzBwB,YAAY7B,KAAK8B,GAAG,CAAC,GAAGlC,UAAUE;;IAEpC,OAAO;QAAE6B;QAASI,WAAW,CAAC1B;IAAM;AACtC;AAEA,SAAS2B,UAAU1C,GAAW;IAC5B,OAAO9D,cAAciD,SAAS,CAACwD,SAAS,CAACC,UAAU,CAAC5C,MAAM9C;AAC5D;AAEA,0EAA0E,GAC1E,eAAe2F,cACb1D,SAAoB,EACpBZ,MAAc,EACdC,UAAkB,EAClBsE,QAQC,EACD9B,MAA8B,EAC9Be,MAA2C,EAC3C/B,GAAW;;QASoC8C,iBAGhCA;IAVf,MAAMrD,UAAUP,WAAWC,WAAWZ;IACtC,MAAMwE,MAA8D;QAClEvE;QACAiD,SAASqB,SAASrB,OAAO;QACzBF,OAAOuB,SAASvB,KAAK;QACrByB,SAASF,SAASE,OAAO;QACzBjB;QACAkB,UAAUlG,SAASmG,yBAAyB,EAACJ,kBAAAA,SAASK,KAAK,qBAAdL,gBAAgBG,QAAQ,EAAEjC,OAAOqB,OAAO;QACrFvD,MAAMgE,SAAShE,IAAI,CAACsD,KAAK,CAAC,GAAGrF,SAASqG,sBAAsB;OACxDN,SAASO,IAAI,GAAG;QAAEA,MAAM;IAAK,IAAI,CAAC;QACtCC,WAAW,WAAER,mBAAAA,SAASK,KAAK,qBAAdL,iBAAgBQ,WAAW,mBAAItD;QAC5CG,aAAaH;QACb0C,WAAWA,UAAU1C;;IAEvB,MAAMP,QAAQL,UAAU,CAAC,0BAA0BC,GAAG,CAACyD,SAAS3E,EAAE,EAAEoF,GAAG,CAACR;IACxE,MAAMtD,QACHL,UAAU,CAAC,iBACXC,GAAG,CAACb,YACJO,MAAM,CAAC;QACNyE,kBAAkBxD;QAClByD,oBAAoB1B;QACpB2B,qBAAqB1C,OAAOyB,SAAS,GAAG,IAAIvG,cAAciD,SAAS,CAACwE,UAAU,CAACC,SAAS,CAAC;IAC3F,GACCC,KAAK,CAAC,IAAMC;AACjB;AAEA;;;;;CAKC,GACD,OAAO,eAAeC,4BACpBC,QAAmC,EACnCC,OAA4E,CAAC,CAAC;QAUhEA,iBASWD;QAAF;IAjBvB,MAAM7E,YAAYjD,cAAcgI,GAAG,GAAG/E,SAAS;IAC/C,MAAMM,UAAUP,WAAWC,WAAW6E,SAASzF,MAAM;IACrD,MAAM4F,YAAY,MAAM1E,QAAQL,UAAU,CAAC,iBAAiBgF,KAAK,CAAC,WAAW,MAAM,MAAM1E,GAAG;IAC5F,MAAM2E,SAASF,UAAUG,IAAI,CAACC,MAAM,CAAC,CAAClF,MACpCtC,SAASyH,iBAAiB,CAACnF,IAAIoF,IAAI,IAAqCT,SAASzC,KAAK,EAAEyC,SAASU,YAAY;IAE/G,IAAIL,OAAOM,MAAM,KAAK,GAAG,OAAO;QAAElC,WAAW;QAAGmC,QAAQ;IAAE;IAC1D,MAAMrF,UAAU5B;IAChB,MAAM2C,SAAQ2D,YAAAA,KAAKjE,GAAG,YAARiE,YAAY1D,KAAKP,GAAG;IAClC,MAAM6E,QAAQb,SAAS3B,OAAO,IAAIjG;IAClC,MAAM0C,OAAO/B,SAAS+H,gBAAgB,CAAC;QACrCrD,SAASuC,SAAS7F,EAAE;QACpBoD,OAAOyC,SAASzC,KAAK;QACrBmD,cAAcV,SAASU,YAAY;QACnCnG,QAAQyF,SAASzF,MAAM;QACvBwG,SAASf,SAASe,OAAO;IAC3B;IACA,MAAM/B,UAAUzF,gBAAO,SAAA,EAAEyG,oBAAAA,SAASe,OAAO,YAAhBf,oBAAoB,CAAC,GAAoCgB,KAAK,qBAAhE,OAAkE7G,EAAE,mBAAI,OAAO;IACtG,MAAM8G,WAAW,MAAMC,QAAQC,GAAG,CAChCd,OAAOe,GAAG,CAAC,OAAO/F;QAChB,MAAMgG,WAAWhG,IAAIoF,IAAI;QACzB,MAAMa,aAAavI,SAASwI,sBAAsB,CAACvB,SAAS7F,EAAE,EAAEkB,IAAIlB,EAAE;QACtE,MAAMqH,gBAAgB,MAAM/F,QAAQL,UAAU,CAAC,0BAA0BC,GAAG,CAACiG,YAAY5F,GAAG;QAC5F,MAAMyD,QAAQqC,cAAcC,MAAM,GAAID,cAAcf,IAAI,KAAuC;QAC/F,qEAAqE;QACrE,IAAItB,CAAAA,yBAAAA,MAAOpB,MAAM,MAAK,aAAa,OAAO;QAC1C,MAAM2D,UAAU,MAAMC,gBAAgB;YACpCxG;YACAZ,QAAQyF,SAASzF,MAAM;YACvBC,YAAYa,IAAIlB,EAAE;YAClB+C,KAAKmE,SAASnE,GAAG;YACjB3B;YACAgC,OAAOyC,SAASzC,KAAK;YACrBE,SAASuC,SAAS7F,EAAE;YACpBW;YACAwB;YACAW,WAAWgD,KAAKhD,SAAS;QAC3B;QACA,MAAMc,SAA8C2D,QAAQjD,SAAS,GAAG,cAAcoC,QAAQ,WAAW;QACzG,MAAMhC,cACJ1D,WACA6E,SAASzF,MAAM,EACfc,IAAIlB,EAAE,EACN;YAAEA,IAAImH;YAAY7D,SAASuC,SAAS7F,EAAE;YAAEoD,OAAOyC,SAASzC,KAAK;YAAEyB;YAASlE;YAAMqE;QAAM,GACpFuC,SACA3D,QACAzB;QAEF,IAAIyB,WAAW,UAAU;gBAMc2D;YALrC,MAAMvJ,mBAAmB6H,SAASzF,MAAM,EAAE;gBACxCqH,MAAM;gBACNC,OAAO;gBACP/G,MACE,GAAGkF,SAASzC,KAAK,CAAC,eAAe,EAAEuE,SAAST,SAASnE,GAAG,EAAE,iBAAiB,CAAC,GAC5E,GAAG8C,SAAS3B,OAAO,CAAC,WAAW,GAAEqD,yBAAAA,QAAQrD,OAAO,CAACtB,KAAK,YAArB2E,yBAAyB,YAAY,GAAG,CAAC,GAC1E;gBACFK,MAAM,CAAC,CAAC,EAAE/B,SAASzF,MAAM,CAAC,kBAAkB,CAAC;YAC/C,GAAGsF,KAAK,CAAC,IAAMC;QACjB;QACA,OAAO/B;IACT;IAEF,MAAMU,YAAYwC,SAASV,MAAM,CAAC,CAACxC,SAAWA,WAAW,aAAa4C,MAAM;IAC5E,MAAMC,SAASK,SAASN,MAAM,GAAGlC;IACjC,IAAImC,SAAS,KAAK,CAACC,OAAO;QACxB,MAAM,IAAIjF,MAAM,GAAGgF,OAAO,IAAI,EAAEK,SAASN,MAAM,CAAC,sCAAsC,EAAEX,SAASzC,KAAK,EAAE;IAC1G;IACA,OAAO;QAAEkB;QAAWmC;IAAO;AAC7B;AAEA,qFAAqF,GACrF,eAAee,gBAAgBtF,KAW9B;IACC,MAAMuE,SAAS,CAAC7D,QAA2C,CAAA;YACzD0B,WAAW;YACXJ,SAAS;gBAAEC,MAAMjC,MAAMC,KAAK;gBAAIQ,YAAY;gBAAMC;gBAAOwB,YAAY;YAAE;QACzE,CAAA;IACA,IAAI,CAAClC,MAAMd,OAAO,EAAE,OAAOqF,OAAO;IAClC,IAAIhG;IACJ,IAAI;QACFA,SAAS,MAAMU,WAAWe,MAAMlB,SAAS,EAAEkB,MAAM9B,MAAM,EAAE8B,MAAM7B,UAAU,EAAE6B,MAAMd,OAAO;IAC1F,EAAE,OAAOwB,OAAO;;QACd,OAAO6D,OAAO,CAAC,sCAAsC,EAAErH,eAAQwD,yBAAD,AAACA,MAAiBoB,OAAO,mBAAIpB,OAAOqB,KAAK,CAAC,GAAG,MAAM;IACnH;IACA,OAAOhC,iBAAiB;QACtBc,KAAKb,MAAMa,GAAG;QACdtC;QACA2C,OAAOlB,MAAMkB,KAAK;QAClBE,SAASpB,MAAMoB,OAAO;QACtB3C,MAAMuB,MAAMvB,IAAI;QAChBmC,WAAWZ,MAAMY,SAAS;QAC1BjB,KAAKK,MAAMC,KAAK;IAClB;AACF;AAEA,SAASwF,SAAS5E,GAAW;IAC3B,IAAI;QACF,OAAO,IAAI8E,IAAI9E,KAAK+E,QAAQ;IAC9B,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAMC,UAAU;IAAC;IAAU;IAAU;IAAU;IAAe;IAAU;IAAQ;CAAS;AAGzF,MAAMC,OAAO,CAACC,QAAkBC,QAAQD,UAAUA,MAAMzB,MAAM,IAAI,QAAQ,CAAC,WAAWtB,IAAI,CAAC+C,UAAU,CAACA,MAAME,QAAQ,CAAC;AAErH,SAASC,SAAS1I,GAAY;IAC5B,MAAMqD,MAAM3D,OAAOM,cAAAA,MAAO,IAAIC,IAAI;IAClC,IAAI,CAACoD,KAAK,OAAO;QAAEH,OAAO;IAA+B;IACzD,IAAIG,IAAIyD,MAAM,GAAG,MAAM,OAAO;QAAE5D,OAAO;IAA2B;IAClE,MAAMe,UAAU9F,qBAAqBkF;IACrC,IAAIY,SAAS,OAAO;QAAEf,OAAO,CAAC,8BAA8B,EAAEhF,6BAA6B+F,UAAU;IAAC;IACtG,OAAO;QAAEZ;IAAI;AACf;AAEA,MAAMsF,mBAAmB,CAAC3I,MAAiBN,OAAOM,cAAAA,MAAO,IAAIC,IAAI,GAAGsE,KAAK,CAAC,GAAG;AAE7E;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMqE,uBAAyC,OAAOC,KAAKC;QAEnCD,4BASP5H,cACAA;IAXtB,IAAI4H,IAAIvF,MAAM,KAAK,QAAQ,OAAOwF,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;QAAE7F,OAAO;IAAqB;IACrF,MAAM8F,gBAAgBtJ,QAAOmJ,6BAAAA,IAAItF,OAAO,CAACyF,aAAa,YAAzBH,6BAA6B;IAC1D,MAAMI,UAAUD,cAAcE,UAAU,CAAC,aAAaF,cAAczE,KAAK,CAAC,UAAUuC,MAAM,IAAIb;IAC9F,IAAI,CAACgD,SAAS,OAAOH,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;QAAE7F,OAAO;IAAkB;IACrE,IAAIjC;IACJ,IAAI;YACM;QAARA,QAAQ,OAAA,OAAO4H,IAAI5H,IAAI,KAAK,WAAWkI,KAAKC,KAAK,CAACP,IAAI5H,IAAI,IAAI4H,IAAI5H,IAAI,YAA9D,OAAmE,CAAC;IAC9E,EAAE,eAAM;QACN,OAAO6H,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;YAAE7F,OAAO;QAAe;IACtD;IACA,MAAMxC,SAAShB,QAAOuB,eAAAA,KAAKP,MAAM,YAAXO,eAAe;IACrC,MAAMoI,SAAS3J,QAAOuB,eAAAA,KAAKoI,MAAM,YAAXpI,eAAe;IACrC,IAAI,CAACqH,KAAK5H,SAAS,OAAOoI,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;QAAE7F,OAAO;IAAiB;IACzE,IAAI,CAACmF,QAAQI,QAAQ,CAACY,SAAS,OAAOP,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;QAAE7F,OAAO;IAAiB;IACrF,IAAI;YAMGoG,mBAwFqBrI;QA7F1B,MAAMsI,UAAU,MAAMlL,cAAcgI,GAAG,GAAGmD,IAAI,GAAGC,aAAa,CAACR;QAC/D,MAAM3H,YAAYjD,cAAcgI,GAAG,GAAG/E,SAAS;QAC/C,MAAMM,UAAUP,WAAWC,WAAWZ;QACtC,MAAM4I,eAAe,MAAM1H,QAAQC,GAAG;QACtC,IAAI,CAACyH,aAAa1B,MAAM,EAAE,OAAOkB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;YAAE7F,OAAO;QAAe;QAC9E,IAAI,EAACoG,oBAAAA,aAAazH,GAAG,CAAC,0BAAjByH,oBAAmC,CAAC,EAAE,CAACC,QAAQG,GAAG,CAAC,KAAK,SAAS;YACpE,OAAOZ,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAAuC;QAC9E;QACA,MAAMyG,aAAa,MAAMnL,sBAAsB+K,QAAQG,GAAG,EAAE;YAAEhJ;QAAO;QACrE,IAAI,CAACiJ,WAAWC,OAAO,IAAID,WAAWE,QAAQ,KAAK,SAAS;YAC1D,OAAOf,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAA0D;QACjG;QACA,MAAMxB,UAAU5B;QAChB,IAAIuJ,WAAW,UAAU;YACvB,OAAOP,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAC1Be,YAAYtB,QAAQ9G;gBACpBqI,cAAc7K,SAAS8K,2BAA2B;gBAClDC,QAAQhL,4BAA4BsI,GAAG,CAAC,CAAC2C,QAAW,CAAA;wBAClDxG,OAAOwG,MAAMxG,KAAK,CAACpD,EAAE;wBACrB6J,OAAOD,MAAMC,KAAK;wBAClBC,aAAaF,MAAME,WAAW;oBAChC,CAAA;YACF;QACF;QACA,IAAI,CAAC1I,SAAS;YACZ,OAAOoH,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAAuD;QAC9F;QACA,MAAMf,MAAMO,KAAKP,GAAG;QACpB,MAAMmE,YAAY1E,QAAQL,UAAU,CAAC;QAErC,IAAI8H,WAAW,UAAU;YACvB,MAAMhG,MAAMqF,SAASzH,KAAKoC,GAAG;YAC7B,IAAI,WAAWA,KAAK,OAAOyF,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAOG,IAAIH,KAAK;YAAC;YACnE,MAAM+G,SAAS/K,SAASmL,2BAA2B,CAACpJ,KAAKgJ,MAAM;YAC/D,IAAIA,OAAOnD,MAAM,KAAK,GAAG,OAAOgC,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAA4B;YAC1F,MAAMnC,SAASH;YACf,MAAM0J,MAAMhE,UAAU9E,GAAG,CAACxD;YAC1B,MAAMuM,UAAU,MAAMjJ,UAAUkJ,cAAc,CAAC,OAAOC;gBACpD,MAAMC,WAAW,MAAMD,YAAY5I,GAAG,CAACyE,UAAUqE,KAAK,CAACzL,SAAS8K,2BAA2B;gBAC3F,IAAIU,SAASE,IAAI,IAAI1L,SAAS8K,2BAA2B,EAAE,OAAO;gBAClE,MAAMxC,WAA0C;oBAC9CnE,KAAKA,IAAIA,GAAG;oBACZ4G;oBACAY,SAAS;mBACLlC,iBAAiB1H,KAAKmJ,WAAW,IAAI;oBAAEA,aAAazB,iBAAiB1H,KAAKmJ,WAAW;gBAAE,IAAI,CAAC;oBAChGU,YAAY/J,OAAOwD,KAAK,CAAC,CAAC;oBAC1BsB,qBAAqB;oBACrBJ,aAAatD;oBACbG,aAAaH;oBACb4I,WAAWxB,QAAQG,GAAG;;gBAExBe,YAAYO,MAAM,CAACV,KAAK9C;gBACxBiD,YAAYO,MAAM,CAACpJ,QAAQL,UAAU,CAAC,uBAAuBC,GAAG,CAAC8I,IAAIhK,EAAE,GAAG4B,mBAAmBxB,QAAQ4J,IAAIhK,EAAE,EAAES,QAAQW,SAASS;gBAC9H,OAAO;YACT;YACA,IAAI,CAACoI,SAAS;gBACZ,OAAOzB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;oBAAE7F,OAAO,CAAC,iBAAiB,EAAEhE,SAAS8K,2BAA2B,CAAC,kBAAkB,CAAC;gBAAC;YACpH;YACA,OAAOlB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAEkC,IAAI;gBAAM3K,IAAIgK,IAAIhK,EAAE;gBAAES;YAAO;QAC7D;QAEA,IAAIsI,WAAW,UAAU;gBACGpI;YAA1B,MAAMwG,aAAa/H,QAAOuB,mBAAAA,KAAKwG,UAAU,YAAfxG,mBAAmB;YAC7C,IAAI,CAACqH,KAAKb,aAAa,OAAOqB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAAqB;YACjF,MAAMgI,cAActJ,QAAQL,UAAU,CAAC,0BAA0BC,GAAG,CAACiG;YACrE,MAAM0D,mBAAmB,MAAMD,YAAYrJ,GAAG;YAC9C,IAAI,CAACsJ,iBAAiBvD,MAAM,EAAE,OAAOkB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAAmB;YACtF,MAAM+B,WAAWkG,iBAAiBvE,IAAI;YACtC,MAAMwE,mBAAmB,MAAM9E,UAAU9E,GAAG,CAACyD,SAAStE,UAAU,EAAEkB,GAAG;YACrE,IAAI,CAACuJ,iBAAiBxD,MAAM,EAAE,OAAOkB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAE7F,OAAO;YAA4B;YAC/F,IAAI+B,SAAShE,IAAI,CAAC6F,MAAM,IAAI5H,SAASqG,sBAAsB,EAAE;gBAC3D,OAAOuD,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;oBAAE7F,OAAO;gBAA8D;YACrG;YACA,MAAMsE,WAAW4D,iBAAiBxE,IAAI;YACtC,MAAM7F,SAAS,MAAMU,WAAWH,WAAWZ,QAAQuE,SAAStE,UAAU,EAAEe;YACxE,MAAMyB,SAAS,MAAMZ,iBAAiB;gBAAEc,KAAKmE,SAASnE,GAAG;gBAAEtC;gBAAQ2C,OAAOuB,SAASvB,KAAK;gBAAEE,SAASqB,SAASrB,OAAO;gBAAE3C,MAAMgE,SAAShE,IAAI;YAAC;YACzI,MAAMiD,SAA8Cf,OAAOyB,SAAS,GAChE,cACAK,SAASf,MAAM,KAAK,cAClB,cACAe,SAASf,MAAM;YACrB,MAAMc,cACJ1D,WACAZ,QACAuE,SAAStE,UAAU,EACnB;gBAAEL,IAAImH;gBAAY7D,SAASqB,SAASrB,OAAO;gBAAEF,OAAOuB,SAASvB,KAAK;gBAAEyB,SAASF,SAASE,OAAO;gBAAElE,MAAMgE,SAAShE,IAAI;gBAAEuE,MAAMP,SAASO,IAAI;gBAAEF,OAAOL;YAAS,GACzJ9B,QACAe,QACAxB,KAAKP,GAAG;YAEV,OAAO2G,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAEkC,IAAI;gBAAMrG,WAAWzB,OAAOyB,SAAS;gBAAEJ,SAASrB,OAAOqB,OAAO;YAAC;QAC/F;QAEA,MAAM7D,aAAajB,QAAOuB,mBAAAA,KAAKN,UAAU,YAAfM,mBAAmB;QAC7C,IAAI,CAACqH,KAAK3H,aAAa,OAAOmI,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;YAAE7F,OAAO;QAAqB;QACjF,MAAMmI,cAAc/E,UAAU9E,GAAG,CAACb;QAClC,MAAMyK,mBAAmB,MAAMC,YAAYxJ,GAAG;QAC9C,IAAI,CAACuJ,iBAAiBxD,MAAM,EAAE,OAAOkB,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;YAAE7F,OAAO;QAAmB;QACtF,MAAMsE,WAAW4D,iBAAiBxE,IAAI;QAEtC,IAAIyC,WAAW,UAAU;YACvB,MAAMiC,QAAgD;gBAAEhJ,aAAaH;YAAI;YACzE,IAAIlB,KAAKoC,GAAG,KAAK4C,WAAW;gBAC1B,MAAM5C,MAAMqF,SAASzH,KAAKoC,GAAG;gBAC7B,IAAI,WAAWA,KAAK,OAAOyF,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;oBAAE7F,OAAOG,IAAIH,KAAK;gBAAC;gBACnEoI,MAAMjI,GAAG,GAAGA,IAAIA,GAAG;YACrB;YACA,IAAIpC,KAAKgJ,MAAM,KAAKhE,WAAW;gBAC7B,MAAMgE,SAAS/K,SAASmL,2BAA2B,CAACpJ,KAAKgJ,MAAM;gBAC/D,IAAIA,OAAOnD,MAAM,KAAK,GAAG,OAAOgC,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;oBAAE7F,OAAO;gBAA4B;gBAC1FoI,MAAMrB,MAAM,GAAGA;YACjB;YACA,IAAIhJ,KAAK4J,OAAO,KAAK5E,WAAW;gBAC9BqF,MAAMT,OAAO,GAAGrC,QAAQvH,KAAK4J,OAAO;gBACpC,IAAIS,MAAMT,OAAO,EAAES,MAAMzF,mBAAmB,GAAG;YACjD;YACA,IAAI5E,KAAKmJ,WAAW,KAAKnE,WAAWqF,MAAMlB,WAAW,GAAGzB,iBAAiB1H,KAAKmJ,WAAW;YACzF,MAAMiB,YAAYnK,MAAM,CAACoK;YACzB,OAAOxC,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAEkC,IAAI;YAAK;QACzC;QAEA,IAAI5B,WAAW,eAAe;YAC5B,MAAMtI,SAASH;YACf,MAAM2K,QAAQjK,UAAUiK,KAAK;YAC7BA,MAAM7F,GAAG,CAAC9D,QAAQL,UAAU,CAAC,uBAAuBC,GAAG,CAACb,aAAauB,mBAAmBxB,QAAQC,YAAYI,QAAQW,SAASS;YAC7HoJ,MAAMrK,MAAM,CAACmK,aAAa;gBAAEP,YAAY/J,OAAOwD,KAAK,CAAC,CAAC;gBAAIjC,aAAaH;YAAI;YAC3E,MAAMoJ,MAAMC,MAAM;YAClB,OAAO1C,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAEkC,IAAI;gBAAMlK;YAAO;QACjD;QAEA,IAAIsI,WAAW,UAAU;YACvB,MAAMkC,QAAQjK,UAAUiK,KAAK;YAC7BA,MAAME,MAAM,CAACJ;YACbE,MAAME,MAAM,CAAC7J,QAAQL,UAAU,CAAC,uBAAuBC,GAAG,CAACb;YAC3D,MAAM4K,MAAMC,MAAM;YAClB,OAAO1C,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;gBAAEkC,IAAI;YAAK;QACzC;QAEA,OAAO;QACP,MAAMrH,UAAU,CAAC,KAAK,EAAE5E,YAAY,GAAG6B,QAAQ,CAAC,cAAc;QAC9D,MAAM6K,WAAWxM,SAAS+H,gBAAgB,CAAC;YACzCrD;YACAF,OAAOxE,SAASyM,wBAAwB;YACxC9E,cAAc1E;YACdzB;YACAwG,SAAS;gBAAE5C,SAAS,CAAC,kBAAkB,EAAErG,oBAAoB,6BAA6B,CAAC;YAAC;QAC9F;QACA,MAAM8C,SAAS,MAAMU,WAAWH,WAAWZ,QAAQC,YAAYe;QAC/D,MAAMyB,SAAS,MAAMZ,iBAAiB;YAAEc,KAAKmE,SAASnE,GAAG;YAAEtC;YAAQ2C,OAAOxE,SAASyM,wBAAwB;YAAE/H;YAAS3C,MAAMyK;QAAS;QACrI,MAAM1G,cACJ1D,WACAZ,QACAC,YACA;YAAEL,IAAIpB,SAASwI,sBAAsB,CAAC9D,SAASjD;YAAaiD;YAASF,OAAOxE,SAASyM,wBAAwB;YAAExG,SAAS;YAAMlE,MAAMyK;YAAUlG,MAAM;YAAMF,OAAO;QAAK,GACtKnC,QACAA,OAAOyB,SAAS,GAAG,cAAc,UACjClC,KAAKP,GAAG;QAEV,OAAO2G,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;YAAEkC,IAAI;YAAMrG,WAAWzB,OAAOyB,SAAS;YAAEJ,SAASrB,OAAOqB,OAAO;QAAC;IAC/F,EAAE,OAAOtB,OAAO;QACd0I,QAAQ1I,KAAK,CAAC,gCAAgCmG,QAAQnG;QACtD,OAAO4F,IAAI5E,MAAM,CAAC,KAAK6E,IAAI,CAAC;YAAE7F,OAAO;QAAsD;IAC7F;AACF,EAAC"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-webhooks.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport type { PluginDomainEventEnvelope } from '@aglyn/aglyn/plugin-manager/plugin-domain-events'\nimport {\n describeConfiguredUrlRefusal,\n configuredUrlRefusal,\n fetchConfiguredPublicUrl,\n firebaseAdmin,\n notifyHostManagers,\n} from '@aglyn/tenant-data-admin'\nimport { PLUGIN_EVENT_MAX_ATTEMPTS } from '@aglyn/tenant-data-admin/server/plugin-event-outbox'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n createSecretBoxKey,\n openSecret,\n parseSecretBoxKeyring,\n sealSecret,\n SECRET_BOX_KEY_BYTES,\n type SecretBoxKey,\n type SecretBoxKeyring,\n} from '@aglyn/shared-util-tools/secret-box'\nimport { createHmac, hkdfSync, randomBytes } from 'node:crypto'\nimport { COMMERCE_EVENT_DECLARATIONS } from '../model/order-events'\nimport * as Webhooks from '../model/order-webhooks'\n\n/**\n * Merchant order webhooks (AGL-3611): the console's door and the delivery\n * behind it. The model (`model/order-webhooks.ts`) says what is stored where.\n *\n * DELIVERY RIDES THE PLUGIN EVENT OUTBOX. Commerce subscribes to its own\n * order and return events under the name `webhooks`; the subscriber posts the\n * event to every endpoint that takes it and records each attempt in the\n * delivery log. An endpoint that already took an event is not posted it\n * again, so when one of three endpoints fails, the subscriber throws, the\n * outbox retries it with its backoff (30s, 2m, 10m, 30m, 1h, 6h, 12h), and\n * only the failing endpoint is called again. On the last attempt the delivery\n * is marked failed and the site's managers are told.\n *\n * EVERY CALL GOES THROUGH `fetchConfiguredPublicUrl`: https on the default\n * port, a public address pinned at connect time, no redirect followed. A\n * merchant's endpoint is a stranger's server to us.\n *\n * THE SECRET IS SEALED with the shared secret box, under the keyring\n * `readCommerceSecretKeyring` answers, bound to its endpoint by the seal's\n * context. The plaintext leaves the server once, in the answer to the request\n * that made or rolled it.\n */\n\nexport const COMMERCE_SECRET_KEY_ENV = 'COMMERCE_SECRET_KEY'\n\nconst SECRETS = 'orderWebhookSecrets'\n\n/** Delivery rows live this long, then Firestore's TTL removes them. */\nexport const ORDER_WEBHOOK_LOG_RETENTION_MS = 30 * 86_400_000\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The id of the key derived from `TOKEN_SIGNING_SECRET`. */\nexport const DERIVED_COMMERCE_KEY_ID = 'tss1'\n\n/**\n * A secret-box key derived from `TOKEN_SIGNING_SECRET` with HKDF, under its\n * own label, so the commerce tokens and the sealed secrets never share key\n * material. `null` when that secret is unset.\n */\nfunction derivedCommerceKey(): SecretBoxKey | null {\n const base = String(process.env['TOKEN_SIGNING_SECRET'] ?? '')\n if (!base) return null\n const material = new Uint8Array(hkdfSync('sha256', base, 'aglyn-commerce', 'secret-box:v1', SECRET_BOX_KEY_BYTES))\n return createSecretBoxKey(material, DERIVED_COMMERCE_KEY_ID)\n}\n\n/**\n * The keyring commerce seals its stored secrets with. `COMMERCE_SECRET_KEY`\n * (a secret-box keyring) when it is set, its first key sealing; otherwise the\n * key derived from `TOKEN_SIGNING_SECRET`, which every deployment that sells\n * already has. The derived key stays in the ring after a dedicated key is\n * configured, so what it sealed still opens. `null` when neither is usable.\n * Never throws.\n */\nexport function readCommerceSecretKeyring(): SecretBoxKeyring | null {\n let derived: SecretBoxKey | null = null\n try {\n derived = derivedCommerceKey()\n } catch {\n derived = null\n }\n const raw = String(process.env[COMMERCE_SECRET_KEY_ENV] ?? '').trim()\n if (raw) {\n try {\n const ring = parseSecretBoxKeyring(raw)\n const keys = derived && !ring.keys.some((key) => key.id === derived!.id) ? [...ring.keys, derived] : ring.keys\n return { current: ring.current, keys }\n } catch {\n // An unusable dedicated key falls back to the derived one rather than\n // sealing nothing.\n }\n }\n return derived ? { current: derived, keys: [derived] } : null\n}\n\n/** Whether this deployment can sign webhooks at all. */\nexport function orderWebhooksConfigured(): boolean {\n return readCommerceSecretKeyring() !== null\n}\n\nconst sealContext = (hostId: string, endpointId: string) => `hosts/${hostId}/${SECRETS}/${endpointId}#secret`\n\n/** A fresh signing secret: `whsec_` and 32 random bytes. */\nexport function mintOrderWebhookSecret(): string {\n return `whsec_${randomBytes(32).toString('base64url')}`\n}\n\n/** The HMAC-SHA256 of `{t}.{body}` under the secret, as hex. */\nexport function signOrderWebhook(secret: string, timestampSeconds: number, body: string): string {\n return createHmac('sha256', secret).update(Webhooks.orderWebhookSignedPayload(timestampSeconds, body)).digest('hex')\n}\n\nconst hostRefFor = (firestore: Firestore, hostId: string) => firestore.collection('hosts').doc(hostId)\n\nasync function readSecret(\n firestore: Firestore,\n hostId: string,\n endpointId: string,\n keyring: SecretBoxKeyring,\n): Promise<string> {\n const hostRef = hostRefFor(firestore, hostId)\n const snapshot = await hostRef.collection('orderWebhookSecrets').doc(endpointId).get()\n const sealed = String(snapshot.get('sealedSecret') ?? '')\n if (!sealed) throw new Error('This endpoint has no signing secret')\n return openSecret(sealed, keyring, { context: sealContext(hostId, endpointId) }).plaintext\n}\n\nfunction sealedSecretRecord(\n hostId: string,\n endpointId: string,\n secret: string,\n keyring: SecretBoxKeyring,\n now: number,\n): Webhooks.OrderWebhookSecretRecord {\n return {\n sealedSecret: sealSecret(secret, keyring.current, { context: sealContext(hostId, endpointId) }),\n secretKeyId: keyring.current.id,\n updatedAtMs: now,\n }\n}\n\n/** What one POST came to. */\nexport interface OrderWebhookPostResult {\n attempt: Webhooks.OrderWebhookAttempt\n delivered: boolean\n}\n\n/**\n * Posts one body to one endpoint, signed. Never throws: a refusal, a timeout\n * or a reset is an attempt that failed, with the reason in words.\n */\nexport async function postOrderWebhook(input: {\n url: string\n secret: string\n event: string\n eventId: string\n body: string\n now?: () => number\n fetchImpl?: typeof fetchConfiguredPublicUrl\n}): Promise<OrderWebhookPostResult> {\n const clock = input.now ?? Date.now\n const startedAt = clock()\n const timestamp = Math.floor(startedAt / 1000)\n const signature = Webhooks.formatOrderWebhookSignature(timestamp, signOrderWebhook(input.secret, timestamp, input.body))\n let httpStatus: number | null = null\n let error: string | undefined\n try {\n const result = await (input.fetchImpl ?? fetchConfiguredPublicUrl)(input.url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n 'user-agent': 'Aglyn-Webhooks/1.0',\n [Webhooks.ORDER_WEBHOOK_SIGNATURE_HEADER]: signature,\n [Webhooks.ORDER_WEBHOOK_EVENT_HEADER]: input.event,\n [Webhooks.ORDER_WEBHOOK_EVENT_ID_HEADER]: input.eventId,\n },\n body: input.body,\n signal: AbortSignal.timeout(Webhooks.ORDER_WEBHOOK_TIMEOUT_MS),\n })\n if ('refusal' in result) {\n error = `Not sent: ${describeConfiguredUrlRefusal(result.refusal)}`\n } else {\n httpStatus = result.status\n if (!Webhooks.orderWebhookAccepted(httpStatus)) {\n error =\n httpStatus >= 300 && httpStatus < 400\n ? `The endpoint answered ${httpStatus}, a redirect; redirects are not followed`\n : `The endpoint answered ${httpStatus}`\n }\n }\n } catch (caught) {\n const name = (caught as Error)?.name\n error =\n name === 'TimeoutError' || name === 'AbortError'\n ? `No answer within ${Webhooks.ORDER_WEBHOOK_TIMEOUT_MS / 1000} seconds`\n : `The connection failed: ${String((caught as Error)?.message ?? caught).slice(0, 200)}`\n }\n const attempt: Webhooks.OrderWebhookAttempt = {\n atMs: startedAt,\n httpStatus,\n ...(error ? { error } : {}),\n durationMs: Math.max(0, clock() - startedAt),\n }\n return { attempt, delivered: !error }\n}\n\nfunction expiresAt(now: number) {\n return firebaseAdmin.firestore.Timestamp.fromMillis(now + ORDER_WEBHOOK_LOG_RETENTION_MS)\n}\n\n/** Records an attempt on the delivery row and on the endpoint's summary. */\nasync function recordAttempt(\n firestore: Firestore,\n hostId: string,\n endpointId: string,\n delivery: {\n id: string\n eventId: string\n event: string\n orderId: string | null\n body: string\n test?: boolean\n prior: Webhooks.OrderWebhookDelivery | null\n },\n result: OrderWebhookPostResult,\n status: Webhooks.OrderWebhookDeliveryStatus,\n now: number,\n): Promise<void> {\n const hostRef = hostRefFor(firestore, hostId)\n const row: Webhooks.OrderWebhookDelivery & { expiresAt: unknown } = {\n endpointId,\n eventId: delivery.eventId,\n event: delivery.event,\n orderId: delivery.orderId,\n status,\n attempts: Webhooks.appendOrderWebhookAttempt(delivery.prior?.attempts, result.attempt),\n body: delivery.body.slice(0, Webhooks.ORDER_WEBHOOK_BODY_MAX),\n ...(delivery.test ? { test: true } : {}),\n createdAtMs: delivery.prior?.createdAtMs ?? now,\n updatedAtMs: now,\n expiresAt: expiresAt(now),\n }\n await hostRef.collection('orderWebhookDeliveries').doc(delivery.id).set(row)\n await hostRef\n .collection('orderWebhooks')\n .doc(endpointId)\n .update({\n lastDeliveryAtMs: now,\n lastDeliveryStatus: status,\n consecutiveFailures: result.delivered ? 0 : firebaseAdmin.firestore.FieldValue.increment(1),\n })\n .catch(() => undefined)\n}\n\n/**\n * The subscriber: posts one event to every endpoint of the site that takes\n * it and has not taken it already. Throws when any endpoint still owes it, so\n * the outbox retries; on the outbox's last attempt it records the failure and\n * tells the site's managers instead.\n */\nexport async function deliverOrderEventToWebhooks(\n envelope: PluginDomainEventEnvelope,\n deps: { fetchImpl?: typeof fetchConfiguredPublicUrl; now?: () => number } = {},\n): Promise<{ delivered: number; failed: number }> {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = hostRefFor(firestore, envelope.hostId)\n const endpoints = await hostRef.collection('orderWebhooks').where('enabled', '==', true).get()\n const taking = endpoints.docs.filter((doc) =>\n Webhooks.orderWebhookTakes(doc.data() as Webhooks.OrderWebhookEndpoint, envelope.event, envelope.occurredAtMs),\n )\n if (taking.length === 0) return { delivered: 0, failed: 0 }\n const keyring = readCommerceSecretKeyring()\n const clock = deps.now ?? Date.now\n const final = envelope.attempt >= PLUGIN_EVENT_MAX_ATTEMPTS\n const body = Webhooks.orderWebhookBody({\n eventId: envelope.id,\n event: envelope.event,\n occurredAtMs: envelope.occurredAtMs,\n hostId: envelope.hostId,\n payload: envelope.payload,\n })\n const orderId = String(((envelope.payload ?? {}) as { order?: { id?: unknown } }).order?.id ?? '') || null\n const outcomes = await Promise.all(\n taking.map(async (doc) => {\n const endpoint = doc.data() as Webhooks.OrderWebhookEndpoint\n const deliveryId = Webhooks.orderWebhookDeliveryId(envelope.id, doc.id)\n const priorSnapshot = await hostRef.collection('orderWebhookDeliveries').doc(deliveryId).get()\n const prior = priorSnapshot.exists ? (priorSnapshot.data() as Webhooks.OrderWebhookDelivery) : null\n // Taken already, on an earlier pass or by a resend from the console.\n if (prior?.status === 'delivered') return 'delivered' as const\n const settled = await attemptEndpoint({\n firestore,\n hostId: envelope.hostId,\n endpointId: doc.id,\n url: endpoint.url,\n keyring,\n event: envelope.event,\n eventId: envelope.id,\n body,\n clock,\n fetchImpl: deps.fetchImpl,\n })\n const status: Webhooks.OrderWebhookDeliveryStatus = settled.delivered ? 'delivered' : final ? 'failed' : 'retrying'\n await recordAttempt(\n firestore,\n envelope.hostId,\n doc.id,\n { id: deliveryId, eventId: envelope.id, event: envelope.event, orderId, body, prior },\n settled,\n status,\n clock(),\n )\n if (status === 'failed') {\n await notifyHostManagers(envelope.hostId, {\n type: 'content.order',\n title: 'An order webhook could not be delivered',\n body:\n `${envelope.event} never reached ${safeHost(endpoint.url)} on {site} after ` +\n `${envelope.attempt} attempts (${settled.attempt.error ?? 'no answer'}). ` +\n 'Resend it from the delivery log once the endpoint is fixed.',\n link: `/${envelope.hostId}/products/notifications`,\n }).catch(() => undefined)\n }\n return status\n }),\n )\n const delivered = outcomes.filter((status) => status === 'delivered').length\n const failed = outcomes.length - delivered\n if (failed > 0 && !final) {\n throw new Error(`${failed} of ${outcomes.length} order webhook endpoints did not take ${envelope.event}`)\n }\n return { delivered, failed }\n}\n\n/** One signed attempt at one endpoint; a missing key or secret is a failed attempt. */\nasync function attemptEndpoint(input: {\n firestore: Firestore\n hostId: string\n endpointId: string\n url: string\n keyring: SecretBoxKeyring | null\n event: string\n eventId: string\n body: string\n clock: () => number\n fetchImpl?: typeof fetchConfiguredPublicUrl\n}): Promise<OrderWebhookPostResult> {\n const failed = (error: string): OrderWebhookPostResult => ({\n delivered: false,\n attempt: { atMs: input.clock(), httpStatus: null, error, durationMs: 0 },\n })\n if (!input.keyring) return failed('Webhook signing is not configured')\n let secret: string\n try {\n secret = await readSecret(input.firestore, input.hostId, input.endpointId, input.keyring)\n } catch (error) {\n return failed(`The signing secret could not be read: ${String((error as Error)?.message ?? error).slice(0, 120)}`)\n }\n return postOrderWebhook({\n url: input.url,\n secret,\n event: input.event,\n eventId: input.eventId,\n body: input.body,\n fetchImpl: input.fetchImpl,\n now: input.clock,\n })\n}\n\nfunction safeHost(url: string): string {\n try {\n return new URL(url).hostname\n } catch {\n return 'the endpoint'\n }\n}\n\n// ---------------------------------------------------------------------------\n// The console's door\n// ---------------------------------------------------------------------------\n\nconst ACTIONS = ['status', 'create', 'update', 'roll-secret', 'delete', 'test', 'resend'] as const\ntype Action = (typeof ACTIONS)[number]\n\nconst isId = (value: string) => Boolean(value) && value.length <= 1500 && !/^__.*__$/.test(value) && !value.includes('/')\n\nfunction cleanUrl(raw: unknown): { url: string } | { error: string } {\n const url = String(raw ?? '').trim()\n if (!url) return { error: 'Enter the endpoint’s address' }\n if (url.length > 2048) return { error: 'That address is too long' }\n const refusal = configuredUrlRefusal(url)\n if (refusal) return { error: `This endpoint cannot be used: ${describeConfiguredUrlRefusal(refusal)}` }\n return { url }\n}\n\nconst cleanDescription = (raw: unknown) => String(raw ?? '').trim().slice(0, 120)\n\n/**\n * `POST /api/commerce/order-webhooks` with `{ hostId, action, … }`. Endpoints\n * post the order book to an outside server, so only an admin of the whole\n * workspace manages them — the bar a refund sets.\n *\n * - `status` → `{ configured, events: [{ event, label, description }] }`\n * - `create` `{ url, events, description? }` → `{ id, secret }` (shown once)\n * - `update` `{ endpointId, url?, events?, enabled?, description? }`\n * - `roll-secret` `{ endpointId }` → `{ secret }`\n * - `delete` `{ endpointId }`; the delivery log stays until it expires\n * - `test` `{ endpointId }` → posts a `webhook.test` event now\n * - `resend` `{ deliveryId }` → posts a logged delivery again now\n */\nexport const orderWebhooksHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let body: Record<string, any>\n try {\n body = (typeof req.body === 'string' ? JSON.parse(req.body) : req.body) ?? {}\n } catch {\n return res.status(400).json({ error: 'Invalid JSON' })\n }\n const hostId = String(body.hostId ?? '')\n const action = String(body.action ?? '') as Action\n if (!isId(hostId)) return res.status(400).json({ error: 'Missing hostId' })\n if (!ACTIONS.includes(action)) return res.status(400).json({ error: 'Unknown action' })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = hostRefFor(firestore, hostId)\n const hostSnapshot = await hostRef.get()\n if (!hostSnapshot.exists) return res.status(404).json({ error: 'Unknown site' })\n if ((hostSnapshot.get('memberRoles') ?? {})[decoded.uid] !== 'admin') {\n return res.status(403).json({ error: 'Webhooks are managed by a site admin' })\n }\n const membership = await resolveOrgPermissions(decoded.uid, { hostId })\n if (!membership.orgWide || membership.hostRole !== 'admin') {\n return res.status(403).json({ error: 'Webhooks are managed by an admin of the whole workspace' })\n }\n const keyring = readCommerceSecretKeyring()\n if (action === 'status') {\n return res.status(200).json({\n configured: Boolean(keyring),\n maxEndpoints: Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS,\n events: COMMERCE_EVENT_DECLARATIONS.map((entry) => ({\n event: entry.event.id,\n label: entry.label,\n description: entry.description,\n })),\n })\n }\n if (!keyring) {\n return res.status(503).json({ error: 'Order webhooks are not configured on this deployment' })\n }\n const now = Date.now()\n const endpoints = hostRef.collection('orderWebhooks')\n\n if (action === 'create') {\n const url = cleanUrl(body.url)\n if ('error' in url) return res.status(400).json({ error: url.error })\n const events = Webhooks.normalizeOrderWebhookEvents(body.events)\n if (events.length === 0) return res.status(400).json({ error: 'Choose at least one event' })\n const secret = mintOrderWebhookSecret()\n const ref = endpoints.doc(createResourceUid())\n const created = await firestore.runTransaction(async (transaction) => {\n const existing = await transaction.get(endpoints.limit(Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS))\n if (existing.size >= Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS) return false\n const endpoint: Webhooks.OrderWebhookEndpoint = {\n url: url.url,\n events,\n enabled: true,\n ...(cleanDescription(body.description) ? { description: cleanDescription(body.description) } : {}),\n secretHint: secret.slice(-4),\n consecutiveFailures: 0,\n createdAtMs: now,\n updatedAtMs: now,\n createdBy: decoded.uid,\n }\n transaction.create(ref, endpoint)\n transaction.create(hostRef.collection('orderWebhookSecrets').doc(ref.id), sealedSecretRecord(hostId, ref.id, secret, keyring, now))\n return true\n })\n if (!created) {\n return res.status(409).json({ error: `A store can keep ${Webhooks.ORDER_WEBHOOK_MAX_ENDPOINTS} webhook endpoints` })\n }\n return res.status(200).json({ ok: true, id: ref.id, secret })\n }\n\n if (action === 'resend') {\n const deliveryId = String(body.deliveryId ?? '')\n if (!isId(deliveryId)) return res.status(400).json({ error: 'Missing deliveryId' })\n const deliveryRef = hostRef.collection('orderWebhookDeliveries').doc(deliveryId)\n const deliverySnapshot = await deliveryRef.get()\n if (!deliverySnapshot.exists) return res.status(404).json({ error: 'Unknown delivery' })\n const delivery = deliverySnapshot.data() as Webhooks.OrderWebhookDelivery\n const endpointSnapshot = await endpoints.doc(delivery.endpointId).get()\n if (!endpointSnapshot.exists) return res.status(404).json({ error: 'That endpoint was deleted' })\n if (delivery.body.length >= Webhooks.ORDER_WEBHOOK_BODY_MAX) {\n return res.status(409).json({ error: 'This delivery was too large to keep, so it cannot be resent' })\n }\n const endpoint = endpointSnapshot.data() as Webhooks.OrderWebhookEndpoint\n const secret = await readSecret(firestore, hostId, delivery.endpointId, keyring)\n const result = await postOrderWebhook({ url: endpoint.url, secret, event: delivery.event, eventId: delivery.eventId, body: delivery.body })\n const status: Webhooks.OrderWebhookDeliveryStatus = result.delivered\n ? 'delivered'\n : delivery.status === 'delivered'\n ? 'delivered'\n : delivery.status\n await recordAttempt(\n firestore,\n hostId,\n delivery.endpointId,\n { id: deliveryId, eventId: delivery.eventId, event: delivery.event, orderId: delivery.orderId, body: delivery.body, test: delivery.test, prior: delivery },\n result,\n status,\n Date.now(),\n )\n return res.status(200).json({ ok: true, delivered: result.delivered, attempt: result.attempt })\n }\n\n const endpointId = String(body.endpointId ?? '')\n if (!isId(endpointId)) return res.status(400).json({ error: 'Missing endpointId' })\n const endpointRef = endpoints.doc(endpointId)\n const endpointSnapshot = await endpointRef.get()\n if (!endpointSnapshot.exists) return res.status(404).json({ error: 'Unknown endpoint' })\n const endpoint = endpointSnapshot.data() as Webhooks.OrderWebhookEndpoint\n\n if (action === 'update') {\n const patch: Partial<Webhooks.OrderWebhookEndpoint> = { updatedAtMs: now }\n if (body.url !== undefined) {\n const url = cleanUrl(body.url)\n if ('error' in url) return res.status(400).json({ error: url.error })\n patch.url = url.url\n }\n if (body.events !== undefined) {\n const events = Webhooks.normalizeOrderWebhookEvents(body.events)\n if (events.length === 0) return res.status(400).json({ error: 'Choose at least one event' })\n patch.events = events\n }\n if (body.enabled !== undefined) {\n patch.enabled = Boolean(body.enabled)\n if (patch.enabled) patch.consecutiveFailures = 0\n }\n if (body.description !== undefined) patch.description = cleanDescription(body.description)\n await endpointRef.update(patch)\n return res.status(200).json({ ok: true })\n }\n\n if (action === 'roll-secret') {\n const secret = mintOrderWebhookSecret()\n const batch = firestore.batch()\n batch.set(hostRef.collection('orderWebhookSecrets').doc(endpointId), sealedSecretRecord(hostId, endpointId, secret, keyring, now))\n batch.update(endpointRef, { secretHint: secret.slice(-4), updatedAtMs: now })\n await batch.commit()\n return res.status(200).json({ ok: true, secret })\n }\n\n if (action === 'delete') {\n const batch = firestore.batch()\n batch.delete(endpointRef)\n batch.delete(hostRef.collection('orderWebhookSecrets').doc(endpointId))\n await batch.commit()\n return res.status(200).json({ ok: true })\n }\n\n // test\n const eventId = `test_${randomBytes(9).toString('base64url')}`\n const testBody = Webhooks.orderWebhookBody({\n eventId,\n event: Webhooks.ORDER_WEBHOOK_TEST_EVENT,\n occurredAtMs: now,\n hostId,\n payload: { message: `A test event from ${PLATFORM_BRAND_NAME}. Answer with any 2xx status.` },\n })\n const secret = await readSecret(firestore, hostId, endpointId, keyring)\n const result = await postOrderWebhook({ url: endpoint.url, secret, event: Webhooks.ORDER_WEBHOOK_TEST_EVENT, eventId, body: testBody })\n await recordAttempt(\n firestore,\n hostId,\n endpointId,\n { id: Webhooks.orderWebhookDeliveryId(eventId, endpointId), eventId, event: Webhooks.ORDER_WEBHOOK_TEST_EVENT, orderId: null, body: testBody, test: true, prior: null },\n result,\n result.delivered ? 'delivered' : 'failed',\n Date.now(),\n )\n return res.status(200).json({ ok: true, delivered: result.delivered, attempt: result.attempt })\n } catch (error) {\n console.error('order webhooks action failed', action, error)\n return res.status(500).json({ error: 'The webhook could not be updated. Please try again.' })\n }\n}\n"],"names":["createResourceUid","PLATFORM_BRAND_NAME","describeConfiguredUrlRefusal","configuredUrlRefusal","fetchConfiguredPublicUrl","firebaseAdmin","notifyHostManagers","PLUGIN_EVENT_MAX_ATTEMPTS","resolveOrgPermissions","createSecretBoxKey","openSecret","parseSecretBoxKeyring","sealSecret","SECRET_BOX_KEY_BYTES","createHmac","hkdfSync","randomBytes","COMMERCE_EVENT_DECLARATIONS","Webhooks","COMMERCE_SECRET_KEY_ENV","SECRETS","ORDER_WEBHOOK_LOG_RETENTION_MS","DERIVED_COMMERCE_KEY_ID","derivedCommerceKey","process","base","String","env","material","Uint8Array","readCommerceSecretKeyring","derived","raw","trim","ring","keys","some","key","id","current","orderWebhooksConfigured","sealContext","hostId","endpointId","mintOrderWebhookSecret","toString","signOrderWebhook","secret","timestampSeconds","body","update","orderWebhookSignedPayload","digest","hostRefFor","firestore","collection","doc","readSecret","keyring","snapshot","hostRef","get","sealed","Error","context","plaintext","sealedSecretRecord","now","sealedSecret","secretKeyId","updatedAtMs","postOrderWebhook","input","clock","Date","startedAt","timestamp","Math","floor","signature","formatOrderWebhookSignature","httpStatus","error","result","fetchImpl","url","method","headers","ORDER_WEBHOOK_SIGNATURE_HEADER","ORDER_WEBHOOK_EVENT_HEADER","event","ORDER_WEBHOOK_EVENT_ID_HEADER","eventId","signal","AbortSignal","timeout","ORDER_WEBHOOK_TIMEOUT_MS","refusal","status","orderWebhookAccepted","caught","name","message","slice","attempt","atMs","durationMs","max","delivered","expiresAt","Timestamp","fromMillis","recordAttempt","delivery","row","orderId","attempts","appendOrderWebhookAttempt","prior","ORDER_WEBHOOK_BODY_MAX","test","createdAtMs","set","lastDeliveryAtMs","lastDeliveryStatus","consecutiveFailures","FieldValue","increment","catch","undefined","deliverOrderEventToWebhooks","envelope","deps","app","endpoints","where","taking","docs","filter","orderWebhookTakes","data","occurredAtMs","length","failed","final","orderWebhookBody","payload","order","outcomes","Promise","all","map","endpoint","deliveryId","orderWebhookDeliveryId","priorSnapshot","exists","settled","attemptEndpoint","type","title","safeHost","link","URL","hostname","ACTIONS","isId","value","Boolean","includes","cleanUrl","cleanDescription","orderWebhooksHandler","req","res","json","authorization","idToken","startsWith","JSON","parse","action","hostSnapshot","decoded","auth","verifyIdToken","uid","membership","orgWide","hostRole","configured","maxEndpoints","ORDER_WEBHOOK_MAX_ENDPOINTS","events","entry","label","description","normalizeOrderWebhookEvents","ref","created","runTransaction","transaction","existing","limit","size","enabled","secretHint","createdBy","create","ok","deliveryRef","deliverySnapshot","endpointSnapshot","endpointRef","patch","batch","commit","delete","testBody","ORDER_WEBHOOK_TEST_EVENT","console"],"mappings":"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"}
|
|
@@ -478,7 +478,7 @@ function connectionRef(hostId) {
|
|
|
478
478
|
type: 'content.order',
|
|
479
479
|
title: 'An order could not be sent to ShippingEasy',
|
|
480
480
|
body: `An order on {site} never reached ShippingEasy after ${envelope.attempt} attempts (${error.message}).`,
|
|
481
|
-
link: `/${envelope.hostId}/products/
|
|
481
|
+
link: `/${envelope.hostId}/products/shipping`
|
|
482
482
|
}).catch(()=>undefined);
|
|
483
483
|
return {
|
|
484
484
|
result: 'failed',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/shippingeasy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport type { PluginDomainEventEnvelope } from '@aglyn/aglyn/plugin-manager/plugin-domain-events'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\nimport { needsReseal, openSecret, sealSecret, type SecretBoxKeyring } from '@aglyn/shared-util-tools/secret-box'\nimport { consumeRateLimit, firebaseAdmin, notifyHostManagers } from '@aglyn/tenant-data-admin'\nimport { PLUGIN_EVENT_MAX_ATTEMPTS } from '@aglyn/tenant-data-admin/server/plugin-event-outbox'\nimport type { HostOrder } from '../model/commerce-orders'\nimport {\n readShippingEasyCallback,\n SHIPPINGEASY_DEFAULT_BASE_URL,\n SHIPPINGEASY_SHIPPED_STATES,\n SHIPPINGEASY_TIMESTAMP_TOLERANCE_MS,\n shippingEasyCanonicalQuery,\n shippingEasyExternalId,\n shippingEasyIntent,\n shippingEasyOrderPayload,\n shippingEasySignaturePlaintext,\n} from '../model/shippingeasy'\nimport { readCommerceSecretKeyring } from './order-webhooks'\nimport {\n connectorHostIdOf,\n connectorSiteRefusal,\n connectorText as text,\n digestSecret,\n requestIsSecure,\n} from './shipping-connector-gates'\nimport { recordShipNotice, shipStationProductsFor } from './shipstation'\n\n// lockdown-423: handled — the callback route and every push ask `connectorSiteRefusal`, which asks `lockdownRefusal`, before any write.\n\n/*\n * THE SHIPPINGEASY CONNECTOR (AGL-3633). The order and the callback as data\n * are in `model/shippingeasy.ts`; this is the HTTP, the keys and the state.\n *\n * ## The merchant's own keys, so nothing to sign up for on our side\n *\n * ShippingEasy's order API is a CUSTOMER API: a merchant pushes orders into\n * their own account with the API key and secret on their Settings → API\n * Credentials page and the store API key of an \"API\" store. There is no\n * partner account between us and them, so the connector is offered wherever\n * commerce's secret keyring exists (`readCommerceSecretKeyring`), exactly as\n * the ShipStation card is, and nowhere else. The secret is sealed with the\n * shared secret box, bound to its site by the seal context; it never leaves\n * the server again, not even to the admin who typed it.\n *\n * ## Orders go out on commerce's own events\n *\n * Commerce subscribes to its `order.paid`, `order.cancelled` and\n * `order.refunded` events under the name `shippingeasy`\n * (`order-event-triggers.ts`). The subscriber pushes the order as it stands\n * NOW, read fresh, so a retried event never sends a stale copy: a paid order\n * with something to ship is created, and a canceled or refunded one that was\n * sent is canceled there. A failure that may pass (a 5xx, a 429, a timeout)\n * throws, and the outbox retries with its backoff; on the last attempt the\n * site's managers are told. A refusal that will not pass (bad keys, an order\n * ShippingEasy rejects) is recorded on the card instead of retried forever.\n * \"Send open orders\" on the card pushes what is open now, for the orders\n * paid before the connection or while it was failing.\n *\n * ## Each order once\n *\n * A record per order under the connection\n * (`commerceShippingEasyConnections/{hostId}/orders/{orderId}`) is claimed in\n * a transaction before the call: a second push of the same order while the\n * first is in flight is retried later, and one already sent answers\n * `already`. When ShippingEasy refuses a create, the order is looked up by\n * its identifier first, so an order that did arrive (a response lost on the\n * way back) is recorded as sent rather than as a failure.\n *\n * ## The callback is a shipment, recorded once\n *\n * `/api/commerce/shippingeasy/{hostId}` takes ShippingEasy's shipment\n * notification, signed with the merchant's API secret over the callback's\n * own path, query and body, and records each order of the shipment through\n * `recordShipNotice` — the same door ShipStation's notices go through — keyed\n * by the tracking number, so a redelivery or a number typed into Aglyn first\n * is one parcel and the buyer gets one shipped email.\n */\n\n/** Where each site's connection is kept: server-only, every client denied. */\nexport const SHIPPINGEASY_CONNECTIONS = 'commerceShippingEasyConnections'\n\n/** The per-order records under a connection. */\nexport const SHIPPINGEASY_ORDER_RECORDS = 'orders'\n\n/** Callback requests per minute, per site and per calling address. */\nexport const SHIPPINGEASY_RATE_LIMIT = 120\n\n/** The largest callback body read. A shipment of a few orders is tens of kilobytes. */\nexport const SHIPPINGEASY_CALLBACK_MAX_BYTES = 1024 * 1024\n\n/** How long a claimed push holds its order before another may try. */\nexport const SHIPPINGEASY_CLAIM_MS = 2 * 60_000\n\n/** How long one call to ShippingEasy may take. */\nexport const SHIPPINGEASY_REQUEST_TIMEOUT_MS = 15_000\n\n/** Orders \"Send open orders\" pushes per click. */\nexport const SHIPPINGEASY_SYNC_LIMIT = 100\n\n/** How far back \"Send open orders\" looks: older open orders are the merchant's to handle by hand. */\nexport const SHIPPINGEASY_SYNC_WINDOW_MS = 90 * 24 * 60 * 60 * 1000\n\n/** The env var that points the connector at another ShippingEasy address. */\nexport const SHIPPINGEASY_BASE_URL_ENV = 'SHIPPINGEASY_API_BASE_URL'\n\n/** A connection as stored. */\nexport interface ShippingEasyConnection {\n hostId: string\n /** The account's API key: sent in every query string, so not a secret on its own. */\n apiKey: string\n /** The account's API secret, sealed with the secret box; never stored in the clear. */\n sealedApiSecret: string\n /** The id of the key that sealed it, for rotation. */\n secretKeyId: string\n /** The API store's key, from ShippingEasy's Stores & Orders page. */\n storeApiKey: string\n createdAtMs: number\n createdBy: string\n updatedAtMs?: number\n lastPushAtMs?: number\n lastCallbackAtMs?: number\n /** The last thing that went wrong, for the card. Cleared by the next success. */\n lastError?: { message: string; atMs: number; orderNumber?: string } | null\n}\n\n/** Where one order stands with ShippingEasy. */\nexport type ShippingEasyOrderState = 'sending' | 'sent' | 'failed' | 'canceling' | 'canceled' | 'cancel_failed'\n\n/** One order's record under a connection. */\nexport interface ShippingEasyOrderRecord {\n orderId: string\n externalId: string\n state: ShippingEasyOrderState\n claimedAtMs?: number\n sentAtMs?: number\n canceledAtMs?: number\n error?: string | null\n updatedAtMs: number\n}\n\n/** The merchant's keys in the clear, for one call. */\nexport interface ShippingEasyCredentials {\n apiKey: string\n apiSecret: string\n storeApiKey: string\n}\n\n/** What the connector's server calls are given, so specs can stand in for the network and the clock. */\nexport interface ShippingEasyDeps {\n fetchImpl?: typeof fetch\n now?: () => number\n env?: Record<string, string | undefined>\n}\n\n/** A failure that may pass: the outbox retries it. */\nexport class ShippingEasyUnavailableError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ShippingEasyUnavailableError'\n }\n}\n\n/*==========================================\n * KEYS\n *=========================================*/\n\n/** What a key from ShippingEasy's pages looks like: their keys are hex, allowed some slack. */\nexport const SHIPPINGEASY_KEY = /^[A-Za-z0-9_-]{8,128}$/\n\n/** What binds a sealed secret to its site: a copy opens for no other. */\nexport const shippingEasySealContext = (hostId: string): string => `${SHIPPINGEASY_CONNECTIONS}/${hostId}#apiSecret`\n\n/** The API secret sealed for a site, with the id of the key that sealed it. */\nexport function sealShippingEasySecret(\n hostId: string,\n apiSecret: string,\n keyring: SecretBoxKeyring,\n): Pick<ShippingEasyConnection, 'sealedApiSecret' | 'secretKeyId'> {\n return {\n sealedApiSecret: sealSecret(apiSecret, keyring.current, { context: shippingEasySealContext(hostId) }),\n secretKeyId: keyring.current.id,\n }\n}\n\n/** A site's API secret in the clear and whether to seal it again, or `null` when it cannot be opened. */\nexport function openShippingEasySecret(\n connection: Pick<ShippingEasyConnection, 'hostId' | 'sealedApiSecret'> | null,\n keyring: SecretBoxKeyring | null,\n): { apiSecret: string; reseal: boolean } | null {\n if (!connection?.sealedApiSecret || !keyring) return null\n try {\n const opened = openSecret(connection.sealedApiSecret, keyring, { context: shippingEasySealContext(connection.hostId) })\n return { apiSecret: opened.plaintext, reseal: needsReseal(opened, keyring) }\n } catch {\n return null\n }\n}\n\n/** The connection's keys in the clear, or `null` when the secret cannot be opened. */\nexport function shippingEasyCredentialsOf(\n connection: ShippingEasyConnection,\n keyring: SecretBoxKeyring | null,\n): ShippingEasyCredentials | null {\n const opened = openShippingEasySecret(connection, keyring)\n return opened ? { apiKey: connection.apiKey, apiSecret: opened.apiSecret, storeApiKey: connection.storeApiKey } : null\n}\n\n/** The hex HMAC-SHA256 ShippingEasy signs with. */\nexport function signShippingEasy(apiSecret: string, plaintext: string): string {\n return createHmac('sha256', apiSecret).update(plaintext, 'utf8').digest('hex')\n}\n\n/** ShippingEasy's API address: the env's when it is an https URL, else theirs. */\nexport function shippingEasyBaseUrl(env: Record<string, string | undefined> = process.env): string {\n const configured = String(env[SHIPPINGEASY_BASE_URL_ENV] ?? '').trim()\n if (configured) {\n try {\n const url = new URL(configured)\n if (url.protocol === 'https:') return url.origin\n } catch {\n // An unreadable override falls back to ShippingEasy's own address.\n }\n }\n return SHIPPINGEASY_DEFAULT_BASE_URL\n}\n\n/*==========================================\n * CALLS\n *=========================================*/\n\n/** One signed call to ShippingEasy. `path` is the full path, `/api/…`. Network failures become `ShippingEasyUnavailableError`. */\nexport async function shippingEasyRequest(\n credentials: ShippingEasyCredentials,\n input: { method: 'GET' | 'POST'; path: string; query?: Record<string, string>; body?: unknown },\n deps: ShippingEasyDeps = {},\n): Promise<{ status: number; text: string }> {\n const now = deps.now ?? Date.now\n const body = input.body === undefined ? '' : JSON.stringify(input.body)\n const params: Record<string, string> = {\n ...(input.query ?? {}),\n api_key: credentials.apiKey,\n api_timestamp: String(Math.floor(now() / 1000)),\n }\n const signature = signShippingEasy(\n credentials.apiSecret,\n shippingEasySignaturePlaintext({ method: input.method, path: input.path, params, body }),\n )\n const url = `${shippingEasyBaseUrl(deps.env)}${input.path}?${shippingEasyCanonicalQuery(params)}&api_signature=${signature}`\n const fetchImpl = deps.fetchImpl ?? fetch\n try {\n const response = await fetchImpl(url, {\n method: input.method,\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n redirect: 'manual',\n signal: AbortSignal.timeout(SHIPPINGEASY_REQUEST_TIMEOUT_MS),\n })\n return { status: response.status, text: (await response.text().catch(() => '')).slice(0, 4000) }\n } catch (error) {\n throw new ShippingEasyUnavailableError(`ShippingEasy could not be reached (${(error as Error)?.name ?? 'error'})`)\n }\n}\n\n/** A refusal's words for the card: ShippingEasy's own message when it sent one, else its status. */\nexport function shippingEasyRefusalMessage(response: { status: number; text: string }): string {\n const fallback = `ShippingEasy answered ${response.status}`\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(response.text) as Record<string, unknown>\n } catch {\n return fallback\n }\n const errors = parsed?.['errors'] ?? parsed?.['error'] ?? parsed?.['message']\n const message = Array.isArray(errors)\n ? errors.map((entry) => (typeof entry === 'string' ? entry : JSON.stringify(entry))).join('; ')\n : typeof errors === 'string'\n ? errors\n : errors\n ? JSON.stringify(errors)\n : ''\n return (message || fallback).slice(0, 300)\n}\n\nconst storePath = (credentials: ShippingEasyCredentials) => `/api/stores/${encodeURIComponent(credentials.storeApiKey)}/orders`\n\n/** Whether a status may pass on its own: worth the outbox's retry. */\nconst transient = (status: number) => status >= 500 || status === 429 || status === 408\n\n/**\n * Whether the keys work: one read of the store's orders. `refused` means\n * ShippingEasy said no to these keys or this store; `unavailable` means it\n * could not be asked.\n */\nexport async function checkShippingEasyCredentials(\n credentials: ShippingEasyCredentials,\n deps: ShippingEasyDeps = {},\n): Promise<{ ok: true } | { ok: false; reason: 'refused' | 'unavailable'; message: string }> {\n try {\n const response = await shippingEasyRequest(credentials, { method: 'GET', path: storePath(credentials), query: { per_page: '1' } }, deps)\n if (response.status >= 200 && response.status < 300) return { ok: true }\n if (transient(response.status)) return { ok: false, reason: 'unavailable', message: shippingEasyRefusalMessage(response) }\n return { ok: false, reason: 'refused', message: shippingEasyRefusalMessage(response) }\n } catch (error) {\n return { ok: false, reason: 'unavailable', message: (error as Error).message }\n }\n}\n\n/*==========================================\n * PUSHING AN ORDER\n *=========================================*/\n\n/** What one push came to. A transient failure throws `ShippingEasyUnavailableError` instead. */\nexport type ShippingEasyPushOutcome =\n | { result: 'not_connected' | 'skipped' | 'already' | 'sent' | 'canceled' }\n | { result: 'refused'; message: string }\n | { result: 'failed'; message: string }\n\nfunction connectionRef(hostId: string): FirebaseFirestore.DocumentReference {\n return firebaseAdmin.app().firestore().collection(SHIPPINGEASY_CONNECTIONS).doc(hostId)\n}\n\n/** Records the last error, or clears it after a success, on the connection. Never throws. */\nasync function noteOnConnection(\n hostId: string,\n patch: Partial<Pick<ShippingEasyConnection, 'lastPushAtMs' | 'lastCallbackAtMs' | 'lastError'>>,\n): Promise<void> {\n await connectionRef(hostId)\n .set(patch, { merge: true })\n .catch((error: unknown) => console.warn('shippingeasy: connection not stamped', hostId, error))\n}\n\ntype Claim =\n | { go: true; record: ShippingEasyOrderRecord | null }\n | { go: false; outcome: ShippingEasyPushOutcome }\n\n/**\n * Sends one order to ShippingEasy as it stands now, or cancels it there.\n * Safe to call any number of times for the same order: see the block header.\n */\nexport async function pushShippingEasyOrder(\n hostId: string,\n orderId: string,\n deps: ShippingEasyDeps = {},\n): Promise<ShippingEasyPushOutcome> {\n const now = deps.now ?? Date.now\n const firestore = firebaseAdmin.app().firestore()\n const stored = await connectionRef(hostId).get()\n if (!stored.exists) return { result: 'not_connected' }\n const connection = stored.data() as ShippingEasyConnection\n const orderSnapshot = await firestore.collection('hosts').doc(hostId).collection('orders').doc(orderId).get()\n if (!orderSnapshot.exists) return { result: 'skipped' }\n const order = (orderSnapshot.data() ?? {}) as Partial<HostOrder> & { createdAtMs?: number; updatedAtMs?: number }\n const intent = shippingEasyIntent(order, orderId)\n if (!intent) return { result: 'skipped' }\n\n const refused = await connectorSiteRefusal({ method: 'POST' }, hostId)\n if (refused) return { result: 'refused', message: (await refused.text()).slice(0, 300) }\n\n const credentials = shippingEasyCredentialsOf(connection, readCommerceSecretKeyring())\n const externalId = shippingEasyExternalId(order, orderId)\n if (!credentials) {\n const message = 'The ShippingEasy API secret cannot be opened. Enter your keys again.'\n await noteOnConnection(hostId, { lastError: { message, atMs: now(), orderNumber: externalId } })\n return { result: 'failed', message }\n }\n\n const recordRef = connectionRef(hostId).collection(SHIPPINGEASY_ORDER_RECORDS).doc(orderId)\n const claim: Claim = await firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(recordRef)\n const record = snapshot.exists ? (snapshot.data() as ShippingEasyOrderRecord) : null\n const atMs = now()\n const inFlight =\n (record?.state === 'sending' || record?.state === 'canceling') &&\n atMs - Number(record.claimedAtMs ?? 0) < SHIPPINGEASY_CLAIM_MS\n if (inFlight) throw new ShippingEasyUnavailableError('This order is being sent to ShippingEasy already')\n if (intent === 'create') {\n if (record && record.state !== 'sending' && record.state !== 'failed') {\n return { go: false, outcome: { result: 'already' } }\n }\n transaction.set(recordRef, {\n orderId,\n externalId: record?.externalId ?? externalId,\n state: 'sending',\n claimedAtMs: atMs,\n updatedAtMs: atMs,\n } satisfies ShippingEasyOrderRecord)\n return { go: true, record }\n }\n // Cancel: only what was sent there. An order that never arrived has\n // nothing to cancel, and one canceled already is answered as such.\n if (!record || record.state === 'failed' || record.state === 'sending') return { go: false, outcome: { result: 'skipped' } }\n if (record.state === 'canceled') return { go: false, outcome: { result: 'already' } }\n transaction.set(recordRef, { ...record, state: 'canceling', claimedAtMs: atMs, updatedAtMs: atMs })\n return { go: true, record }\n })\n if ('outcome' in claim) return claim.outcome\n\n const sentId = claim.record?.externalId ?? externalId\n const settle = async (state: ShippingEasyOrderState, extra: Partial<ShippingEasyOrderRecord> = {}) => {\n await recordRef.set({ state, updatedAtMs: now(), ...extra }, { merge: true })\n }\n\n if (intent === 'cancel') {\n let response: { status: number; text: string }\n try {\n response = await shippingEasyRequest(\n credentials,\n { method: 'POST', path: `${storePath(credentials)}/${encodeURIComponent(sentId)}/cancellations` },\n deps,\n )\n } catch (error) {\n await settle('sent')\n throw error\n }\n if (transient(response.status)) {\n await settle('sent')\n throw new ShippingEasyUnavailableError(shippingEasyRefusalMessage(response))\n }\n if (response.status >= 200 && response.status < 300) {\n await settle('canceled', { canceledAtMs: now(), error: null })\n await noteOnConnection(hostId, { lastPushAtMs: now(), lastError: null })\n return { result: 'canceled' }\n }\n const message = `Order ${sentId} could not be canceled in ShippingEasy: ${shippingEasyRefusalMessage(response)}`\n await settle('cancel_failed', { error: message })\n await noteOnConnection(hostId, { lastError: { message, atMs: now(), orderNumber: sentId } })\n return { result: 'failed', message }\n }\n\n const lookUp = async (): Promise<boolean> => {\n const found = await shippingEasyRequest(\n credentials,\n { method: 'GET', path: `${storePath(credentials)}/${encodeURIComponent(sentId)}` },\n deps,\n ).catch(() => null)\n return Boolean(found && found.status >= 200 && found.status < 300)\n }\n const markSent = async (): Promise<ShippingEasyPushOutcome> => {\n await settle('sent', { sentAtMs: now(), error: null })\n await noteOnConnection(hostId, { lastPushAtMs: now(), lastError: null })\n return { result: 'sent' }\n }\n // A claim that lapsed mid-call may have landed: look before creating\n // again, so ShippingEasy never holds the order twice.\n if (claim.record?.state === 'sending' && (await lookUp())) return markSent()\n\n const hostRef = firestore.collection('hosts').doc(hostId)\n const products = await shipStationProductsFor(hostRef, [order])\n const payload = shippingEasyOrderPayload({ docId: orderId, order }, products)\n // A network failure throws past here with the claim left to lapse rather\n // than released: the call may have landed, and the retry's refused create\n // looks the order up before calling it failed.\n const response = await shippingEasyRequest(credentials, { method: 'POST', path: storePath(credentials), body: payload }, deps)\n if (transient(response.status)) {\n await settle('failed', { error: shippingEasyRefusalMessage(response) })\n throw new ShippingEasyUnavailableError(shippingEasyRefusalMessage(response))\n }\n if (response.status >= 200 && response.status < 300) return markSent()\n // A refused create may be an order that arrived on an earlier try whose\n // answer was lost: ask for it by its identifier before calling it failed.\n if (response.status !== 401 && response.status !== 403 && (await lookUp())) return markSent()\n const message =\n response.status === 401 || response.status === 403\n ? 'ShippingEasy refused the API key, secret or store API key. Enter them again.'\n : `Order ${sentId} was refused by ShippingEasy: ${shippingEasyRefusalMessage(response)}`\n await settle('failed', { error: message })\n await noteOnConnection(hostId, { lastError: { message, atMs: now(), orderNumber: sentId } })\n return { result: 'failed', message }\n}\n\n/**\n * The `shippingeasy` subscriber to commerce's order events. Throws on a\n * failure that may pass, so the outbox retries; on its last attempt the\n * site's managers are told and the card shows why.\n */\nexport async function deliverOrderEventToShippingEasy(\n envelope: Pick<PluginDomainEventEnvelope, 'hostId' | 'payload' | 'attempt' | 'event'>,\n deps: ShippingEasyDeps = {},\n): Promise<ShippingEasyPushOutcome | null> {\n const orderId = String(((envelope.payload ?? {}) as { order?: { id?: unknown } }).order?.id ?? '')\n if (!orderId || orderId.includes('/')) return null\n try {\n return await pushShippingEasyOrder(envelope.hostId, orderId, deps)\n } catch (error) {\n if (!(error instanceof ShippingEasyUnavailableError) || envelope.attempt < PLUGIN_EVENT_MAX_ATTEMPTS) throw error\n const message = `${error.message}. Select Send open orders on the ShippingEasy card to try again.`\n await noteOnConnection(envelope.hostId, { lastError: { message, atMs: (deps.now ?? Date.now)() } })\n await notifyHostManagers(envelope.hostId, {\n type: 'content.order',\n title: 'An order could not be sent to ShippingEasy',\n body: `An order on {site} never reached ShippingEasy after ${envelope.attempt} attempts (${error.message}).`,\n link: `/${envelope.hostId}/products/settings`,\n }).catch(() => undefined)\n return { result: 'failed', message }\n }\n}\n\n/** What \"Send open orders\" came to. */\nexport interface ShippingEasySyncResult {\n sent: number\n already: number\n failed: number\n /** Whether more open orders are left past this pass's limit. */\n more: boolean\n}\n\n/**\n * Pushes the site's open orders from the last 90 days, a pass at a time: the\n * orders paid before the connection, or while it was failing. One indexed\n * query (`requiresShipping ==`, `updatedAtMs` range) — the same index the\n * ShipStation feed reads.\n */\nexport async function syncShippingEasyOrders(hostId: string, deps: ShippingEasyDeps = {}): Promise<ShippingEasySyncResult> {\n const now = deps.now ?? Date.now\n const orders = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('orders')\n .where('requiresShipping', '==', true)\n .where('updatedAtMs', '>=', now() - SHIPPINGEASY_SYNC_WINDOW_MS)\n .orderBy('updatedAtMs', 'asc')\n .limit(500)\n .get()\n const open = orders.docs.filter((doc) => shippingEasyIntent((doc.data() ?? {}) as never, doc.id) === 'create')\n const result: ShippingEasySyncResult = { sent: 0, already: 0, failed: 0, more: open.length > SHIPPINGEASY_SYNC_LIMIT }\n for (const doc of open.slice(0, SHIPPINGEASY_SYNC_LIMIT)) {\n try {\n const outcome = await pushShippingEasyOrder(hostId, doc.id, deps)\n if (outcome.result === 'sent') result.sent += 1\n else if (outcome.result === 'already') result.already += 1\n else if (outcome.result === 'failed' || outcome.result === 'refused') {\n result.failed += 1\n // Bad keys or a refused site fail every order alike: stop asking.\n if (outcome.result === 'refused' || /refused the API key/.test(outcome.message)) break\n }\n } catch (error) {\n result.failed += 1\n if (error instanceof ShippingEasyUnavailableError) continue\n throw error\n }\n }\n return result\n}\n\n/*==========================================\n * THE CALLBACK\n *=========================================*/\n\n/**\n * Whether a callback was signed with this secret: the HMAC of its method,\n * path, query (without `api_signature`) and raw body, compared in constant\n * time; and, when it carries `api_timestamp`, sent within the hour.\n */\nexport function verifyShippingEasyCallback(input: {\n apiSecret: string\n method: string\n path: string\n query: URLSearchParams\n body: string\n nowMs: number\n}): 'ok' | 'unsigned' | 'bad_signature' | 'expired' {\n const presented = String(input.query.get('api_signature') ?? '').trim().toLowerCase()\n if (!/^[0-9a-f]{64}$/.test(presented)) return 'unsigned'\n const params: Record<string, string> = {}\n for (const [name, value] of input.query.entries()) if (name !== 'api_signature') params[name] = value\n const expected = signShippingEasy(\n input.apiSecret,\n shippingEasySignaturePlaintext({ method: input.method, path: input.path, params, body: input.body }),\n )\n if (!timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(presented, 'hex'))) return 'bad_signature'\n if (params['api_timestamp'] !== undefined) {\n const seconds = Number(params['api_timestamp'])\n if (!Number.isFinite(seconds) || Math.abs(input.nowMs - seconds * 1000) > SHIPPINGEASY_TIMESTAMP_TOLERANCE_MS) return 'expired'\n }\n return 'ok'\n}\n\n/** `POST /api/commerce/shippingeasy/{hostId}` — ShippingEasy's shipment notification. See the block header. */\nexport async function shippingEasyRoute(\n request: Request,\n context: { params: Record<string, string | string[]> },\n deps: ShippingEasyDeps = {},\n): Promise<Response> {\n const now = deps.now ?? Date.now\n const hostId = connectorHostIdOf(context.params)\n if (!hostId) return text(404, 'Not found')\n if (request.method !== 'POST') return text(405, 'ShippingEasy posts shipments here.', { allow: 'POST' })\n if (!requestIsSecure(request, deps.env)) return text(403, `Use the https:// address ${PLATFORM_BRAND_NAME} gave you.`)\n\n const ip = readClientIp(request.headers) ?? 'unknown'\n const rate = await consumeRateLimit(`shippingeasy:${hostId}:${ip}`, { limit: SHIPPINGEASY_RATE_LIMIT, windowMs: 60_000 })\n if (!rate.allowed) {\n return text(429, 'Too many requests; ShippingEasy will retry.', {\n 'retry-after': String(Math.max(1, Math.ceil((rate.resetMs - now()) / 1000))),\n })\n }\n\n const declared = Number(request.headers.get('content-length') ?? 0)\n if (declared > SHIPPINGEASY_CALLBACK_MAX_BYTES) return text(413, 'The callback is too large.')\n const body = await request.text()\n if (Buffer.byteLength(body, 'utf8') > SHIPPINGEASY_CALLBACK_MAX_BYTES) return text(413, 'The callback is too large.')\n\n const ref = connectionRef(hostId)\n const stored = await ref.get()\n const connection = stored.exists ? (stored.data() as ShippingEasyConnection) : null\n const keyring = readCommerceSecretKeyring()\n const opened = openShippingEasySecret(connection, keyring)\n if (connection && !opened) console.error('shippingeasy: the stored API secret could not be opened', hostId)\n const url = new URL(request.url)\n // An unknown site is answered exactly like a wrong signature: an unproven\n // caller learns nothing about which sites are connected.\n const verdict = opened\n ? verifyShippingEasyCallback({ apiSecret: opened.apiSecret, method: request.method, path: url.pathname, query: url.searchParams, body, nowMs: now() })\n : 'bad_signature'\n if (verdict !== 'ok') {\n return text(401, verdict === 'expired' ? 'The callback is too old.' : `The signature does not match the API secret saved in ${PLATFORM_BRAND_NAME}.`)\n }\n if (opened?.reseal && keyring) {\n await ref\n .set(sealShippingEasySecret(hostId, opened.apiSecret, keyring), { merge: true })\n .catch((error: unknown) => console.warn('shippingeasy: secret not resealed', hostId, error))\n }\n\n const refused = await connectorSiteRefusal(request, hostId)\n if (refused) return refused\n\n const callback = readShippingEasyCallback(body)\n if ('problem' in callback) return text(400, callback.problem)\n await noteOnConnection(hostId, { lastCallbackAtMs: now() })\n if (!SHIPPINGEASY_SHIPPED_STATES.has(callback.workflowState)) {\n return text(200, `Nothing recorded for a ${callback.workflowState || 'shipment'} label.`)\n }\n const digest = digestSecret(body).slice(0, 32)\n const problems: string[] = []\n try {\n for (const notice of callback.notices) {\n const outcome = await recordShipNotice(hostId, notice, digest, { source: 'shippingeasy' })\n if (outcome.status !== 200) problems.push(outcome.body)\n }\n } catch (error) {\n console.error('shippingeasy: callback failed', hostId, error)\n return text(500, `${PLATFORM_BRAND_NAME} could not record the shipment. ShippingEasy will retry.`)\n }\n // A notice for an order this site does not have, or one already canceled,\n // will not record on a retry either: it is answered 200 so ShippingEasy\n // stops sending it, and the card says what happened.\n if (problems.length) {\n await noteOnConnection(hostId, { lastError: { message: problems.join(' ').slice(0, 300), atMs: now() } })\n }\n return text(200, problems.length ? problems.join(' ') : 'Recorded.')\n}\n\n/*==========================================\n * THE CONSOLE CARD\n *=========================================*/\n\n/** What the card shows about a connection. Never a key in full, never the secret. */\nexport interface ShippingEasyConnectionStatus {\n /** Whether this deployment can seal a secret at all. */\n available: boolean\n connected: boolean\n apiKeyEnding?: string\n storeApiKeyEnding?: string\n createdAtMs?: number\n updatedAtMs?: number\n lastPushAtMs?: number\n lastCallbackAtMs?: number\n lastError?: { message: string; atMs: number; orderNumber?: string } | null\n}\n\nconst ending = (key: string) => key.slice(-4)\n\nexport function shippingEasyStatusOf(connection: ShippingEasyConnection | null, available: boolean): ShippingEasyConnectionStatus {\n if (!connection) return { available, connected: false }\n return {\n available,\n connected: true,\n apiKeyEnding: ending(connection.apiKey),\n storeApiKeyEnding: ending(connection.storeApiKey),\n createdAtMs: connection.createdAtMs,\n ...(connection.updatedAtMs ? { updatedAtMs: connection.updatedAtMs } : {}),\n ...(connection.lastPushAtMs ? { lastPushAtMs: connection.lastPushAtMs } : {}),\n ...(connection.lastCallbackAtMs ? { lastCallbackAtMs: connection.lastCallbackAtMs } : {}),\n ...(connection.lastError ? { lastError: connection.lastError } : {}),\n }\n}\n\n/** The card's actions. `status` is the GET. */\nexport const SHIPPINGEASY_ACTIONS = ['status', 'connect', 'sync', 'disconnect'] as const\nexport type ShippingEasyAction = (typeof SHIPPINGEASY_ACTIONS)[number]\n\nconst ACTIVITY = { type: 'commerce:shippingEasy', id: 'shippingeasy', name: 'ShippingEasy' } as const\n\n/**\n * One action of the ShippingEasy card, for a caller the connectors route has\n * already proven a site admin (or, for `status`, an editor) on a site with\n * commerce. Answers the status and body the route sends.\n *\n * - `connect` saves the three keys after one read proves them, sealing the\n * secret; on a site already connected it replaces them (a merchant who\n * made a new secret in ShippingEasy enters it here).\n * - `sync` pushes the open orders, a pass at a time.\n * - `disconnect` deletes the connection; the per-order records go with it,\n * so a later connect sends the open orders afresh.\n */\nexport async function shippingEasyConnectorAction(\n input: {\n hostId: string\n action: ShippingEasyAction\n body: Record<string, unknown>\n actor: { uid: string; email: string | null }\n log: (action: string, target: typeof ACTIVITY) => Promise<void>\n prepareOpenOrders?: (hostId: string) => Promise<unknown>\n },\n deps: ShippingEasyDeps = {},\n): Promise<{ status: number; body: Record<string, unknown> }> {\n const { hostId, action, body } = input\n const now = deps.now ?? Date.now\n const keyring = readCommerceSecretKeyring()\n const available = keyring !== null\n const ref = connectionRef(hostId)\n const read = async () => {\n const doc = await ref.get()\n return doc.exists ? (doc.data() as ShippingEasyConnection) : null\n }\n\n if (action === 'status') return { status: 200, body: { ...shippingEasyStatusOf(await read(), available) } }\n\n if (action === 'disconnect') {\n if (!(await read())) return { status: 200, body: { available, connected: false } }\n await firebaseAdmin.app().firestore().recursiveDelete(ref)\n await input.log('Disconnected ShippingEasy', ACTIVITY)\n return { status: 200, body: { available, connected: false } }\n }\n\n if (!keyring) return { status: 503, body: { error: 'ShippingEasy cannot be connected on this deployment yet' } }\n\n if (action === 'sync') {\n const connection = await read()\n if (!connection) return { status: 409, body: { error: 'ShippingEasy is not connected.' } }\n const result = await syncShippingEasyOrders(hostId, deps)\n await input.log('Sent open orders to ShippingEasy', ACTIVITY)\n return { status: 200, body: { ...shippingEasyStatusOf(await read(), available), sync: { ...result } } }\n }\n\n const keys = {\n apiKey: String(body['apiKey'] ?? '').trim(),\n apiSecret: String(body['apiSecret'] ?? '').trim(),\n storeApiKey: String(body['storeApiKey'] ?? '').trim(),\n }\n if (!SHIPPINGEASY_KEY.test(keys.apiKey) || !SHIPPINGEASY_KEY.test(keys.apiSecret) || !SHIPPINGEASY_KEY.test(keys.storeApiKey)) {\n return { status: 400, body: { error: 'Paste the API key, API secret and store API key exactly as ShippingEasy shows them.' } }\n }\n const check = await checkShippingEasyCredentials(keys, deps)\n if ('reason' in check) {\n return check.reason === 'refused'\n ? { status: 422, body: { error: `ShippingEasy did not accept these keys (${check.message}). Check all three and try again.` } }\n : { status: 502, body: { error: 'ShippingEasy could not be reached. Try again in a minute.' } }\n }\n const atMs = now()\n const existing = await read()\n const connection: ShippingEasyConnection = {\n ...(existing ?? { createdAtMs: atMs, createdBy: input.actor.uid }),\n hostId,\n apiKey: keys.apiKey,\n storeApiKey: keys.storeApiKey,\n ...sealShippingEasySecret(hostId, keys.apiSecret, keyring),\n ...(existing ? { updatedAtMs: atMs } : {}),\n lastError: null,\n }\n await ref.set(connection)\n if (!existing && input.prepareOpenOrders) {\n await input.prepareOpenOrders(hostId).catch((error: unknown) =>\n console.warn('shippingeasy: open orders not stamped', hostId, error),\n )\n }\n await input.log(existing ? 'Replaced the ShippingEasy keys' : 'Connected ShippingEasy', ACTIVITY)\n return { status: 200, body: { ...shippingEasyStatusOf(connection, available) } }\n}\n"],"names":["createHmac","timingSafeEqual","PLATFORM_BRAND_NAME","readClientIp","needsReseal","openSecret","sealSecret","consumeRateLimit","firebaseAdmin","notifyHostManagers","PLUGIN_EVENT_MAX_ATTEMPTS","readShippingEasyCallback","SHIPPINGEASY_DEFAULT_BASE_URL","SHIPPINGEASY_SHIPPED_STATES","SHIPPINGEASY_TIMESTAMP_TOLERANCE_MS","shippingEasyCanonicalQuery","shippingEasyExternalId","shippingEasyIntent","shippingEasyOrderPayload","shippingEasySignaturePlaintext","readCommerceSecretKeyring","connectorHostIdOf","connectorSiteRefusal","connectorText","text","digestSecret","requestIsSecure","recordShipNotice","shipStationProductsFor","SHIPPINGEASY_CONNECTIONS","SHIPPINGEASY_ORDER_RECORDS","SHIPPINGEASY_RATE_LIMIT","SHIPPINGEASY_CALLBACK_MAX_BYTES","SHIPPINGEASY_CLAIM_MS","SHIPPINGEASY_REQUEST_TIMEOUT_MS","SHIPPINGEASY_SYNC_LIMIT","SHIPPINGEASY_SYNC_WINDOW_MS","SHIPPINGEASY_BASE_URL_ENV","ShippingEasyUnavailableError","Error","message","name","SHIPPINGEASY_KEY","shippingEasySealContext","hostId","sealShippingEasySecret","apiSecret","keyring","sealedApiSecret","current","context","secretKeyId","id","openShippingEasySecret","connection","opened","plaintext","reseal","shippingEasyCredentialsOf","apiKey","storeApiKey","signShippingEasy","update","digest","shippingEasyBaseUrl","env","process","configured","String","trim","url","URL","protocol","origin","shippingEasyRequest","credentials","input","deps","now","Date","body","undefined","JSON","stringify","params","query","api_key","api_timestamp","Math","floor","signature","method","path","fetchImpl","fetch","response","headers","accept","redirect","signal","AbortSignal","timeout","status","catch","slice","error","shippingEasyRefusalMessage","parsed","fallback","parse","errors","Array","isArray","map","entry","join","storePath","encodeURIComponent","transient","checkShippingEasyCredentials","per_page","ok","reason","connectionRef","app","firestore","collection","doc","noteOnConnection","patch","set","merge","console","warn","pushShippingEasyOrder","orderId","orderSnapshot","claim","stored","get","exists","result","data","order","intent","refused","externalId","lastError","atMs","orderNumber","recordRef","runTransaction","transaction","record","snapshot","inFlight","state","Number","claimedAtMs","go","outcome","updatedAtMs","sentId","settle","extra","canceledAtMs","lastPushAtMs","lookUp","found","Boolean","markSent","sentAtMs","hostRef","products","payload","docId","deliverOrderEventToShippingEasy","envelope","includes","attempt","type","title","link","syncShippingEasyOrders","orders","where","orderBy","limit","open","docs","filter","sent","already","failed","more","length","test","verifyShippingEasyCallback","presented","toLowerCase","value","entries","expected","Buffer","from","seconds","isFinite","abs","nowMs","shippingEasyRoute","request","allow","ip","rate","windowMs","allowed","max","ceil","resetMs","declared","byteLength","ref","verdict","pathname","searchParams","callback","problem","lastCallbackAtMs","has","workflowState","problems","notice","notices","source","push","ending","key","shippingEasyStatusOf","available","connected","apiKeyEnding","storeApiKeyEnding","createdAtMs","SHIPPINGEASY_ACTIONS","ACTIVITY","shippingEasyConnectorAction","action","read","recursiveDelete","log","sync","keys","check","existing","createdBy","actor","uid","prepareOpenOrders"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/shippingeasy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHmac, timingSafeEqual } from 'crypto'\nimport { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport type { PluginDomainEventEnvelope } from '@aglyn/aglyn/plugin-manager/plugin-domain-events'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\nimport { needsReseal, openSecret, sealSecret, type SecretBoxKeyring } from '@aglyn/shared-util-tools/secret-box'\nimport { consumeRateLimit, firebaseAdmin, notifyHostManagers } from '@aglyn/tenant-data-admin'\nimport { PLUGIN_EVENT_MAX_ATTEMPTS } from '@aglyn/tenant-data-admin/server/plugin-event-outbox'\nimport type { HostOrder } from '../model/commerce-orders'\nimport {\n readShippingEasyCallback,\n SHIPPINGEASY_DEFAULT_BASE_URL,\n SHIPPINGEASY_SHIPPED_STATES,\n SHIPPINGEASY_TIMESTAMP_TOLERANCE_MS,\n shippingEasyCanonicalQuery,\n shippingEasyExternalId,\n shippingEasyIntent,\n shippingEasyOrderPayload,\n shippingEasySignaturePlaintext,\n} from '../model/shippingeasy'\nimport { readCommerceSecretKeyring } from './order-webhooks'\nimport {\n connectorHostIdOf,\n connectorSiteRefusal,\n connectorText as text,\n digestSecret,\n requestIsSecure,\n} from './shipping-connector-gates'\nimport { recordShipNotice, shipStationProductsFor } from './shipstation'\n\n// lockdown-423: handled — the callback route and every push ask `connectorSiteRefusal`, which asks `lockdownRefusal`, before any write.\n\n/*\n * THE SHIPPINGEASY CONNECTOR (AGL-3633). The order and the callback as data\n * are in `model/shippingeasy.ts`; this is the HTTP, the keys and the state.\n *\n * ## The merchant's own keys, so nothing to sign up for on our side\n *\n * ShippingEasy's order API is a CUSTOMER API: a merchant pushes orders into\n * their own account with the API key and secret on their Settings → API\n * Credentials page and the store API key of an \"API\" store. There is no\n * partner account between us and them, so the connector is offered wherever\n * commerce's secret keyring exists (`readCommerceSecretKeyring`), exactly as\n * the ShipStation card is, and nowhere else. The secret is sealed with the\n * shared secret box, bound to its site by the seal context; it never leaves\n * the server again, not even to the admin who typed it.\n *\n * ## Orders go out on commerce's own events\n *\n * Commerce subscribes to its `order.paid`, `order.cancelled` and\n * `order.refunded` events under the name `shippingeasy`\n * (`order-event-triggers.ts`). The subscriber pushes the order as it stands\n * NOW, read fresh, so a retried event never sends a stale copy: a paid order\n * with something to ship is created, and a canceled or refunded one that was\n * sent is canceled there. A failure that may pass (a 5xx, a 429, a timeout)\n * throws, and the outbox retries with its backoff; on the last attempt the\n * site's managers are told. A refusal that will not pass (bad keys, an order\n * ShippingEasy rejects) is recorded on the card instead of retried forever.\n * \"Send open orders\" on the card pushes what is open now, for the orders\n * paid before the connection or while it was failing.\n *\n * ## Each order once\n *\n * A record per order under the connection\n * (`commerceShippingEasyConnections/{hostId}/orders/{orderId}`) is claimed in\n * a transaction before the call: a second push of the same order while the\n * first is in flight is retried later, and one already sent answers\n * `already`. When ShippingEasy refuses a create, the order is looked up by\n * its identifier first, so an order that did arrive (a response lost on the\n * way back) is recorded as sent rather than as a failure.\n *\n * ## The callback is a shipment, recorded once\n *\n * `/api/commerce/shippingeasy/{hostId}` takes ShippingEasy's shipment\n * notification, signed with the merchant's API secret over the callback's\n * own path, query and body, and records each order of the shipment through\n * `recordShipNotice` — the same door ShipStation's notices go through — keyed\n * by the tracking number, so a redelivery or a number typed into Aglyn first\n * is one parcel and the buyer gets one shipped email.\n */\n\n/** Where each site's connection is kept: server-only, every client denied. */\nexport const SHIPPINGEASY_CONNECTIONS = 'commerceShippingEasyConnections'\n\n/** The per-order records under a connection. */\nexport const SHIPPINGEASY_ORDER_RECORDS = 'orders'\n\n/** Callback requests per minute, per site and per calling address. */\nexport const SHIPPINGEASY_RATE_LIMIT = 120\n\n/** The largest callback body read. A shipment of a few orders is tens of kilobytes. */\nexport const SHIPPINGEASY_CALLBACK_MAX_BYTES = 1024 * 1024\n\n/** How long a claimed push holds its order before another may try. */\nexport const SHIPPINGEASY_CLAIM_MS = 2 * 60_000\n\n/** How long one call to ShippingEasy may take. */\nexport const SHIPPINGEASY_REQUEST_TIMEOUT_MS = 15_000\n\n/** Orders \"Send open orders\" pushes per click. */\nexport const SHIPPINGEASY_SYNC_LIMIT = 100\n\n/** How far back \"Send open orders\" looks: older open orders are the merchant's to handle by hand. */\nexport const SHIPPINGEASY_SYNC_WINDOW_MS = 90 * 24 * 60 * 60 * 1000\n\n/** The env var that points the connector at another ShippingEasy address. */\nexport const SHIPPINGEASY_BASE_URL_ENV = 'SHIPPINGEASY_API_BASE_URL'\n\n/** A connection as stored. */\nexport interface ShippingEasyConnection {\n hostId: string\n /** The account's API key: sent in every query string, so not a secret on its own. */\n apiKey: string\n /** The account's API secret, sealed with the secret box; never stored in the clear. */\n sealedApiSecret: string\n /** The id of the key that sealed it, for rotation. */\n secretKeyId: string\n /** The API store's key, from ShippingEasy's Stores & Orders page. */\n storeApiKey: string\n createdAtMs: number\n createdBy: string\n updatedAtMs?: number\n lastPushAtMs?: number\n lastCallbackAtMs?: number\n /** The last thing that went wrong, for the card. Cleared by the next success. */\n lastError?: { message: string; atMs: number; orderNumber?: string } | null\n}\n\n/** Where one order stands with ShippingEasy. */\nexport type ShippingEasyOrderState = 'sending' | 'sent' | 'failed' | 'canceling' | 'canceled' | 'cancel_failed'\n\n/** One order's record under a connection. */\nexport interface ShippingEasyOrderRecord {\n orderId: string\n externalId: string\n state: ShippingEasyOrderState\n claimedAtMs?: number\n sentAtMs?: number\n canceledAtMs?: number\n error?: string | null\n updatedAtMs: number\n}\n\n/** The merchant's keys in the clear, for one call. */\nexport interface ShippingEasyCredentials {\n apiKey: string\n apiSecret: string\n storeApiKey: string\n}\n\n/** What the connector's server calls are given, so specs can stand in for the network and the clock. */\nexport interface ShippingEasyDeps {\n fetchImpl?: typeof fetch\n now?: () => number\n env?: Record<string, string | undefined>\n}\n\n/** A failure that may pass: the outbox retries it. */\nexport class ShippingEasyUnavailableError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ShippingEasyUnavailableError'\n }\n}\n\n/*==========================================\n * KEYS\n *=========================================*/\n\n/** What a key from ShippingEasy's pages looks like: their keys are hex, allowed some slack. */\nexport const SHIPPINGEASY_KEY = /^[A-Za-z0-9_-]{8,128}$/\n\n/** What binds a sealed secret to its site: a copy opens for no other. */\nexport const shippingEasySealContext = (hostId: string): string => `${SHIPPINGEASY_CONNECTIONS}/${hostId}#apiSecret`\n\n/** The API secret sealed for a site, with the id of the key that sealed it. */\nexport function sealShippingEasySecret(\n hostId: string,\n apiSecret: string,\n keyring: SecretBoxKeyring,\n): Pick<ShippingEasyConnection, 'sealedApiSecret' | 'secretKeyId'> {\n return {\n sealedApiSecret: sealSecret(apiSecret, keyring.current, { context: shippingEasySealContext(hostId) }),\n secretKeyId: keyring.current.id,\n }\n}\n\n/** A site's API secret in the clear and whether to seal it again, or `null` when it cannot be opened. */\nexport function openShippingEasySecret(\n connection: Pick<ShippingEasyConnection, 'hostId' | 'sealedApiSecret'> | null,\n keyring: SecretBoxKeyring | null,\n): { apiSecret: string; reseal: boolean } | null {\n if (!connection?.sealedApiSecret || !keyring) return null\n try {\n const opened = openSecret(connection.sealedApiSecret, keyring, { context: shippingEasySealContext(connection.hostId) })\n return { apiSecret: opened.plaintext, reseal: needsReseal(opened, keyring) }\n } catch {\n return null\n }\n}\n\n/** The connection's keys in the clear, or `null` when the secret cannot be opened. */\nexport function shippingEasyCredentialsOf(\n connection: ShippingEasyConnection,\n keyring: SecretBoxKeyring | null,\n): ShippingEasyCredentials | null {\n const opened = openShippingEasySecret(connection, keyring)\n return opened ? { apiKey: connection.apiKey, apiSecret: opened.apiSecret, storeApiKey: connection.storeApiKey } : null\n}\n\n/** The hex HMAC-SHA256 ShippingEasy signs with. */\nexport function signShippingEasy(apiSecret: string, plaintext: string): string {\n return createHmac('sha256', apiSecret).update(plaintext, 'utf8').digest('hex')\n}\n\n/** ShippingEasy's API address: the env's when it is an https URL, else theirs. */\nexport function shippingEasyBaseUrl(env: Record<string, string | undefined> = process.env): string {\n const configured = String(env[SHIPPINGEASY_BASE_URL_ENV] ?? '').trim()\n if (configured) {\n try {\n const url = new URL(configured)\n if (url.protocol === 'https:') return url.origin\n } catch {\n // An unreadable override falls back to ShippingEasy's own address.\n }\n }\n return SHIPPINGEASY_DEFAULT_BASE_URL\n}\n\n/*==========================================\n * CALLS\n *=========================================*/\n\n/** One signed call to ShippingEasy. `path` is the full path, `/api/…`. Network failures become `ShippingEasyUnavailableError`. */\nexport async function shippingEasyRequest(\n credentials: ShippingEasyCredentials,\n input: { method: 'GET' | 'POST'; path: string; query?: Record<string, string>; body?: unknown },\n deps: ShippingEasyDeps = {},\n): Promise<{ status: number; text: string }> {\n const now = deps.now ?? Date.now\n const body = input.body === undefined ? '' : JSON.stringify(input.body)\n const params: Record<string, string> = {\n ...(input.query ?? {}),\n api_key: credentials.apiKey,\n api_timestamp: String(Math.floor(now() / 1000)),\n }\n const signature = signShippingEasy(\n credentials.apiSecret,\n shippingEasySignaturePlaintext({ method: input.method, path: input.path, params, body }),\n )\n const url = `${shippingEasyBaseUrl(deps.env)}${input.path}?${shippingEasyCanonicalQuery(params)}&api_signature=${signature}`\n const fetchImpl = deps.fetchImpl ?? fetch\n try {\n const response = await fetchImpl(url, {\n method: input.method,\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n redirect: 'manual',\n signal: AbortSignal.timeout(SHIPPINGEASY_REQUEST_TIMEOUT_MS),\n })\n return { status: response.status, text: (await response.text().catch(() => '')).slice(0, 4000) }\n } catch (error) {\n throw new ShippingEasyUnavailableError(`ShippingEasy could not be reached (${(error as Error)?.name ?? 'error'})`)\n }\n}\n\n/** A refusal's words for the card: ShippingEasy's own message when it sent one, else its status. */\nexport function shippingEasyRefusalMessage(response: { status: number; text: string }): string {\n const fallback = `ShippingEasy answered ${response.status}`\n let parsed: Record<string, unknown>\n try {\n parsed = JSON.parse(response.text) as Record<string, unknown>\n } catch {\n return fallback\n }\n const errors = parsed?.['errors'] ?? parsed?.['error'] ?? parsed?.['message']\n const message = Array.isArray(errors)\n ? errors.map((entry) => (typeof entry === 'string' ? entry : JSON.stringify(entry))).join('; ')\n : typeof errors === 'string'\n ? errors\n : errors\n ? JSON.stringify(errors)\n : ''\n return (message || fallback).slice(0, 300)\n}\n\nconst storePath = (credentials: ShippingEasyCredentials) => `/api/stores/${encodeURIComponent(credentials.storeApiKey)}/orders`\n\n/** Whether a status may pass on its own: worth the outbox's retry. */\nconst transient = (status: number) => status >= 500 || status === 429 || status === 408\n\n/**\n * Whether the keys work: one read of the store's orders. `refused` means\n * ShippingEasy said no to these keys or this store; `unavailable` means it\n * could not be asked.\n */\nexport async function checkShippingEasyCredentials(\n credentials: ShippingEasyCredentials,\n deps: ShippingEasyDeps = {},\n): Promise<{ ok: true } | { ok: false; reason: 'refused' | 'unavailable'; message: string }> {\n try {\n const response = await shippingEasyRequest(credentials, { method: 'GET', path: storePath(credentials), query: { per_page: '1' } }, deps)\n if (response.status >= 200 && response.status < 300) return { ok: true }\n if (transient(response.status)) return { ok: false, reason: 'unavailable', message: shippingEasyRefusalMessage(response) }\n return { ok: false, reason: 'refused', message: shippingEasyRefusalMessage(response) }\n } catch (error) {\n return { ok: false, reason: 'unavailable', message: (error as Error).message }\n }\n}\n\n/*==========================================\n * PUSHING AN ORDER\n *=========================================*/\n\n/** What one push came to. A transient failure throws `ShippingEasyUnavailableError` instead. */\nexport type ShippingEasyPushOutcome =\n | { result: 'not_connected' | 'skipped' | 'already' | 'sent' | 'canceled' }\n | { result: 'refused'; message: string }\n | { result: 'failed'; message: string }\n\nfunction connectionRef(hostId: string): FirebaseFirestore.DocumentReference {\n return firebaseAdmin.app().firestore().collection(SHIPPINGEASY_CONNECTIONS).doc(hostId)\n}\n\n/** Records the last error, or clears it after a success, on the connection. Never throws. */\nasync function noteOnConnection(\n hostId: string,\n patch: Partial<Pick<ShippingEasyConnection, 'lastPushAtMs' | 'lastCallbackAtMs' | 'lastError'>>,\n): Promise<void> {\n await connectionRef(hostId)\n .set(patch, { merge: true })\n .catch((error: unknown) => console.warn('shippingeasy: connection not stamped', hostId, error))\n}\n\ntype Claim =\n | { go: true; record: ShippingEasyOrderRecord | null }\n | { go: false; outcome: ShippingEasyPushOutcome }\n\n/**\n * Sends one order to ShippingEasy as it stands now, or cancels it there.\n * Safe to call any number of times for the same order: see the block header.\n */\nexport async function pushShippingEasyOrder(\n hostId: string,\n orderId: string,\n deps: ShippingEasyDeps = {},\n): Promise<ShippingEasyPushOutcome> {\n const now = deps.now ?? Date.now\n const firestore = firebaseAdmin.app().firestore()\n const stored = await connectionRef(hostId).get()\n if (!stored.exists) return { result: 'not_connected' }\n const connection = stored.data() as ShippingEasyConnection\n const orderSnapshot = await firestore.collection('hosts').doc(hostId).collection('orders').doc(orderId).get()\n if (!orderSnapshot.exists) return { result: 'skipped' }\n const order = (orderSnapshot.data() ?? {}) as Partial<HostOrder> & { createdAtMs?: number; updatedAtMs?: number }\n const intent = shippingEasyIntent(order, orderId)\n if (!intent) return { result: 'skipped' }\n\n const refused = await connectorSiteRefusal({ method: 'POST' }, hostId)\n if (refused) return { result: 'refused', message: (await refused.text()).slice(0, 300) }\n\n const credentials = shippingEasyCredentialsOf(connection, readCommerceSecretKeyring())\n const externalId = shippingEasyExternalId(order, orderId)\n if (!credentials) {\n const message = 'The ShippingEasy API secret cannot be opened. Enter your keys again.'\n await noteOnConnection(hostId, { lastError: { message, atMs: now(), orderNumber: externalId } })\n return { result: 'failed', message }\n }\n\n const recordRef = connectionRef(hostId).collection(SHIPPINGEASY_ORDER_RECORDS).doc(orderId)\n const claim: Claim = await firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(recordRef)\n const record = snapshot.exists ? (snapshot.data() as ShippingEasyOrderRecord) : null\n const atMs = now()\n const inFlight =\n (record?.state === 'sending' || record?.state === 'canceling') &&\n atMs - Number(record.claimedAtMs ?? 0) < SHIPPINGEASY_CLAIM_MS\n if (inFlight) throw new ShippingEasyUnavailableError('This order is being sent to ShippingEasy already')\n if (intent === 'create') {\n if (record && record.state !== 'sending' && record.state !== 'failed') {\n return { go: false, outcome: { result: 'already' } }\n }\n transaction.set(recordRef, {\n orderId,\n externalId: record?.externalId ?? externalId,\n state: 'sending',\n claimedAtMs: atMs,\n updatedAtMs: atMs,\n } satisfies ShippingEasyOrderRecord)\n return { go: true, record }\n }\n // Cancel: only what was sent there. An order that never arrived has\n // nothing to cancel, and one canceled already is answered as such.\n if (!record || record.state === 'failed' || record.state === 'sending') return { go: false, outcome: { result: 'skipped' } }\n if (record.state === 'canceled') return { go: false, outcome: { result: 'already' } }\n transaction.set(recordRef, { ...record, state: 'canceling', claimedAtMs: atMs, updatedAtMs: atMs })\n return { go: true, record }\n })\n if ('outcome' in claim) return claim.outcome\n\n const sentId = claim.record?.externalId ?? externalId\n const settle = async (state: ShippingEasyOrderState, extra: Partial<ShippingEasyOrderRecord> = {}) => {\n await recordRef.set({ state, updatedAtMs: now(), ...extra }, { merge: true })\n }\n\n if (intent === 'cancel') {\n let response: { status: number; text: string }\n try {\n response = await shippingEasyRequest(\n credentials,\n { method: 'POST', path: `${storePath(credentials)}/${encodeURIComponent(sentId)}/cancellations` },\n deps,\n )\n } catch (error) {\n await settle('sent')\n throw error\n }\n if (transient(response.status)) {\n await settle('sent')\n throw new ShippingEasyUnavailableError(shippingEasyRefusalMessage(response))\n }\n if (response.status >= 200 && response.status < 300) {\n await settle('canceled', { canceledAtMs: now(), error: null })\n await noteOnConnection(hostId, { lastPushAtMs: now(), lastError: null })\n return { result: 'canceled' }\n }\n const message = `Order ${sentId} could not be canceled in ShippingEasy: ${shippingEasyRefusalMessage(response)}`\n await settle('cancel_failed', { error: message })\n await noteOnConnection(hostId, { lastError: { message, atMs: now(), orderNumber: sentId } })\n return { result: 'failed', message }\n }\n\n const lookUp = async (): Promise<boolean> => {\n const found = await shippingEasyRequest(\n credentials,\n { method: 'GET', path: `${storePath(credentials)}/${encodeURIComponent(sentId)}` },\n deps,\n ).catch(() => null)\n return Boolean(found && found.status >= 200 && found.status < 300)\n }\n const markSent = async (): Promise<ShippingEasyPushOutcome> => {\n await settle('sent', { sentAtMs: now(), error: null })\n await noteOnConnection(hostId, { lastPushAtMs: now(), lastError: null })\n return { result: 'sent' }\n }\n // A claim that lapsed mid-call may have landed: look before creating\n // again, so ShippingEasy never holds the order twice.\n if (claim.record?.state === 'sending' && (await lookUp())) return markSent()\n\n const hostRef = firestore.collection('hosts').doc(hostId)\n const products = await shipStationProductsFor(hostRef, [order])\n const payload = shippingEasyOrderPayload({ docId: orderId, order }, products)\n // A network failure throws past here with the claim left to lapse rather\n // than released: the call may have landed, and the retry's refused create\n // looks the order up before calling it failed.\n const response = await shippingEasyRequest(credentials, { method: 'POST', path: storePath(credentials), body: payload }, deps)\n if (transient(response.status)) {\n await settle('failed', { error: shippingEasyRefusalMessage(response) })\n throw new ShippingEasyUnavailableError(shippingEasyRefusalMessage(response))\n }\n if (response.status >= 200 && response.status < 300) return markSent()\n // A refused create may be an order that arrived on an earlier try whose\n // answer was lost: ask for it by its identifier before calling it failed.\n if (response.status !== 401 && response.status !== 403 && (await lookUp())) return markSent()\n const message =\n response.status === 401 || response.status === 403\n ? 'ShippingEasy refused the API key, secret or store API key. Enter them again.'\n : `Order ${sentId} was refused by ShippingEasy: ${shippingEasyRefusalMessage(response)}`\n await settle('failed', { error: message })\n await noteOnConnection(hostId, { lastError: { message, atMs: now(), orderNumber: sentId } })\n return { result: 'failed', message }\n}\n\n/**\n * The `shippingeasy` subscriber to commerce's order events. Throws on a\n * failure that may pass, so the outbox retries; on its last attempt the\n * site's managers are told and the card shows why.\n */\nexport async function deliverOrderEventToShippingEasy(\n envelope: Pick<PluginDomainEventEnvelope, 'hostId' | 'payload' | 'attempt' | 'event'>,\n deps: ShippingEasyDeps = {},\n): Promise<ShippingEasyPushOutcome | null> {\n const orderId = String(((envelope.payload ?? {}) as { order?: { id?: unknown } }).order?.id ?? '')\n if (!orderId || orderId.includes('/')) return null\n try {\n return await pushShippingEasyOrder(envelope.hostId, orderId, deps)\n } catch (error) {\n if (!(error instanceof ShippingEasyUnavailableError) || envelope.attempt < PLUGIN_EVENT_MAX_ATTEMPTS) throw error\n const message = `${error.message}. Select Send open orders on the ShippingEasy card to try again.`\n await noteOnConnection(envelope.hostId, { lastError: { message, atMs: (deps.now ?? Date.now)() } })\n await notifyHostManagers(envelope.hostId, {\n type: 'content.order',\n title: 'An order could not be sent to ShippingEasy',\n body: `An order on {site} never reached ShippingEasy after ${envelope.attempt} attempts (${error.message}).`,\n link: `/${envelope.hostId}/products/shipping`,\n }).catch(() => undefined)\n return { result: 'failed', message }\n }\n}\n\n/** What \"Send open orders\" came to. */\nexport interface ShippingEasySyncResult {\n sent: number\n already: number\n failed: number\n /** Whether more open orders are left past this pass's limit. */\n more: boolean\n}\n\n/**\n * Pushes the site's open orders from the last 90 days, a pass at a time: the\n * orders paid before the connection, or while it was failing. One indexed\n * query (`requiresShipping ==`, `updatedAtMs` range) — the same index the\n * ShipStation feed reads.\n */\nexport async function syncShippingEasyOrders(hostId: string, deps: ShippingEasyDeps = {}): Promise<ShippingEasySyncResult> {\n const now = deps.now ?? Date.now\n const orders = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('orders')\n .where('requiresShipping', '==', true)\n .where('updatedAtMs', '>=', now() - SHIPPINGEASY_SYNC_WINDOW_MS)\n .orderBy('updatedAtMs', 'asc')\n .limit(500)\n .get()\n const open = orders.docs.filter((doc) => shippingEasyIntent((doc.data() ?? {}) as never, doc.id) === 'create')\n const result: ShippingEasySyncResult = { sent: 0, already: 0, failed: 0, more: open.length > SHIPPINGEASY_SYNC_LIMIT }\n for (const doc of open.slice(0, SHIPPINGEASY_SYNC_LIMIT)) {\n try {\n const outcome = await pushShippingEasyOrder(hostId, doc.id, deps)\n if (outcome.result === 'sent') result.sent += 1\n else if (outcome.result === 'already') result.already += 1\n else if (outcome.result === 'failed' || outcome.result === 'refused') {\n result.failed += 1\n // Bad keys or a refused site fail every order alike: stop asking.\n if (outcome.result === 'refused' || /refused the API key/.test(outcome.message)) break\n }\n } catch (error) {\n result.failed += 1\n if (error instanceof ShippingEasyUnavailableError) continue\n throw error\n }\n }\n return result\n}\n\n/*==========================================\n * THE CALLBACK\n *=========================================*/\n\n/**\n * Whether a callback was signed with this secret: the HMAC of its method,\n * path, query (without `api_signature`) and raw body, compared in constant\n * time; and, when it carries `api_timestamp`, sent within the hour.\n */\nexport function verifyShippingEasyCallback(input: {\n apiSecret: string\n method: string\n path: string\n query: URLSearchParams\n body: string\n nowMs: number\n}): 'ok' | 'unsigned' | 'bad_signature' | 'expired' {\n const presented = String(input.query.get('api_signature') ?? '').trim().toLowerCase()\n if (!/^[0-9a-f]{64}$/.test(presented)) return 'unsigned'\n const params: Record<string, string> = {}\n for (const [name, value] of input.query.entries()) if (name !== 'api_signature') params[name] = value\n const expected = signShippingEasy(\n input.apiSecret,\n shippingEasySignaturePlaintext({ method: input.method, path: input.path, params, body: input.body }),\n )\n if (!timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(presented, 'hex'))) return 'bad_signature'\n if (params['api_timestamp'] !== undefined) {\n const seconds = Number(params['api_timestamp'])\n if (!Number.isFinite(seconds) || Math.abs(input.nowMs - seconds * 1000) > SHIPPINGEASY_TIMESTAMP_TOLERANCE_MS) return 'expired'\n }\n return 'ok'\n}\n\n/** `POST /api/commerce/shippingeasy/{hostId}` — ShippingEasy's shipment notification. See the block header. */\nexport async function shippingEasyRoute(\n request: Request,\n context: { params: Record<string, string | string[]> },\n deps: ShippingEasyDeps = {},\n): Promise<Response> {\n const now = deps.now ?? Date.now\n const hostId = connectorHostIdOf(context.params)\n if (!hostId) return text(404, 'Not found')\n if (request.method !== 'POST') return text(405, 'ShippingEasy posts shipments here.', { allow: 'POST' })\n if (!requestIsSecure(request, deps.env)) return text(403, `Use the https:// address ${PLATFORM_BRAND_NAME} gave you.`)\n\n const ip = readClientIp(request.headers) ?? 'unknown'\n const rate = await consumeRateLimit(`shippingeasy:${hostId}:${ip}`, { limit: SHIPPINGEASY_RATE_LIMIT, windowMs: 60_000 })\n if (!rate.allowed) {\n return text(429, 'Too many requests; ShippingEasy will retry.', {\n 'retry-after': String(Math.max(1, Math.ceil((rate.resetMs - now()) / 1000))),\n })\n }\n\n const declared = Number(request.headers.get('content-length') ?? 0)\n if (declared > SHIPPINGEASY_CALLBACK_MAX_BYTES) return text(413, 'The callback is too large.')\n const body = await request.text()\n if (Buffer.byteLength(body, 'utf8') > SHIPPINGEASY_CALLBACK_MAX_BYTES) return text(413, 'The callback is too large.')\n\n const ref = connectionRef(hostId)\n const stored = await ref.get()\n const connection = stored.exists ? (stored.data() as ShippingEasyConnection) : null\n const keyring = readCommerceSecretKeyring()\n const opened = openShippingEasySecret(connection, keyring)\n if (connection && !opened) console.error('shippingeasy: the stored API secret could not be opened', hostId)\n const url = new URL(request.url)\n // An unknown site is answered exactly like a wrong signature: an unproven\n // caller learns nothing about which sites are connected.\n const verdict = opened\n ? verifyShippingEasyCallback({ apiSecret: opened.apiSecret, method: request.method, path: url.pathname, query: url.searchParams, body, nowMs: now() })\n : 'bad_signature'\n if (verdict !== 'ok') {\n return text(401, verdict === 'expired' ? 'The callback is too old.' : `The signature does not match the API secret saved in ${PLATFORM_BRAND_NAME}.`)\n }\n if (opened?.reseal && keyring) {\n await ref\n .set(sealShippingEasySecret(hostId, opened.apiSecret, keyring), { merge: true })\n .catch((error: unknown) => console.warn('shippingeasy: secret not resealed', hostId, error))\n }\n\n const refused = await connectorSiteRefusal(request, hostId)\n if (refused) return refused\n\n const callback = readShippingEasyCallback(body)\n if ('problem' in callback) return text(400, callback.problem)\n await noteOnConnection(hostId, { lastCallbackAtMs: now() })\n if (!SHIPPINGEASY_SHIPPED_STATES.has(callback.workflowState)) {\n return text(200, `Nothing recorded for a ${callback.workflowState || 'shipment'} label.`)\n }\n const digest = digestSecret(body).slice(0, 32)\n const problems: string[] = []\n try {\n for (const notice of callback.notices) {\n const outcome = await recordShipNotice(hostId, notice, digest, { source: 'shippingeasy' })\n if (outcome.status !== 200) problems.push(outcome.body)\n }\n } catch (error) {\n console.error('shippingeasy: callback failed', hostId, error)\n return text(500, `${PLATFORM_BRAND_NAME} could not record the shipment. ShippingEasy will retry.`)\n }\n // A notice for an order this site does not have, or one already canceled,\n // will not record on a retry either: it is answered 200 so ShippingEasy\n // stops sending it, and the card says what happened.\n if (problems.length) {\n await noteOnConnection(hostId, { lastError: { message: problems.join(' ').slice(0, 300), atMs: now() } })\n }\n return text(200, problems.length ? problems.join(' ') : 'Recorded.')\n}\n\n/*==========================================\n * THE CONSOLE CARD\n *=========================================*/\n\n/** What the card shows about a connection. Never a key in full, never the secret. */\nexport interface ShippingEasyConnectionStatus {\n /** Whether this deployment can seal a secret at all. */\n available: boolean\n connected: boolean\n apiKeyEnding?: string\n storeApiKeyEnding?: string\n createdAtMs?: number\n updatedAtMs?: number\n lastPushAtMs?: number\n lastCallbackAtMs?: number\n lastError?: { message: string; atMs: number; orderNumber?: string } | null\n}\n\nconst ending = (key: string) => key.slice(-4)\n\nexport function shippingEasyStatusOf(connection: ShippingEasyConnection | null, available: boolean): ShippingEasyConnectionStatus {\n if (!connection) return { available, connected: false }\n return {\n available,\n connected: true,\n apiKeyEnding: ending(connection.apiKey),\n storeApiKeyEnding: ending(connection.storeApiKey),\n createdAtMs: connection.createdAtMs,\n ...(connection.updatedAtMs ? { updatedAtMs: connection.updatedAtMs } : {}),\n ...(connection.lastPushAtMs ? { lastPushAtMs: connection.lastPushAtMs } : {}),\n ...(connection.lastCallbackAtMs ? { lastCallbackAtMs: connection.lastCallbackAtMs } : {}),\n ...(connection.lastError ? { lastError: connection.lastError } : {}),\n }\n}\n\n/** The card's actions. `status` is the GET. */\nexport const SHIPPINGEASY_ACTIONS = ['status', 'connect', 'sync', 'disconnect'] as const\nexport type ShippingEasyAction = (typeof SHIPPINGEASY_ACTIONS)[number]\n\nconst ACTIVITY = { type: 'commerce:shippingEasy', id: 'shippingeasy', name: 'ShippingEasy' } as const\n\n/**\n * One action of the ShippingEasy card, for a caller the connectors route has\n * already proven a site admin (or, for `status`, an editor) on a site with\n * commerce. Answers the status and body the route sends.\n *\n * - `connect` saves the three keys after one read proves them, sealing the\n * secret; on a site already connected it replaces them (a merchant who\n * made a new secret in ShippingEasy enters it here).\n * - `sync` pushes the open orders, a pass at a time.\n * - `disconnect` deletes the connection; the per-order records go with it,\n * so a later connect sends the open orders afresh.\n */\nexport async function shippingEasyConnectorAction(\n input: {\n hostId: string\n action: ShippingEasyAction\n body: Record<string, unknown>\n actor: { uid: string; email: string | null }\n log: (action: string, target: typeof ACTIVITY) => Promise<void>\n prepareOpenOrders?: (hostId: string) => Promise<unknown>\n },\n deps: ShippingEasyDeps = {},\n): Promise<{ status: number; body: Record<string, unknown> }> {\n const { hostId, action, body } = input\n const now = deps.now ?? Date.now\n const keyring = readCommerceSecretKeyring()\n const available = keyring !== null\n const ref = connectionRef(hostId)\n const read = async () => {\n const doc = await ref.get()\n return doc.exists ? (doc.data() as ShippingEasyConnection) : null\n }\n\n if (action === 'status') return { status: 200, body: { ...shippingEasyStatusOf(await read(), available) } }\n\n if (action === 'disconnect') {\n if (!(await read())) return { status: 200, body: { available, connected: false } }\n await firebaseAdmin.app().firestore().recursiveDelete(ref)\n await input.log('Disconnected ShippingEasy', ACTIVITY)\n return { status: 200, body: { available, connected: false } }\n }\n\n if (!keyring) return { status: 503, body: { error: 'ShippingEasy cannot be connected on this deployment yet' } }\n\n if (action === 'sync') {\n const connection = await read()\n if (!connection) return { status: 409, body: { error: 'ShippingEasy is not connected.' } }\n const result = await syncShippingEasyOrders(hostId, deps)\n await input.log('Sent open orders to ShippingEasy', ACTIVITY)\n return { status: 200, body: { ...shippingEasyStatusOf(await read(), available), sync: { ...result } } }\n }\n\n const keys = {\n apiKey: String(body['apiKey'] ?? '').trim(),\n apiSecret: String(body['apiSecret'] ?? '').trim(),\n storeApiKey: String(body['storeApiKey'] ?? '').trim(),\n }\n if (!SHIPPINGEASY_KEY.test(keys.apiKey) || !SHIPPINGEASY_KEY.test(keys.apiSecret) || !SHIPPINGEASY_KEY.test(keys.storeApiKey)) {\n return { status: 400, body: { error: 'Paste the API key, API secret and store API key exactly as ShippingEasy shows them.' } }\n }\n const check = await checkShippingEasyCredentials(keys, deps)\n if ('reason' in check) {\n return check.reason === 'refused'\n ? { status: 422, body: { error: `ShippingEasy did not accept these keys (${check.message}). Check all three and try again.` } }\n : { status: 502, body: { error: 'ShippingEasy could not be reached. Try again in a minute.' } }\n }\n const atMs = now()\n const existing = await read()\n const connection: ShippingEasyConnection = {\n ...(existing ?? { createdAtMs: atMs, createdBy: input.actor.uid }),\n hostId,\n apiKey: keys.apiKey,\n storeApiKey: keys.storeApiKey,\n ...sealShippingEasySecret(hostId, keys.apiSecret, keyring),\n ...(existing ? { updatedAtMs: atMs } : {}),\n lastError: null,\n }\n await ref.set(connection)\n if (!existing && input.prepareOpenOrders) {\n await input.prepareOpenOrders(hostId).catch((error: unknown) =>\n console.warn('shippingeasy: open orders not stamped', hostId, error),\n )\n }\n await input.log(existing ? 'Replaced the ShippingEasy keys' : 'Connected ShippingEasy', ACTIVITY)\n return { status: 200, body: { ...shippingEasyStatusOf(connection, available) } }\n}\n"],"names":["createHmac","timingSafeEqual","PLATFORM_BRAND_NAME","readClientIp","needsReseal","openSecret","sealSecret","consumeRateLimit","firebaseAdmin","notifyHostManagers","PLUGIN_EVENT_MAX_ATTEMPTS","readShippingEasyCallback","SHIPPINGEASY_DEFAULT_BASE_URL","SHIPPINGEASY_SHIPPED_STATES","SHIPPINGEASY_TIMESTAMP_TOLERANCE_MS","shippingEasyCanonicalQuery","shippingEasyExternalId","shippingEasyIntent","shippingEasyOrderPayload","shippingEasySignaturePlaintext","readCommerceSecretKeyring","connectorHostIdOf","connectorSiteRefusal","connectorText","text","digestSecret","requestIsSecure","recordShipNotice","shipStationProductsFor","SHIPPINGEASY_CONNECTIONS","SHIPPINGEASY_ORDER_RECORDS","SHIPPINGEASY_RATE_LIMIT","SHIPPINGEASY_CALLBACK_MAX_BYTES","SHIPPINGEASY_CLAIM_MS","SHIPPINGEASY_REQUEST_TIMEOUT_MS","SHIPPINGEASY_SYNC_LIMIT","SHIPPINGEASY_SYNC_WINDOW_MS","SHIPPINGEASY_BASE_URL_ENV","ShippingEasyUnavailableError","Error","message","name","SHIPPINGEASY_KEY","shippingEasySealContext","hostId","sealShippingEasySecret","apiSecret","keyring","sealedApiSecret","current","context","secretKeyId","id","openShippingEasySecret","connection","opened","plaintext","reseal","shippingEasyCredentialsOf","apiKey","storeApiKey","signShippingEasy","update","digest","shippingEasyBaseUrl","env","process","configured","String","trim","url","URL","protocol","origin","shippingEasyRequest","credentials","input","deps","now","Date","body","undefined","JSON","stringify","params","query","api_key","api_timestamp","Math","floor","signature","method","path","fetchImpl","fetch","response","headers","accept","redirect","signal","AbortSignal","timeout","status","catch","slice","error","shippingEasyRefusalMessage","parsed","fallback","parse","errors","Array","isArray","map","entry","join","storePath","encodeURIComponent","transient","checkShippingEasyCredentials","per_page","ok","reason","connectionRef","app","firestore","collection","doc","noteOnConnection","patch","set","merge","console","warn","pushShippingEasyOrder","orderId","orderSnapshot","claim","stored","get","exists","result","data","order","intent","refused","externalId","lastError","atMs","orderNumber","recordRef","runTransaction","transaction","record","snapshot","inFlight","state","Number","claimedAtMs","go","outcome","updatedAtMs","sentId","settle","extra","canceledAtMs","lastPushAtMs","lookUp","found","Boolean","markSent","sentAtMs","hostRef","products","payload","docId","deliverOrderEventToShippingEasy","envelope","includes","attempt","type","title","link","syncShippingEasyOrders","orders","where","orderBy","limit","open","docs","filter","sent","already","failed","more","length","test","verifyShippingEasyCallback","presented","toLowerCase","value","entries","expected","Buffer","from","seconds","isFinite","abs","nowMs","shippingEasyRoute","request","allow","ip","rate","windowMs","allowed","max","ceil","resetMs","declared","byteLength","ref","verdict","pathname","searchParams","callback","problem","lastCallbackAtMs","has","workflowState","problems","notice","notices","source","push","ending","key","shippingEasyStatusOf","available","connected","apiKeyEnding","storeApiKeyEnding","createdAtMs","SHIPPINGEASY_ACTIONS","ACTIVITY","shippingEasyConnectorAction","action","read","recursiveDelete","log","sync","keys","check","existing","createdBy","actor","uid","prepareOpenOrders"],"mappings":"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"}
|
|
@@ -49,7 +49,15 @@ import { STORE_SETUP_COUNTRIES_MAX, STORE_SETUP_LOCATION_MAX, STORE_SETUP_MAX_MI
|
|
|
49
49
|
export const STORE_PICKUP_AI_OP = 'store-pickup';
|
|
50
50
|
export const STORE_DELIVERY_AI_OP = 'store-delivery';
|
|
51
51
|
export const STORE_TAX_AI_OP = 'store-tax';
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Where a person finds what the build set: the settings page each section's
|
|
54
|
+
* card is on (AGL-3713). Pickup is per location, on Shipping & delivery.
|
|
55
|
+
*/ const WHERE = {
|
|
56
|
+
shipping: 'Products → Shipping & delivery',
|
|
57
|
+
pickup: 'Products → Shipping & delivery',
|
|
58
|
+
'local-delivery': 'Products → Shipping & delivery',
|
|
59
|
+
tax: 'Products → Taxes'
|
|
60
|
+
};
|
|
53
61
|
/** The writer's content for an item: its arguments, tagged with the section its op changes. */ export function storeSetupContentFromArgs(section, args) {
|
|
54
62
|
const content = {
|
|
55
63
|
section
|
|
@@ -64,7 +72,7 @@ const STATED = 'ONLY when the request states it; never a typical or suggested am
|
|
|
64
72
|
/** What every store-setup operation shares. */ function storeSetupCapability(op, section, rest) {
|
|
65
73
|
return _extends({
|
|
66
74
|
op,
|
|
67
|
-
where: WHERE
|
|
75
|
+
where: WHERE[section]
|
|
68
76
|
}, rest, {
|
|
69
77
|
// Free includes no commerce.
|
|
70
78
|
freeAllowed: false,
|