@aglyn/plugins-commerce 1.0.0-beta.261 → 1.0.0-beta.263
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/console/order-detail-dialog.component.js +17 -45
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-fulfillment-panel.component.js +12 -25
- package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -1
- package/src/lib/components/console/order-invoice.component.js +2 -10
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/order-shipping-actions.component.d.ts +1 -15
- package/src/lib/components/console/order-shipping-actions.component.js +3 -23
- package/src/lib/components/console/order-shipping-actions.component.js.map +1 -1
- package/src/lib/components/console/product-labels-dialog.component.d.ts +2 -5
- package/src/lib/components/console/product-labels-dialog.component.js +6 -24
- package/src/lib/components/console/product-labels-dialog.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +7 -20
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/model/commerce-shipping.d.ts +2 -0
- package/src/lib/model/commerce-shipping.js +1 -0
- package/src/lib/model/commerce-shipping.js.map +1 -1
- package/src/lib/model/order-dialog.d.ts +52 -0
- package/src/lib/model/order-dialog.js +85 -0
- package/src/lib/model/order-dialog.js.map +1 -0
- package/src/lib/model/product-write.d.ts +71 -0
- package/src/lib/model/product-write.js +28 -0
- package/src/lib/model/product-write.js.map +1 -1
- package/src/lib/printing/product-labels.d.ts +22 -0
- package/src/lib/printing/product-labels.js +39 -2
- package/src/lib/printing/product-labels.js.map +1 -1
- package/src/lib/server/billing-webhook.js +3 -3
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/order-figures.js +37 -0
- package/src/lib/server/order-figures.js.map +1 -1
- package/src/lib/server/order-print.d.ts +40 -0
- package/src/lib/server/order-print.js +113 -0
- package/src/lib/server/order-print.js.map +1 -0
- package/src/lib/server/products-write.d.ts +26 -0
- package/src/lib/server/products-write.js +184 -1
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server.js +8 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/transfer/shipping-presets.d.ts +16 -0
- package/src/lib/transfer/shipping-presets.js +34 -1
- package/src/lib/transfer/shipping-presets.js.map +1 -1
- package/src/lib/utils/order-invoice.d.ts +5 -0
- package/src/lib/utils/order-invoice.js +14 -0
- package/src/lib/utils/order-invoice.js.map +1 -1
- package/src/lib/utils/order-packing-slip.d.ts +31 -0
- package/src/lib/utils/order-packing-slip.js +46 -0
- package/src/lib/utils/order-packing-slip.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/products-write.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 { createHash } from 'node:crypto'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getLockdownVerdict, getOrgForHost, isImpersonationSession } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n type EditedProduct,\n productCollectionIds,\n productEditWrite,\n productMembershipInput,\n productSaveFields,\n smartCollectionRulesOf,\n stockAdjustmentWrite,\n validateProduct,\n type InventoryAdjustmentReason,\n} from '../model'\nimport { planRefusal, roomRefusal } from './product-drafts'\n\n/*\n * `POST /api/commerce/products/save` and `POST /api/commerce/products/stock`\n * (AGL-3652): the console product editor's save and its Adjust stock dialog,\n * for the native apps.\n *\n * The console makes both writes from the browser, computing derived fields\n * first (search keys, stock verdict, price, image, smart-collection\n * membership). These routes run THAT computation (`model/product-write.ts`,\n * which the console imports too) and make the same writes, under the gate\n * the Firestore rules put on them: a verified member with a write role on the\n * site (`canWriteHostContent`), or staff, while the site, its org and the\n * platform are not frozen (`hostWritesFrozen`). A new product also meets the\n * create path's own door: the plan's `commerce` entitlement and its\n * `productsPerHost` quota, counted in the same transaction as the create.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const ROLE_REFUSAL = 'Editing requires the editor role'\nconst MAX_BODY_BYTES = 256_000\n\n/** The reasons the Adjust stock dialog offers. */\nexport const STOCK_ADJUST_REASONS: readonly InventoryAdjustmentReason[] = ['restock', 'correction', 'damage', 'refund']\n\nexport interface ProductsWriteDeps {\n firestore?: () => Firestore\n verifyIdToken?: (token: string) => Promise<Record<string, unknown> & { uid: string }>\n orgForHost?: (hostId: string) => Promise<{ org: Record<string, unknown> } | null>\n lockdown?: (options: Parameters<typeof getLockdownVerdict>[0]) => Promise<unknown>\n dropCache?: typeof dropPluginSiteCache\n now?: () => number\n}\n\ntype Reply = Parameters<PluginApiHandler>[1]\n\nexport function bodyOf(req: Parameters<PluginApiHandler>[0]): Record<string, unknown> {\n const raw = req.body\n if (typeof raw === 'string') {\n if (raw.length > MAX_BODY_BYTES) return {}\n try {\n return JSON.parse(raw || '{}')\n } catch {\n return {}\n }\n }\n return (raw ?? {}) as Record<string, unknown>\n}\n\nexport const validId = (value: unknown): value is string =>\n typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value)\n\nexport interface Gate {\n uid: string\n staff: boolean\n host: FirebaseFirestore.DocumentSnapshot\n org: Record<string, unknown>\n}\n\n/**\n * Who may write this site's products, as the rules decide it. Answers the\n * caller's reply on a refusal and returns null.\n */\nexport async function gate(req: Parameters<PluginApiHandler>[0], res: Reply, hostId: string, deps: Required<ProductsWriteDeps>): Promise<Gate | null> {\n const authorization = String(req.headers.authorization ?? '')\n const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!token) return void res.status(401).json({ error: 'Unauthenticated' }), null\n let decoded: Record<string, unknown> & { uid: string }\n try {\n decoded = await deps.verifyIdToken(token)\n } catch (error) {\n // Only a refused token is the caller's; an Auth outage is a 500 (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return void res.status(401).json({ error: 'Sign in again to continue' }), null\n }\n const staff = decoded['staff'] === true\n if (!staff && decoded['email_verified'] !== true && !isImpersonationSession(decoded as never)) {\n return void res.status(403).json({ error: 'Verify your email address to make changes' }), null\n }\n const host = await deps.firestore().collection('hosts').doc(hostId).get()\n if (!host.exists) return void res.status(404).json({ error: 'Unknown site' }), null\n if (!staff && !hostRoleCanWrite((host.get('memberRoles') ?? {})[decoded.uid])) {\n return void res.status(403).json({ error: ROLE_REFUSAL }), null\n }\n const org = ((await deps.orgForHost(hostId))?.org ?? {}) as Record<string, unknown>\n const locked = await deps.lockdown({ staff, uid: decoded.uid, org: org as never, host: host.data() as never, request: { method: 'POST' } as never })\n if (locked) return void res.status(423).json({ error: 'This site is not accepting changes right now' }), null\n return { uid: decoded.uid, staff, host, org }\n}\n\n/** The site-wide cache drop a product change is owed (AGL-3386): an outbox entry in the write, then the drop. */\nexport function outboxEntry(firestore: Firestore, hostId: string) {\n return {\n ref: firestore.collection('publishOutbox').doc(createResourceUid()),\n data: { hostId, paths: ['/'], createdAt: FieldValue.serverTimestamp(), attempts: 0, entireHost: true },\n }\n}\n\nexport async function settleCache(deps: Required<ProductsWriteDeps>, hostId: string, entry: FirebaseFirestore.DocumentReference, reason: string) {\n const dropped = await deps.dropCache({ hostIds: [hostId], reason }).catch(() => ({ complete: false }))\n if (dropped.complete) await entry.delete().catch(() => undefined)\n}\n\nexport function resolved(deps: ProductsWriteDeps): Required<ProductsWriteDeps> {\n return {\n firestore: deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore),\n verifyIdToken: deps.verifyIdToken ?? ((token) => firebaseAdmin.app().auth().verifyIdToken(token) as never),\n orgForHost: deps.orgForHost ?? ((hostId) => getOrgForHost(hostId) as never),\n lockdown: deps.lockdown ?? ((options) => getLockdownVerdict(options)),\n dropCache: deps.dropCache ?? dropPluginSiteCache,\n now: deps.now ?? (() => Date.now()),\n }\n}\n\n/**\n * Saves a product as the console editor does. `create: true` makes a new\n * product under the client-minted `productId` (a retry finds its own product\n * and answers `replayed`); otherwise it replaces the live product, keeping\n * its live stock holds.\n */\nexport function createProductSaveHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const product = body['product'] as EditedProduct | undefined\n if (!validId(hostId) || !validId(productId) || !product || typeof product !== 'object' || !Array.isArray(product.variants)) {\n return res.status(400).json({ error: 'Missing site, product or its variants' })\n }\n if (typeof product.name !== 'string' || !product.name.trim()) return res.status(400).json({ error: 'Product name is required' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const nowMs = deps.now()\n const fields = productSaveFields(product, nowMs)\n const problem = validateProduct(fields as never)\n if (problem) return res.status(400).json({ error: problem })\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const productRef = products.doc(productId)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const create = body['create'] === true\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(productRef), tx.get(smartQuery)])\n const membership = {\n collectionIds: productCollectionIds(\n productMembershipInput(fields, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n ),\n }\n if (create) {\n if (existing.exists) return { status: 200, body: { ok: true, id: productId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = (await tx.get(products.count())).data().count\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n tx.create(productRef, {\n ...fields,\n ...membership,\n createdAtMs: nowMs,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n } else {\n if (!existing.exists || existing.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n tx.set(productRef, {\n ...storedTimestamps(existing.data() ?? {}, product as unknown as Record<string, unknown>),\n ...productEditWrite(fields, existing.get('stockHolds')),\n ...membership,\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: productId, replayed: false } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product saved from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/save]', error)\n return res.status(500).json({ error: 'The product could not be saved' })\n }\n }\n}\n\n/**\n * The stored timestamps an edit keeps. The console's replace writes back the\n * Timestamps its seed carried (`createdAt`, a publish time); a client's JSON\n * cannot carry one, so each stored Timestamp field the request does not name\n * is kept as stored.\n */\nexport function storedTimestamps(stored: Record<string, unknown>, asked: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(stored).filter(\n ([key, value]) => !(key in asked) && typeof (value as { toDate?: unknown } | null)?.toDate === 'function',\n ),\n )\n}\n\n/** The ledger row id one attempt owns: the same key from the same member is the same row. */\nexport function stockAttemptId(uid: string, key: string): string {\n return 'app-' + createHash('sha256').update(`${uid}:${key}`).digest('hex').slice(0, 40)\n}\n\n/**\n * Adjusts one variant's stock as the Adjust stock dialog does: the variants\n * and stock fields on the product, and one `inventoryAdjustments` row, in one\n * transaction. The Idempotency-Key names that row, so a retried attempt finds\n * it and moves nothing twice.\n */\nexport function createProductStockHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const variantId = body['variantId']\n const key = String(req.headers['idempotency-key'] ?? '').trim().slice(0, 200)\n const reason = body['reason'] as InventoryAdjustmentReason\n const delta = Math.round(Number(body['delta']))\n const locationId = typeof body['locationId'] === 'string' && body['locationId'] ? body['locationId'] : undefined\n if (!validId(hostId) || !validId(productId) || typeof variantId !== 'string' || !variantId) {\n return res.status(400).json({ error: 'Missing site, product or variant' })\n }\n if (!key) return res.status(400).json({ error: 'Missing Idempotency-Key' })\n if (!STOCK_ADJUST_REASONS.includes(reason)) return res.status(400).json({ error: 'Unknown reason' })\n if (!Number.isFinite(delta) || delta === 0) return res.status(400).json({ error: 'Enter a number of units' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const productRef = hostRef.collection('products').doc(productId)\n const ledgerRef = hostRef.collection('inventoryAdjustments').doc(stockAttemptId(door.uid, key))\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [ledger, stored] = await Promise.all([tx.get(ledgerRef), tx.get(productRef)])\n if (ledger.exists) {\n return { status: 200, body: { ok: true, replayed: true, inventory: stored.get('inventory') ?? null } }\n }\n if (!stored.exists || stored.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n const product = stored.data() as never as Parameters<typeof stockAdjustmentWrite>[0]\n if (!(product.variants ?? []).some((variant) => variant.id === variantId)) {\n return { status: 404, body: { error: 'That variant is no longer on this product' } }\n }\n const write = stockAdjustmentWrite(product, productId, { variantId, delta, reason, locationId }, deps.now())\n if (!write) return { status: 400, body: { error: 'Enter a number of units' } }\n tx.update(productRef, write.update)\n tx.create(ledgerRef, write.ledger)\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, replayed: false, inventory: (write.update as { inventory?: unknown }).inventory ?? null } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'stock adjusted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/stock]', error)\n return res.status(500).json({ error: 'Stock could not be adjusted' })\n }\n }\n}\n\nexport const productSaveHandler = createProductSaveHandler()\nexport const productStockHandler = createProductStockHandler()\n"],"names":["createHash","isRefusedIdToken","createResourceUid","hostRoleCanWrite","dropPluginSiteCache","firebaseAdmin","getLockdownVerdict","getOrgForHost","isImpersonationSession","FieldValue","productCollectionIds","productEditWrite","productMembershipInput","productSaveFields","smartCollectionRulesOf","stockAdjustmentWrite","validateProduct","planRefusal","roomRefusal","ROLE_REFUSAL","MAX_BODY_BYTES","STOCK_ADJUST_REASONS","bodyOf","req","raw","body","length","JSON","parse","validId","value","test","gate","res","hostId","deps","host","authorization","String","headers","token","startsWith","slice","status","json","error","decoded","verifyIdToken","staff","firestore","collection","doc","get","exists","uid","org","orgForHost","locked","lockdown","data","request","method","outboxEntry","ref","paths","createdAt","serverTimestamp","attempts","entireHost","settleCache","entry","reason","dropped","dropCache","hostIds","catch","complete","delete","undefined","resolved","app","auth","options","now","Date","createProductSaveHandler","productId","product","Array","isArray","variants","name","trim","door","nowMs","fields","problem","db","hostRef","products","productRef","smartQuery","where","outbox","create","answer","runTransaction","tx","existing","smart","Promise","all","membership","collectionIds","docs","map","id","ok","replayed","early","used","count","room","Number","createdAtMs","deletedAt","updatedAt","createdBy","set","storedTimestamps","console","stored","asked","Object","fromEntries","entries","filter","key","toDate","stockAttemptId","update","digest","createProductStockHandler","variantId","delta","Math","round","locationId","includes","isFinite","ledgerRef","ledger","inventory","some","variant","write","productSaveHandler","productStockHandler"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/products-write.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 { createHash } from 'node:crypto'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getLockdownVerdict, getOrgForHost, isImpersonationSession } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n type EditedProduct,\n liftLegacyProduct,\n productCollectionIds,\n productDuplicateFields,\n productDuplicateName,\n productDuplicateSlugBase,\n productEditWrite,\n productMembershipInput,\n productSaveFields,\n smartCollectionRulesOf,\n stockAdjustmentWrite,\n validateProduct,\n type InventoryAdjustmentReason,\n} from '../model'\nimport { planRefusal, roomRefusal } from './product-drafts'\n\n/*\n * `POST /api/commerce/products/save` and `POST /api/commerce/products/stock`\n * (AGL-3652): the console product editor's save and its Adjust stock dialog,\n * for the native apps.\n *\n * The console makes both writes from the browser, computing derived fields\n * first (search keys, stock verdict, price, image, smart-collection\n * membership). These routes run THAT computation (`model/product-write.ts`,\n * which the console imports too) and make the same writes, under the gate\n * the Firestore rules put on them: a verified member with a write role on the\n * site (`canWriteHostContent`), or staff, while the site, its org and the\n * platform are not frozen (`hostWritesFrozen`). A new product also meets the\n * create path's own door: the plan's `commerce` entitlement and its\n * `productsPerHost` quota, counted in the same transaction as the create.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const ROLE_REFUSAL = 'Editing requires the editor role'\nconst MAX_BODY_BYTES = 256_000\n\n/** The reasons the Adjust stock dialog offers. */\nexport const STOCK_ADJUST_REASONS: readonly InventoryAdjustmentReason[] = ['restock', 'correction', 'damage', 'refund']\n\nexport interface ProductsWriteDeps {\n firestore?: () => Firestore\n verifyIdToken?: (token: string) => Promise<Record<string, unknown> & { uid: string }>\n orgForHost?: (hostId: string) => Promise<{ org: Record<string, unknown> } | null>\n lockdown?: (options: Parameters<typeof getLockdownVerdict>[0]) => Promise<unknown>\n dropCache?: typeof dropPluginSiteCache\n now?: () => number\n}\n\ntype Reply = Parameters<PluginApiHandler>[1]\n\nexport function bodyOf(req: Parameters<PluginApiHandler>[0]): Record<string, unknown> {\n const raw = req.body\n if (typeof raw === 'string') {\n if (raw.length > MAX_BODY_BYTES) return {}\n try {\n return JSON.parse(raw || '{}')\n } catch {\n return {}\n }\n }\n return (raw ?? {}) as Record<string, unknown>\n}\n\nexport const validId = (value: unknown): value is string =>\n typeof value === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(value)\n\nexport interface Gate {\n uid: string\n staff: boolean\n host: FirebaseFirestore.DocumentSnapshot\n org: Record<string, unknown>\n}\n\n/**\n * Who may write this site's products, as the rules decide it. Answers the\n * caller's reply on a refusal and returns null.\n */\nexport async function gate(req: Parameters<PluginApiHandler>[0], res: Reply, hostId: string, deps: Required<ProductsWriteDeps>): Promise<Gate | null> {\n const authorization = String(req.headers.authorization ?? '')\n const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!token) return void res.status(401).json({ error: 'Unauthenticated' }), null\n let decoded: Record<string, unknown> & { uid: string }\n try {\n decoded = await deps.verifyIdToken(token)\n } catch (error) {\n // Only a refused token is the caller's; an Auth outage is a 500 (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return void res.status(401).json({ error: 'Sign in again to continue' }), null\n }\n const staff = decoded['staff'] === true\n if (!staff && decoded['email_verified'] !== true && !isImpersonationSession(decoded as never)) {\n return void res.status(403).json({ error: 'Verify your email address to make changes' }), null\n }\n const host = await deps.firestore().collection('hosts').doc(hostId).get()\n if (!host.exists) return void res.status(404).json({ error: 'Unknown site' }), null\n if (!staff && !hostRoleCanWrite((host.get('memberRoles') ?? {})[decoded.uid])) {\n return void res.status(403).json({ error: ROLE_REFUSAL }), null\n }\n const org = ((await deps.orgForHost(hostId))?.org ?? {}) as Record<string, unknown>\n const locked = await deps.lockdown({ staff, uid: decoded.uid, org: org as never, host: host.data() as never, request: { method: 'POST' } as never })\n if (locked) return void res.status(423).json({ error: 'This site is not accepting changes right now' }), null\n return { uid: decoded.uid, staff, host, org }\n}\n\n/** The site-wide cache drop a product change is owed (AGL-3386): an outbox entry in the write, then the drop. */\nexport function outboxEntry(firestore: Firestore, hostId: string) {\n return {\n ref: firestore.collection('publishOutbox').doc(createResourceUid()),\n data: { hostId, paths: ['/'], createdAt: FieldValue.serverTimestamp(), attempts: 0, entireHost: true },\n }\n}\n\nexport async function settleCache(deps: Required<ProductsWriteDeps>, hostId: string, entry: FirebaseFirestore.DocumentReference, reason: string) {\n const dropped = await deps.dropCache({ hostIds: [hostId], reason }).catch(() => ({ complete: false }))\n if (dropped.complete) await entry.delete().catch(() => undefined)\n}\n\nexport function resolved(deps: ProductsWriteDeps): Required<ProductsWriteDeps> {\n return {\n firestore: deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore),\n verifyIdToken: deps.verifyIdToken ?? ((token) => firebaseAdmin.app().auth().verifyIdToken(token) as never),\n orgForHost: deps.orgForHost ?? ((hostId) => getOrgForHost(hostId) as never),\n lockdown: deps.lockdown ?? ((options) => getLockdownVerdict(options)),\n dropCache: deps.dropCache ?? dropPluginSiteCache,\n now: deps.now ?? (() => Date.now()),\n }\n}\n\n/**\n * Saves a product as the console editor does. `create: true` makes a new\n * product under the client-minted `productId` (a retry finds its own product\n * and answers `replayed`); otherwise it replaces the live product, keeping\n * its live stock holds.\n */\nexport function createProductSaveHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const product = body['product'] as EditedProduct | undefined\n if (!validId(hostId) || !validId(productId) || !product || typeof product !== 'object' || !Array.isArray(product.variants)) {\n return res.status(400).json({ error: 'Missing site, product or its variants' })\n }\n if (typeof product.name !== 'string' || !product.name.trim()) return res.status(400).json({ error: 'Product name is required' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const nowMs = deps.now()\n const fields = productSaveFields(product, nowMs)\n const problem = validateProduct(fields as never)\n if (problem) return res.status(400).json({ error: problem })\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const productRef = products.doc(productId)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const create = body['create'] === true\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(productRef), tx.get(smartQuery)])\n const membership = {\n collectionIds: productCollectionIds(\n productMembershipInput(fields, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n ),\n }\n if (create) {\n if (existing.exists) return { status: 200, body: { ok: true, id: productId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = (await tx.get(products.count())).data().count\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n tx.create(productRef, {\n ...fields,\n ...membership,\n createdAtMs: nowMs,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n } else {\n if (!existing.exists || existing.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n tx.set(productRef, {\n ...storedTimestamps(existing.data() ?? {}, product as unknown as Record<string, unknown>),\n ...productEditWrite(fields, existing.get('stockHolds')),\n ...membership,\n updatedAt: FieldValue.serverTimestamp(),\n })\n }\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: productId, replayed: false } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product saved from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/save]', error)\n return res.status(500).json({ error: 'The product could not be saved' })\n }\n }\n}\n\n/**\n * The stored timestamps an edit keeps. The console's replace writes back the\n * Timestamps its seed carried (`createdAt`, a publish time); a client's JSON\n * cannot carry one, so each stored Timestamp field the request does not name\n * is kept as stored.\n */\nexport function storedTimestamps(stored: Record<string, unknown>, asked: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(stored).filter(\n ([key, value]) => !(key in asked) && typeof (value as { toDate?: unknown } | null)?.toDate === 'function',\n ),\n )\n}\n\n/** The ledger row id one attempt owns: the same key from the same member is the same row. */\nexport function stockAttemptId(uid: string, key: string): string {\n return 'app-' + createHash('sha256').update(`${uid}:${key}`).digest('hex').slice(0, 40)\n}\n\n/**\n * Adjusts one variant's stock as the Adjust stock dialog does: the variants\n * and stock fields on the product, and one `inventoryAdjustments` row, in one\n * transaction. The Idempotency-Key names that row, so a retried attempt finds\n * it and moves nothing twice.\n */\nexport function createProductStockHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const hostId = body['hostId']\n const productId = body['productId']\n const variantId = body['variantId']\n const key = String(req.headers['idempotency-key'] ?? '').trim().slice(0, 200)\n const reason = body['reason'] as InventoryAdjustmentReason\n const delta = Math.round(Number(body['delta']))\n const locationId = typeof body['locationId'] === 'string' && body['locationId'] ? body['locationId'] : undefined\n if (!validId(hostId) || !validId(productId) || typeof variantId !== 'string' || !variantId) {\n return res.status(400).json({ error: 'Missing site, product or variant' })\n }\n if (!key) return res.status(400).json({ error: 'Missing Idempotency-Key' })\n if (!STOCK_ADJUST_REASONS.includes(reason)) return res.status(400).json({ error: 'Unknown reason' })\n if (!Number.isFinite(delta) || delta === 0) return res.status(400).json({ error: 'Enter a number of units' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const productRef = hostRef.collection('products').doc(productId)\n const ledgerRef = hostRef.collection('inventoryAdjustments').doc(stockAttemptId(door.uid, key))\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [ledger, stored] = await Promise.all([tx.get(ledgerRef), tx.get(productRef)])\n if (ledger.exists) {\n return { status: 200, body: { ok: true, replayed: true, inventory: stored.get('inventory') ?? null } }\n }\n if (!stored.exists || stored.get('deletedAt')) return { status: 404, body: { error: 'This product was deleted' } }\n const product = stored.data() as never as Parameters<typeof stockAdjustmentWrite>[0]\n if (!(product.variants ?? []).some((variant) => variant.id === variantId)) {\n return { status: 404, body: { error: 'That variant is no longer on this product' } }\n }\n const write = stockAdjustmentWrite(product, productId, { variantId, delta, reason, locationId }, deps.now())\n if (!write) return { status: 400, body: { error: 'Enter a number of units' } }\n tx.update(productRef, write.update)\n tx.create(ledgerRef, write.ledger)\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, replayed: false, inventory: (write.update as { inventory?: unknown }).inventory ?? null } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'stock adjusted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/stock]', error)\n return res.status(500).json({ error: 'Stock could not be adjusted' })\n }\n }\n}\n\n/**\n * The first slug in `base`, `base-2`, `base-3`… no product of the site holds,\n * as the console's slug ledger claims one (`product-slugs.ts`): a second\n * Duplicate of the same product does not mint a second `-copy` (AGL-3321).\n */\nexport async function claimProductSlug(products: FirebaseFirestore.CollectionReference, base: string): Promise<string> {\n for (let suffix = 1; suffix < 1000; suffix += 1) {\n const candidate = suffix < 2 ? base : `${base}-${suffix}`\n const held = await products.where('slug', '==', candidate).limit(1).get()\n if (held.empty) return candidate\n }\n return `${base}-${Date.now()}`\n}\n\n/**\n * `POST /api/commerce/products/duplicate` (AGL-3841): the products list's\n * Duplicate for the native apps. The console builds the copy in the browser\n * (`productDuplicateFields`, its slug ledger, the copy's smart collections)\n * and creates it through the quota-enforcing resources route; this runs the\n * same model over the stored product and creates it under the save route's\n * create door (the plan's commerce entitlement and its product quota, counted\n * in the same transaction). `newProductId` is the client's, so a retry finds\n * its own copy and answers `replayed`.\n */\nexport function createProductDuplicateHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, productId, newProductId } = body\n if (!validId(hostId) || !validId(productId) || !validId(newProductId) || productId === newProductId) {\n return res.status(400).json({ error: 'Missing site or product' })\n }\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const products = hostRef.collection('products')\n const copyRef = products.doc(newProductId)\n const already = await copyRef.get()\n if (already.exists) return res.status(200).json({ ok: true, id: newProductId, replayed: true })\n const source = await products.doc(productId).get()\n if (!source.exists || source.get('deletedAt')) return res.status(404).json({ error: 'This product was deleted' })\n const product = liftLegacyProduct((source.data() ?? {}) as never)\n const slug = await claimProductSlug(products, productDuplicateSlugBase(product.slug, product.name))\n const nowMs = deps.now()\n const fields = productDuplicateFields(product, slug, nowMs)\n const smartQuery = hostRef.collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart')\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const [existing, smart] = await Promise.all([tx.get(copyRef), tx.get(smartQuery)])\n if (existing.exists) return { status: 200, body: { ok: true, id: newProductId, replayed: true } }\n const early = planRefusal(door.org as never)\n if (early) return { status: early.status, body: { error: early.error } }\n const used = (await tx.get(products.count())).data().count\n const room = roomRefusal(door.org as never, Number(used) || 0)\n if (room) return { status: room.status, body: { error: room.error } }\n // The COPY's smart collections: its name differs from the source's.\n const collectionIds = productCollectionIds(\n productMembershipInput({ name: productDuplicateName(product.name) }, product) as never,\n smart.docs.map((doc) => smartCollectionRulesOf(doc.id, doc.data())),\n )\n tx.create(copyRef, {\n ...fields,\n collectionIds,\n deletedAt: null,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n createdBy: door.uid,\n })\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, id: newProductId, replayed: false, slug } }\n })\n if (answer.status === 200 && !(answer.body as { replayed?: boolean }).replayed) {\n await settleCache(deps, hostId, outbox.ref, 'product duplicated from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/duplicate]', error)\n return res.status(500).json({ error: 'Could not duplicate product' })\n }\n }\n}\n\n/**\n * `POST /api/commerce/products/delete` (AGL-3841): the products list's Delete\n * for the native apps, the console's write (`deletedAt` on the product, which\n * stops it being purchasable) with the site-wide cache drop, under the save\n * route's gate. Deleting a deleted product answers `already`.\n */\nexport function createProductDeleteHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, productId } = body\n if (!validId(hostId) || !validId(productId)) return res.status(400).json({ error: 'Missing site or product' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const productRef = db.collection('hosts').doc(hostId).collection('products').doc(productId)\n const outbox = outboxEntry(db, hostId)\n const answer = await db.runTransaction(async (tx) => {\n const stored = await tx.get(productRef)\n if (!stored.exists) return { status: 404, body: { error: 'This product no longer exists' } }\n if (stored.get('deletedAt')) return { status: 200, body: { ok: true, already: true } }\n tx.update(productRef, { deletedAt: FieldValue.serverTimestamp() })\n tx.set(outbox.ref, outbox.data)\n return { status: 200, body: { ok: true, already: false } }\n })\n if (answer.status === 200 && !(answer.body as { already?: boolean }).already) {\n await settleCache(deps, hostId, outbox.ref, 'product deleted from the app')\n }\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/products/delete]', error)\n return res.status(500).json({ error: 'Could not delete product' })\n }\n }\n}\n\nexport const productSaveHandler = createProductSaveHandler()\nexport const productStockHandler = createProductStockHandler()\nexport const productDuplicateHandler = createProductDuplicateHandler()\nexport const productDeleteHandler = createProductDeleteHandler()\n"],"names":["createHash","isRefusedIdToken","createResourceUid","hostRoleCanWrite","dropPluginSiteCache","firebaseAdmin","getLockdownVerdict","getOrgForHost","isImpersonationSession","FieldValue","liftLegacyProduct","productCollectionIds","productDuplicateFields","productDuplicateName","productDuplicateSlugBase","productEditWrite","productMembershipInput","productSaveFields","smartCollectionRulesOf","stockAdjustmentWrite","validateProduct","planRefusal","roomRefusal","ROLE_REFUSAL","MAX_BODY_BYTES","STOCK_ADJUST_REASONS","bodyOf","req","raw","body","length","JSON","parse","validId","value","test","gate","res","hostId","deps","host","authorization","String","headers","token","startsWith","slice","status","json","error","decoded","verifyIdToken","staff","firestore","collection","doc","get","exists","uid","org","orgForHost","locked","lockdown","data","request","method","outboxEntry","ref","paths","createdAt","serverTimestamp","attempts","entireHost","settleCache","entry","reason","dropped","dropCache","hostIds","catch","complete","delete","undefined","resolved","app","auth","options","now","Date","createProductSaveHandler","productId","product","Array","isArray","variants","name","trim","door","nowMs","fields","problem","db","hostRef","products","productRef","smartQuery","where","outbox","create","answer","runTransaction","tx","existing","smart","Promise","all","membership","collectionIds","docs","map","id","ok","replayed","early","used","count","room","Number","createdAtMs","deletedAt","updatedAt","createdBy","set","storedTimestamps","console","stored","asked","Object","fromEntries","entries","filter","key","toDate","stockAttemptId","update","digest","createProductStockHandler","variantId","delta","Math","round","locationId","includes","isFinite","ledgerRef","ledger","inventory","some","variant","write","claimProductSlug","base","suffix","candidate","held","limit","empty","createProductDuplicateHandler","newProductId","source","copyRef","already","slug","createProductDeleteHandler","productSaveHandler","productStockHandler","productDuplicateHandler","productDeleteHandler"],"mappings":"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"}
|
package/src/lib/server.js
CHANGED
|
@@ -423,11 +423,19 @@ registerPluginJob({
|
|
|
423
423
|
// on first call.
|
|
424
424
|
registerPluginApiRoute('commerce/products/save', async (req, res)=>(await import("./server/products-write.js")).productSaveHandler(req, res));
|
|
425
425
|
registerPluginApiRoute('commerce/products/stock', async (req, res)=>(await import("./server/products-write.js")).productStockHandler(req, res));
|
|
426
|
+
// The products list's Duplicate and Delete for the native apps (AGL-3841):
|
|
427
|
+
// the console's own copy and soft delete, with the site-wide cache drop.
|
|
428
|
+
registerPluginApiRoute('commerce/products/duplicate', async (req, res)=>(await import("./server/products-write.js")).productDuplicateHandler(req, res));
|
|
429
|
+
registerPluginApiRoute('commerce/products/delete', async (req, res)=>(await import("./server/products-write.js")).productDeleteHandler(req, res));
|
|
426
430
|
// The order dialog's note and restock answer for the native apps
|
|
427
431
|
// (AGL-3651, AGL-3652): the console's own timeline computation, in one
|
|
428
432
|
// transaction on the stored order. Loaded on first call.
|
|
429
433
|
registerPluginApiRoute('commerce/order-note', async (req, res)=>(await import("./server/order-annotations.js")).orderNoteHandler(req, res));
|
|
430
434
|
registerPluginApiRoute('commerce/order-restock-answer', async (req, res)=>(await import("./server/order-annotations.js")).orderRestockAnswerHandler(req, res));
|
|
435
|
+
// The order dialog's Packing slip and Invoice for the native apps
|
|
436
|
+
// (AGL-3841): the console's own two pages over the stored order, which the
|
|
437
|
+
// apps print on the device's print sheet. Loaded on first call.
|
|
438
|
+
registerPluginApiRoute('commerce/order-print', async (req, res)=>(await import("./server/order-print.js")).orderPrintHandler(req, res));
|
|
431
439
|
// Issue / void store credit (AGL-2226). Server-side because the host
|
|
432
440
|
// catch-all in the Firestore rules would otherwise let a client write
|
|
433
441
|
// its own `balanceCents`, which checkout applies as amount-off.
|
package/src/lib/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/server.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\n\n/**\n * Server half of the commerce plugin (AGL-396): the site-facing storefront\n * API handlers, registered with the plugin API registry and served by the\n * tenant dispatcher at their unchanged `/api/commerce/*` URLs. This module\n * pulls in firebase-admin + Stripe, so it is NOT re-exported from the client\n * barrel — apps import `@aglyn/plugins-commerce/server` only from their\n * (server-only) API dispatcher registration.\n */\n\nimport {\n registerBillingWebhookHandler,\n registerPluginApiRoute,\n registerPluginJob,\n registerSitePageEnricher,\n registerSitePageResolver,\n registerPluginPermissions,\n registerPluginConfigSchema,\n} from '@aglyn/aglyn/server'\nimport { isEmailConfigured } from '@aglyn/shared-util-email'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerCommerceMediaPublishGuard } from './server/media-publish-guard'\nimport { registerCommerceRecurringCharges } from './server/recurring-charges'\nimport { registerProductCardReader } from './server/product-card'\nimport { registerProductAiCapability } from './server/product-ai-capability'\nimport { registerStoreSetupAiCapabilities } from './server/store-setup-ai-capability'\nimport { registerStoreSetupAiWriter } from './server/store-setup-ai-writer.console'\nimport { registerProductDraftWriter } from './server/product-drafts'\nimport { registerCommerceTaxProfile } from './server/tax-profile'\nimport { registerTaxReturnSource } from '@aglyn/aglyn/plugin-manager/plugin-tax-return-sources'\nimport { commerceTaxReturnSource } from './server/tax-return-source'\nimport { taxSummaryHandler } from './server/tax-summary'\nimport { registerRevenueSource } from '@aglyn/aglyn/plugin-manager/plugin-revenue-sources'\nimport { commerceRevenueSource } from './server/revenue-source'\nimport { commerceBillingWebhookHandler } from './server/billing-webhook'\nimport { registerOrderFigureReaders } from './server/order-figures'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\nimport { commerceSitePageEnricher } from './server/site-page-enricher'\nimport { commerceSitePageResolver } from './server/site-page-resolver'\nimport { cartCheckoutHandler } from './server/cart-checkout'\nimport { cartHandler } from './server/cart'\nimport { cartExtrasHandler } from './server/checkout-extras'\nimport { catalogHandler } from './server/catalog'\nimport { checkoutHandler } from './server/checkout'\nimport { downloadHandler } from './server/download'\nimport { newsletterHandler } from './server/newsletter'\nimport { notifyRestockHandler } from './server/notify-restock'\nimport { productHandler } from './server/product'\nimport { relatedHandler } from './server/related'\nimport { reservationAvailabilityHandler } from './server/reservation-availability'\nimport { gateHandler } from './server/gate'\nimport { memberFeedHandler } from './server/member-feed'\nimport { membershipAccountHandler } from './server/membership-account'\nimport { membershipAdminPasswordHandler } from './server/membership-admin-password'\nimport { membershipAdminRemoveHandler } from './server/membership-admin-remove'\nimport { membershipContentHandler } from './server/membership-content'\nimport { membershipLoginHandler } from './server/membership-login'\nimport { membershipLogoutHandler } from './server/membership-logout'\nimport { membershipRecoverHandler } from './server/membership-recover'\nimport { membershipRegisterHandler } from './server/membership-register'\nimport { membershipResetHandler } from './server/membership-reset'\nimport { membershipWishlistHandler } from './server/membership-wishlist'\nimport { reserveHandler } from './server/reserve'\nimport { streamHandler } from './server/stream'\nimport { subscriptionPortalHandler } from './server/subscription-portal'\nimport { reviewsHandler } from './server/reviews'\nimport { connectHandler } from './server/connect'\nimport { cancelOrderHandler } from './server/cancel-order'\nimport { carrierRatesAvailabilityHandler } from './server/carrier-rates-availability'\nimport { collectionMembershipHandler } from './server/collection-membership'\nimport { draftOrderHandler } from './server/draft-order'\nimport { fulfillOrderHandler } from './server/fulfill-order'\nimport { orderReceiptSendHandler } from './server/order-receipt-send'\nimport { orderStatusHandler, registerOrderStatusActions } from './server/order-status'\nimport { returnRequestStatusAction } from './server/return-status-action'\nimport { giftCardsHandler } from './server/gift-cards'\nimport { memberPostHandler } from './server/member-post'\nimport { orderAnalyticsHandler } from './server/order-analytics'\nimport { checkoutStatusHandler } from './server/checkout-status'\nimport { posOrderHandler } from './server/pos-order'\nimport { printersHandler } from './server/printers'\nimport { posPaymentHandler } from './server/pos-payment'\nimport { posReadersHandler } from './server/pos-readers'\nimport { posTerminalConnectionTokenHandler } from './server/pos-terminal-connection-token'\nimport { posDisplayHandler } from './server/pos-display'\nimport { posKioskHandler } from './server/pos-kiosk'\nimport { registerPosOpsRoutes } from './server/pos-ops-routes'\nimport {\n processAbandonedHandler,\n scanAbandonedCheckouts,\n} from './server/process-abandoned'\nimport { processRestockHandler, scanRestockAlerts } from './server/process-restock'\nimport { scanStockDecrements } from './server/reconcile-stock'\nimport { refundHandler } from './server/refund'\nimport { orderWebhooksHandler } from './server/order-webhooks'\nimport { returnRequestHandler, returnsHandler } from './server/returns'\nimport { scanSupplierDeliveries } from './server/supplier-outbox'\nimport { supplierUpdateHandler } from './server/supplier-update'\n\n/**\n * The two commerce beats (AGL-2227).\n *\n * `commerce/process-abandoned` and `commerce/process-restock` have existed\n * since AGL-323/326 as `x-cron-secret` HTTP doors, and `registerCommerceConsoleApi`\n * below has called them \"the scheduler-driven jobs\" in a comment that whole\n * time. **Nothing scheduled them.** Not `scheduled-crons.yml` (its table names\n * 11 paths; neither of these), not `vercel.json` (no `crons` key at all), not\n * `registerPluginJob` — the only two registrations in the repo were scheduled\n * publishing and the bookings hold-expiry.\n *\n * The cost was a Pro-tier entitlement (`abandonedCart`) that had never sent an\n * email, and a storefront \"notify me when it's back\" form writing into a queue\n * with no drain. AGL-1793 had already added the collection-group indexes these\n * two scans need, which is the clearest evidence they were always meant to run.\n *\n * Module scope, like the bookings job beside it: the runner route reaches jobs\n * through `ensureAll(['tenantApi'])`, and a registration inside a `register*`\n * function would depend on which entry point happened to be loaded.\n *\n * 15 minutes, not 1: `process-abandoned` will not remind a checkout younger\n * than an hour and `process-restock` only mails alerts whose product already\n * has stock, so a tighter beat buys nothing and costs two collection-group\n * scans a minute. Both are bounded (200 docs) and idempotent — each pass\n * stamps what it sent — so an overlapping or repeated beat cannot double-send.\n */\nconst RECOVERY_JOB_INTERVAL_MINUTES = 15\n\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'abandoned-checkout-recovery',\n intervalMinutes: RECOVERY_JOB_INTERVAL_MINUTES,\n description:\n 'Email one recovery reminder per stalled checkout (abandonedCart plans).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n // Quietly, not as an error: email is optional per deployment, and a beat\n // that logs every minute on a self-host without Resend buries everything\n // else in the log.\n if (!isEmailConfigured()) return\n const { sent } = await scanAbandonedCheckouts(gate)\n if (sent) console.info(`commerce: sent ${sent} abandoned-cart reminders`)\n },\n})\n\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'back-in-stock-alerts',\n intervalMinutes: RECOVERY_JOB_INTERVAL_MINUTES,\n description: 'Email shoppers whose requested product is in stock again.',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n if (!isEmailConfigured()) return\n const { sent } = await scanRestockAlerts(gate)\n if (sent) console.info(`commerce: sent ${sent} back-in-stock alerts`)\n },\n})\n\n/**\n * The review-request email (AGL-3736). HOURLY: a request is owed days after\n * fulfilment, so an hour's lateness is invisible, and the scan is one\n * collection-group range query over `reviewRequestDueAtMs` — a field only\n * orders still owed a request carry, so an empty beat costs one read.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'review-request-emails',\n intervalMinutes: 60,\n description:\n 'Email each buyer one review request, with a single-use link per product, ' +\n 'days after their order is fulfilled (AGL-3736).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n if (!isEmailConfigured()) return\n const { scanReviewRequests } = await import('./server/review-requests')\n const { sent } = await scanReviewRequests(gate)\n if (sent) console.info(`commerce: sent ${sent} review requests`)\n },\n})\n\n/**\n * The missing-decrement detector (AGL-2358).\n *\n * HOURLY, not on the 15-minute recovery beat: it is a detector for a rare\n * process death, not a queue drain, and nothing it finds gets less true for\n * waiting an hour. The cost of the beat is what sets the interval — one\n * collection-group read of the platform's most recent orders, plus two\n * queries per host that has any — and an hour keeps that off the same tick as\n * the two scans above.\n *\n * NOT gated on `isEmailConfigured()`, unlike its two neighbours. Those send\n * mail; this writes a console notification and a log line, both of which work\n * on a self-host with no mail provider at all — and a stock count that is\n * silently wrong is exactly what a self-hoster least wants suppressed by a\n * setting about email.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'stock-decrement-reconciliation',\n intervalMinutes: 60,\n description:\n 'Report paid orders whose stock decrement never landed (AGL-2358).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n const scan = await scanStockDecrements(gate)\n if (scan.missingLines || scan.truncatedHosts) {\n console.warn(\n `commerce: ${scan.missingLines} order lines across ${scan.hosts} ` +\n `sites have no sale ledger row (${scan.reportedOrders} newly ` +\n `reported, ${scan.truncatedHosts} sites' ledger window truncated)`,\n )\n }\n },\n})\n\n/**\n * The dropship supplier outbox drain (AGL-2473).\n *\n * EVERY MINUTE, unlike the three above, and the interval is the point rather\n * than an oversight. This is the only one of the four that a BUYER is waiting\n * on: the row it drains is a paid order that has not been routed to whoever\n * ships it, and every minute it sits is a minute the parcel is not moving. The\n * first backoff step is also 60s, so a supplier that blipped is retried on the\n * next tick.\n *\n * The cost that set fifteen minutes for the recovery pair does not apply. Those\n * are collection-GROUP scans across every site on the platform; this is one\n * ordinary equality query against a top-level collection that is EMPTY in the\n * ordinary case — an empty result bills a single read, so the whole beat is\n * ~1,440 reads a day whether or not anyone is dropshipping.\n *\n * Not gated on `isEmailConfigured()`. The supplier notification is an HTTP POST\n * to the merchant's own supplier, and a self-host with no mail provider still\n * has dropship orders to route.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'supplier-webhook-delivery',\n intervalMinutes: 1,\n description:\n 'Deliver queued dropship supplier notifications, with backoff and a ' +\n 'dead letter that tells the merchant (AGL-2473).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n const scan = await scanSupplierDeliveries(gate)\n if (scan.deadLettered) {\n console.warn(\n `commerce: ${scan.deadLettered} dropship orders could not be routed ` +\n `to their supplier and have been reported on the order`,\n )\n }\n },\n})\n\n/**\n * Stripe payment method domains for every connected custom domain (AGL-3629).\n *\n * The backfill for domains connected before registration existed, and the\n * repair for a connect whose event was lost, in one daily pass. A domain\n * already registered costs a document read and no Stripe call, so the beat is\n * cheap; a day's delay costs only the Apple Pay button on a domain connected\n * while Stripe was unreachable, and the first in-page checkout there registers\n * it anyway.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'payment-method-domains',\n intervalMinutes: 24 * 60,\n description:\n 'Register connected custom domains with Stripe so Apple Pay, Google Pay ' +\n 'and Link show on them (AGL-3629).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n if (!process.env.STRIPE_SECRET_KEY) return\n const { reconcileCustomDomains } = await import('./server/payment-method-domains')\n const result = await reconcileCustomDomains(gate)\n if (result.registered || result.failed) {\n console.info(\n `commerce: payment method domains — ${result.registered} registered, ` +\n `${result.failed} failed across ${result.hosts} sites`,\n )\n }\n },\n})\n\n/**\n * A visitor's payment door: it opens a Stripe Checkout Session for whoever\n * calls it, so the dispatcher holds it to the card-testing counters\n * (AGL-3363). Declared here, beside the registration, so the door cannot\n * exist without it.\n */\nconst CARD_PAYMENT_DOOR = { cardPayment: true } as const\n\n/** Registers the commerce plugin's storefront API routes. */\nexport function registerCommerceApi(): void {\n registerProductCardReader()\n // The merchant's tax rule, for every plugin that prices a charge here.\n registerCommerceTaxProfile()\n registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Merchant-settable register discount ceiling (AGL-2161). Registered on\n // BOTH surfaces, like the permissions above: the POS route reads it\n // server-side, and the settings card renders it from the same schema.\n registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n // PDP/PLP template pages (AGL-418): /products/* + /collections/*.\n registerSitePageResolver(commerceSitePageResolver)\n // Seeds product grids on ordinary screens (AGL-659) — the resolver above\n // only covers /products/* and /collections/*, so /products itself, and any\n // designed page with a grid on it, needed this to render server-side.\n registerSitePageEnricher(commerceSitePageEnricher)\n registerPluginApiRoute('commerce/cart-checkout', cartCheckoutHandler, CARD_PAYMENT_DOOR)\n registerPluginApiRoute('commerce/cart', cartHandler)\n // What the cart offers besides shipping (AGL-3624): pickup locations, and\n // local delivery's fee, minimum and windows for a postal code.\n registerPluginApiRoute('commerce/local-fulfillment-options', async (req, res) =>\n (await import('./server/local-fulfillment')).localFulfillmentOptionsHandler(req, res),\n )\n // Optional lines another plugin offers at the cart (AGL-3635).\n registerPluginApiRoute('commerce/cart-extras', cartExtrasHandler)\n registerPluginApiRoute('commerce/catalog', catalogHandler)\n registerPluginApiRoute('commerce/checkout', checkoutHandler, CARD_PAYMENT_DOOR)\n registerPluginApiRoute('commerce/download', downloadHandler)\n // The pre-channels Google feed address (AGL-299), now written by the\n // catalog's feed publisher (AGL-3637); the module loads with a fetch.\n registerPluginApiRoute('commerce/feed', {\n web: async (request) => (await import('./server/legacy-feed')).legacyFeedRoute(request),\n })\n registerPluginApiRoute('commerce/newsletter', newsletterHandler)\n registerPluginApiRoute('commerce/notify-restock', notifyRestockHandler)\n // GA-safe order projection for the storefront `purchase` (AGL-1641).\n registerPluginApiRoute('commerce/order-analytics', orderAnalyticsHandler)\n // The guest order-status page's data (AGL-3610), behind the signed link in\n // every buyer email — a recipient link, so it outlives the site's gates.\n registerPluginApiRoute('commerce/order-status', orderStatusHandler, { recipientLink: true })\n // …and its \"Request a return\" button (AGL-3611), on an order that can\n // still send something back.\n registerOrderStatusActions(returnRequestStatusAction)\n // What became of a session the shopper was returned from (AGL-3606).\n registerPluginApiRoute('commerce/checkout-status', checkoutStatusHandler)\n registerPluginApiRoute('commerce/product', productHandler)\n registerPluginApiRoute('commerce/related', relatedHandler)\n registerPluginApiRoute('commerce/reservation-availability', reservationAvailabilityHandler)\n registerPluginApiRoute('commerce/reserve', reserveHandler, CARD_PAYMENT_DOOR)\n registerPluginApiRoute('commerce/gate', gateHandler)\n registerPluginApiRoute('commerce/member-feed', memberFeedHandler)\n registerPluginApiRoute('commerce/stream', streamHandler)\n registerPluginApiRoute('commerce/subscription-portal', subscriptionPortalHandler)\n registerPluginApiRoute('commerce/reviews', reviewsHandler)\n // A buyer's return request (AGL-3611), from their account or the signed\n // order-status link.\n registerPluginApiRoute('commerce/return-request', returnRequestHandler)\n registerPluginApiRoute('membership/account', membershipAccountHandler)\n registerPluginApiRoute('membership/content', membershipContentHandler)\n registerPluginApiRoute('membership/login', membershipLoginHandler)\n registerPluginApiRoute('membership/logout', membershipLogoutHandler)\n // Password recovery pair (AGL-552): request + complete.\n registerPluginApiRoute('membership/recover', membershipRecoverHandler)\n registerPluginApiRoute('membership/register', membershipRegisterHandler)\n registerPluginApiRoute('membership/reset', membershipResetHandler)\n registerPluginApiRoute('membership/wishlist', membershipWishlistHandler)\n}\n\n/**\n * Registers the commerce plugin's console-side API routes (AGL-396):\n * merchant/staff operations (Connect onboarding, refunds, draft & POS\n * orders, member posts), the supplier tracking callback, and the HTTP doors\n * for the abandoned-cart / restock passes.\n *\n * Those last two are NOT what schedules them — the `registerPluginJob` calls\n * above are (AGL-2227). This comment used to call them \"the scheduler-driven\n * jobs\", which is how they stayed dark for months: it asserted the wiring\n * instead of having it.\n */\nexport function registerCommerceConsoleApi(): void {\n // The register's gate (`managePos`) is resolved on THIS surface: every POS\n // route below runs in the console, and a key no surface registered here is\n // absent from the resolved map, which reads as refused — the site's own\n // owner was answered 403 at the register.\n registerPluginPermissions(COMMERCE_PERMISSIONS)\n // What a product looks like to a surface that is not this plugin's — a\n // campaign email that features one asks here rather than importing the model.\n registerProductCardReader()\n // …and a draft product another plugin asks for by name (AGL-3616): an AI\n // build setting a store up from a brief, unpriced unless the brief priced it.\n // The console runs AI jobs (AGL-3026).\n registerProductDraftWriter()\n // …and the operation an AI build plans for it, which that writer executes.\n registerProductAiCapability()\n // …and the store's shipping, pickup, local delivery and tax settings an AI\n // build sets up (AGL-3749), written live as the console's cards write them,\n // with the four operations that writer executes.\n registerStoreSetupAiWriter()\n registerStoreSetupAiCapabilities()\n // …and why a file somebody is SELLING may not be made public (AGL-3080).\n // The media library asks before it hands an asset its permanent CDN URL\n // back; what a product is, and which of its fields hold paid media, is\n // this plugin's to know.\n registerCommerceMediaPublishGuard()\n // …and which memberships a site sells, for a security lockdown to pause\n // (AGL-3364). The lockdown route runs on this surface.\n registerCommerceRecurringCharges()\n // …and the tax rule, for the webhook that confirms what they charged.\n registerCommerceTaxProfile()\n // …and its sales on the operator's own sales tax return (AGL-3080): the\n // tax a storefront checkout collected under the platform's registrations.\n // The staff return awaits this surface before it asks, and refuses to be\n // filed without this source — commerce declares `taxReturnSource`.\n registerTaxReturnSource(commerceTaxReturnSource, { pluginId: BUNDLE_ID })\n // …and the take its orders earn the operator, on the staff revenue report\n // (AGL-3080) — commerce declares `revenueSource`, so the report names this\n // source as unread rather than counting it as zero if it is missing.\n registerRevenueSource(commerceRevenueSource, { pluginId: BUNDLE_ID })\n // Stripe webhook sections (AGL-418): orders/carts/drafts/reservations/\n // subscriptions ride the platform webhook via the hook registry.\n registerBillingWebhookHandler(commerceBillingWebhookHandler)\n // Cancel + stock release in one transaction (AGL-1808). Server-side because\n // the release depends on the transition rule, and a client write could not\n // re-ask it under the same lock that flips the status.\n registerPluginApiRoute('commerce/cancel-order', cancelOrderHandler)\n // Whether live carrier rates can be offered on this site (AGL-3612).\n registerPluginApiRoute('commerce/shipping/carrier-rates', carrierRatesAvailabilityHandler)\n registerPluginApiRoute('commerce/connect', connectHandler)\n registerPluginApiRoute('commerce/draft-order', draftOrderHandler)\n // Fulfil + mark-delivered with the transition re-asked under the write\n // (AGL-1819) — the same stale-dialog hole cancel-order closes, minus the\n // stock release those two transitions never had. The customer REST API's\n // `PATCH /v1/sites/{id}/orders/{id}` records a shipment through the same\n // transaction (`server/api-v1/orders-and-products.ts`).\n registerPluginApiRoute('commerce/fulfill-order', fulfillOrderHandler)\n // Pickup and local delivery steps (AGL-3624) — ready, picked up, out for\n // delivery, delivered — from the console queue, the order dialog and the\n // native Aglyn app. Loaded on first call.\n registerPluginApiRoute('commerce/local-fulfillment', async (req, res) =>\n (await import('./server/local-fulfillment-status')).localFulfillmentHandler(req, res),\n )\n // \"Resend receipt\" from the order dialog (AGL-3610): by email, or by text\n // when the platform's SMS provider is configured. Rate-limited per member\n // and per order.\n registerPluginApiRoute('commerce/order-receipt-send', orderReceiptSendHandler)\n // The product editor's save and Adjust stock for the native apps (AGL-3652):\n // the console's own product-write computation under the rules' gate. Loaded\n // on first call.\n registerPluginApiRoute('commerce/products/save', async (req, res) =>\n (await import('./server/products-write')).productSaveHandler(req, res),\n )\n registerPluginApiRoute('commerce/products/stock', async (req, res) =>\n (await import('./server/products-write')).productStockHandler(req, res),\n )\n // The order dialog's note and restock answer for the native apps\n // (AGL-3651, AGL-3652): the console's own timeline computation, in one\n // transaction on the stored order. Loaded on first call.\n registerPluginApiRoute('commerce/order-note', async (req, res) =>\n (await import('./server/order-annotations')).orderNoteHandler(req, res),\n )\n registerPluginApiRoute('commerce/order-restock-answer', async (req, res) =>\n (await import('./server/order-annotations')).orderRestockAnswerHandler(req, res),\n )\n // Issue / void store credit (AGL-2226). Server-side because the host\n // catch-all in the Firestore rules would otherwise let a client write\n // its own `balanceCents`, which checkout applies as amount-off.\n registerPluginApiRoute('commerce/gift-cards', giftCardsHandler)\n // The merchant's own storefront sales tax, by who owes it (AGL-2440): the\n // same rows and classifier the operator's return reads, fenced to one site.\n registerPluginApiRoute('commerce/tax-summary', { web: taxSummaryHandler })\n // The payment methods card (AGL-3629): toggles, the platform's offer, the\n // payout account's capabilities and the site's wallet domains. Loaded with\n // the first request, so a console that never opens Settings never imports it.\n registerPluginApiRoute('commerce/payment-methods', {\n web: async (request) =>\n (await import('./server/payment-methods')).paymentMethodsHandler(request),\n })\n // A smart collection's rules changed or it was deleted: re-stamp which\n // products it holds (AGL-3321), the membership the storefront queries.\n registerPluginApiRoute('commerce/collection-membership', collectionMembershipHandler)\n // A site member's password help (AGL-914) and removal (AGL-3308), from the\n // Users page drawer and the Inbox. Console-auth, not the visitor cookie the\n // storefront's `membership/*` routes take, so they are served where the\n // console posts: the console's own dispatcher loads this surface only.\n registerPluginApiRoute(\n 'membership/admin-password',\n membershipAdminPasswordHandler,\n )\n registerPluginApiRoute('membership/admin-remove', membershipAdminRemoveHandler)\n registerPluginApiRoute('commerce/member-post', memberPostHandler)\n registerPluginApiRoute('commerce/pos-order', posOrderHandler)\n // A register's cloud receipt printers and the jobs a manager sends them\n // (AGL-3619), and the two doors the printers themselves poll. The doors are\n // MACHINE routes: a printer is no member and names no site cookie, so it\n // authenticates with the per-printer secret in its URL and its own device id,\n // and the route asks the plan before it hands over a job. Loaded with the\n // first poll, so a console that has no printers never imports them.\n registerPluginApiRoute('commerce/printers', printersHandler)\n registerPluginApiRoute(\n 'commerce/cloudprnt/:hostId/:printerId/:secret',\n {\n web: async (request, context) =>\n (await import('./server/printer-poll')).cloudPrntRoute(request, context),\n },\n { machine: true },\n )\n registerPluginApiRoute(\n 'commerce/epson-sdp/:hostId/:printerId/:secret',\n {\n web: async (request, context) =>\n (await import('./server/printer-poll')).epsonServerDirectPrintRoute(request, context),\n },\n { machine: true },\n )\n // The register's tenders against an open sale, its Stripe Terminal card\n // readers, and the paired customer display (AGL-3607, AGL-3608). The\n // display's `pair`/`poll`/`respond` take a display token and no console\n // session; every other action is gated like a sale.\n registerPluginApiRoute('commerce/pos-payment', posPaymentHandler)\n registerPluginApiRoute('commerce/pos-readers', posReadersHandler)\n registerPluginApiRoute('commerce/pos-display', posDisplayHandler)\n // The self-service kiosk (AGL-3623): a register's paired screen where the\n // customer orders and pays. Its device token, never a staff session; the\n // register's queue of \"pay at counter\" orders is gated like a sale.\n registerPluginApiRoute('commerce/pos-kiosk', posKioskHandler)\n // Shifts, staff PINs, the customer lookup and returns (AGL-3609).\n registerPosOpsRoutes()\n // The offline register (AGL-3625): the kit a register caches to sell cash\n // while the connection is down, and the sync that records those sales once.\n // Gated like a sale; loaded with its first call.\n registerPluginApiRoute('commerce/pos-offline-sync', async (req, res) =>\n (await import('./server/pos-offline-sync')).posOfflineSyncHandler(req, res),\n )\n // The native Aglyn POS app's Stripe Terminal SDK: a connection token scoped\n // to the site's Location, and the Location itself (AGL-3618). Gated like a\n // sale.\n registerPluginApiRoute('commerce/pos-terminal-connection-token', posTerminalConnectionTokenHandler)\n registerPluginApiRoute('commerce/process-abandoned', processAbandonedHandler)\n registerPluginApiRoute('commerce/process-restock', processRestockHandler)\n registerPluginApiRoute('commerce/refund', refundHandler)\n // Returns (AGL-3611): approve, decline, receive with restock, refund\n // through the route above, a label from a shipping plugin.\n registerPluginApiRoute('commerce/returns', returnsHandler)\n // The merchant's outbound order webhooks (AGL-3611): endpoints, secrets,\n // a test ping and a resend from the delivery log.\n registerPluginApiRoute('commerce/order-webhooks', orderWebhooksHandler)\n registerPluginApiRoute('commerce/supplier-update', supplierUpdateHandler)\n // Stamps the open orders that predate the shipping fields, before an\n // export for shipping (AGL-3613).\n registerPluginApiRoute('commerce/orders-shipping-prepare', async (req, res) =>\n (await import('./server/orders-shipping-prepare')).ordersShippingPrepareHandler(req, res),\n )\n // ShipStation's Custom Store endpoint (AGL-3613): ShipStation's servers\n // pull the order feed and post shipments back with the site's own Basic\n // credentials, so it is a MACHINE's route and asks the site's commerce,\n // plan, release and lockdown gates itself once the credentials prove the\n // site. Loaded on its first call, never with the console API surface.\n registerPluginApiRoute(\n 'commerce/shipstation/:hostId',\n {\n web: async (request, context) =>\n (await import('./server/shipstation')).shipStationRoute(request, context),\n },\n { machine: true },\n )\n // ShippingEasy's shipment callback (AGL-3633): ShippingEasy's servers post\n // each label bought for an order this store sent them, signed with the\n // merchant's own API secret, so it is a MACHINE's route like ShipStation's\n // above and asks the same gates once the signature proves the site.\n registerPluginApiRoute(\n 'commerce/shippingeasy/:hostId',\n {\n web: async (request, context) =>\n (await import('./server/shippingeasy')).shippingEasyRoute(request, context),\n },\n { machine: true },\n )\n // The ShipStation and ShippingEasy cards' connect, show, new password,\n // send open orders and disconnect.\n registerPluginApiRoute('commerce/shipping-connectors', async (req, res) =>\n (await import('./server/shipping-connectors')).shippingConnectorsHandler(req, res),\n )\n // The store's sales as figure tables (AGL-2915), for the AI plugin's\n // insights to read by id rather than by reading orders. The console runs\n // insight jobs, so the console surface registers them.\n registerOrderFigureReaders(() => firebaseAdmin.app().firestore())\n}\n\n// Shared with the (still app-side) membership/account route.\nexport { mintDownloadToken } from './server/download'\n\n// Site-member session primitives, shared with the (still app-side)\n// membership/* routes until those migrate too (AGL-396).\nexport * from './server/membership'\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { commercePlanEntitlements } from './plan-entitlements'\n"],"names":["registerBillingWebhookHandler","registerPluginApiRoute","registerPluginJob","registerSitePageEnricher","registerSitePageResolver","registerPluginPermissions","registerPluginConfigSchema","isEmailConfigured","firebaseAdmin","BUNDLE_ID","registerCommerceMediaPublishGuard","registerCommerceRecurringCharges","registerProductCardReader","registerProductAiCapability","registerStoreSetupAiCapabilities","registerStoreSetupAiWriter","registerProductDraftWriter","registerCommerceTaxProfile","registerTaxReturnSource","commerceTaxReturnSource","taxSummaryHandler","registerRevenueSource","commerceRevenueSource","commerceBillingWebhookHandler","registerOrderFigureReaders","COMMERCE_PERMISSIONS","COMMERCE_CONFIG_SCHEMA","commerceSitePageEnricher","commerceSitePageResolver","cartCheckoutHandler","cartHandler","cartExtrasHandler","catalogHandler","checkoutHandler","downloadHandler","newsletterHandler","notifyRestockHandler","productHandler","relatedHandler","reservationAvailabilityHandler","gateHandler","memberFeedHandler","membershipAccountHandler","membershipAdminPasswordHandler","membershipAdminRemoveHandler","membershipContentHandler","membershipLoginHandler","membershipLogoutHandler","membershipRecoverHandler","membershipRegisterHandler","membershipResetHandler","membershipWishlistHandler","reserveHandler","streamHandler","subscriptionPortalHandler","reviewsHandler","connectHandler","cancelOrderHandler","carrierRatesAvailabilityHandler","collectionMembershipHandler","draftOrderHandler","fulfillOrderHandler","orderReceiptSendHandler","orderStatusHandler","registerOrderStatusActions","returnRequestStatusAction","giftCardsHandler","memberPostHandler","orderAnalyticsHandler","checkoutStatusHandler","posOrderHandler","printersHandler","posPaymentHandler","posReadersHandler","posTerminalConnectionTokenHandler","posDisplayHandler","posKioskHandler","registerPosOpsRoutes","processAbandonedHandler","scanAbandonedCheckouts","processRestockHandler","scanRestockAlerts","scanStockDecrements","refundHandler","orderWebhooksHandler","returnRequestHandler","returnsHandler","scanSupplierDeliveries","supplierUpdateHandler","RECOVERY_JOB_INTERVAL_MINUTES","pluginId","name","intervalMinutes","description","lockdown","scope","handler","gate","sent","console","info","scanReviewRequests","scan","missingLines","truncatedHosts","warn","hosts","reportedOrders","deadLettered","process","env","STRIPE_SECRET_KEY","reconcileCustomDomains","result","registered","failed","CARD_PAYMENT_DOOR","cardPayment","registerCommerceApi","req","res","localFulfillmentOptionsHandler","web","request","legacyFeedRoute","recipientLink","registerCommerceConsoleApi","localFulfillmentHandler","productSaveHandler","productStockHandler","orderNoteHandler","orderRestockAnswerHandler","paymentMethodsHandler","context","cloudPrntRoute","machine","epsonServerDirectPrintRoute","posOfflineSyncHandler","ordersShippingPrepareHandler","shipStationRoute","shippingEasyRoute","shippingConnectorsHandler","app","firestore","mintDownloadToken"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/server.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\n\n/**\n * Server half of the commerce plugin (AGL-396): the site-facing storefront\n * API handlers, registered with the plugin API registry and served by the\n * tenant dispatcher at their unchanged `/api/commerce/*` URLs. This module\n * pulls in firebase-admin + Stripe, so it is NOT re-exported from the client\n * barrel — apps import `@aglyn/plugins-commerce/server` only from their\n * (server-only) API dispatcher registration.\n */\n\nimport {\n registerBillingWebhookHandler,\n registerPluginApiRoute,\n registerPluginJob,\n registerSitePageEnricher,\n registerSitePageResolver,\n registerPluginPermissions,\n registerPluginConfigSchema,\n} from '@aglyn/aglyn/server'\nimport { isEmailConfigured } from '@aglyn/shared-util-email'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerCommerceMediaPublishGuard } from './server/media-publish-guard'\nimport { registerCommerceRecurringCharges } from './server/recurring-charges'\nimport { registerProductCardReader } from './server/product-card'\nimport { registerProductAiCapability } from './server/product-ai-capability'\nimport { registerStoreSetupAiCapabilities } from './server/store-setup-ai-capability'\nimport { registerStoreSetupAiWriter } from './server/store-setup-ai-writer.console'\nimport { registerProductDraftWriter } from './server/product-drafts'\nimport { registerCommerceTaxProfile } from './server/tax-profile'\nimport { registerTaxReturnSource } from '@aglyn/aglyn/plugin-manager/plugin-tax-return-sources'\nimport { commerceTaxReturnSource } from './server/tax-return-source'\nimport { taxSummaryHandler } from './server/tax-summary'\nimport { registerRevenueSource } from '@aglyn/aglyn/plugin-manager/plugin-revenue-sources'\nimport { commerceRevenueSource } from './server/revenue-source'\nimport { commerceBillingWebhookHandler } from './server/billing-webhook'\nimport { registerOrderFigureReaders } from './server/order-figures'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\nimport { commerceSitePageEnricher } from './server/site-page-enricher'\nimport { commerceSitePageResolver } from './server/site-page-resolver'\nimport { cartCheckoutHandler } from './server/cart-checkout'\nimport { cartHandler } from './server/cart'\nimport { cartExtrasHandler } from './server/checkout-extras'\nimport { catalogHandler } from './server/catalog'\nimport { checkoutHandler } from './server/checkout'\nimport { downloadHandler } from './server/download'\nimport { newsletterHandler } from './server/newsletter'\nimport { notifyRestockHandler } from './server/notify-restock'\nimport { productHandler } from './server/product'\nimport { relatedHandler } from './server/related'\nimport { reservationAvailabilityHandler } from './server/reservation-availability'\nimport { gateHandler } from './server/gate'\nimport { memberFeedHandler } from './server/member-feed'\nimport { membershipAccountHandler } from './server/membership-account'\nimport { membershipAdminPasswordHandler } from './server/membership-admin-password'\nimport { membershipAdminRemoveHandler } from './server/membership-admin-remove'\nimport { membershipContentHandler } from './server/membership-content'\nimport { membershipLoginHandler } from './server/membership-login'\nimport { membershipLogoutHandler } from './server/membership-logout'\nimport { membershipRecoverHandler } from './server/membership-recover'\nimport { membershipRegisterHandler } from './server/membership-register'\nimport { membershipResetHandler } from './server/membership-reset'\nimport { membershipWishlistHandler } from './server/membership-wishlist'\nimport { reserveHandler } from './server/reserve'\nimport { streamHandler } from './server/stream'\nimport { subscriptionPortalHandler } from './server/subscription-portal'\nimport { reviewsHandler } from './server/reviews'\nimport { connectHandler } from './server/connect'\nimport { cancelOrderHandler } from './server/cancel-order'\nimport { carrierRatesAvailabilityHandler } from './server/carrier-rates-availability'\nimport { collectionMembershipHandler } from './server/collection-membership'\nimport { draftOrderHandler } from './server/draft-order'\nimport { fulfillOrderHandler } from './server/fulfill-order'\nimport { orderReceiptSendHandler } from './server/order-receipt-send'\nimport { orderStatusHandler, registerOrderStatusActions } from './server/order-status'\nimport { returnRequestStatusAction } from './server/return-status-action'\nimport { giftCardsHandler } from './server/gift-cards'\nimport { memberPostHandler } from './server/member-post'\nimport { orderAnalyticsHandler } from './server/order-analytics'\nimport { checkoutStatusHandler } from './server/checkout-status'\nimport { posOrderHandler } from './server/pos-order'\nimport { printersHandler } from './server/printers'\nimport { posPaymentHandler } from './server/pos-payment'\nimport { posReadersHandler } from './server/pos-readers'\nimport { posTerminalConnectionTokenHandler } from './server/pos-terminal-connection-token'\nimport { posDisplayHandler } from './server/pos-display'\nimport { posKioskHandler } from './server/pos-kiosk'\nimport { registerPosOpsRoutes } from './server/pos-ops-routes'\nimport {\n processAbandonedHandler,\n scanAbandonedCheckouts,\n} from './server/process-abandoned'\nimport { processRestockHandler, scanRestockAlerts } from './server/process-restock'\nimport { scanStockDecrements } from './server/reconcile-stock'\nimport { refundHandler } from './server/refund'\nimport { orderWebhooksHandler } from './server/order-webhooks'\nimport { returnRequestHandler, returnsHandler } from './server/returns'\nimport { scanSupplierDeliveries } from './server/supplier-outbox'\nimport { supplierUpdateHandler } from './server/supplier-update'\n\n/**\n * The two commerce beats (AGL-2227).\n *\n * `commerce/process-abandoned` and `commerce/process-restock` have existed\n * since AGL-323/326 as `x-cron-secret` HTTP doors, and `registerCommerceConsoleApi`\n * below has called them \"the scheduler-driven jobs\" in a comment that whole\n * time. **Nothing scheduled them.** Not `scheduled-crons.yml` (its table names\n * 11 paths; neither of these), not `vercel.json` (no `crons` key at all), not\n * `registerPluginJob` — the only two registrations in the repo were scheduled\n * publishing and the bookings hold-expiry.\n *\n * The cost was a Pro-tier entitlement (`abandonedCart`) that had never sent an\n * email, and a storefront \"notify me when it's back\" form writing into a queue\n * with no drain. AGL-1793 had already added the collection-group indexes these\n * two scans need, which is the clearest evidence they were always meant to run.\n *\n * Module scope, like the bookings job beside it: the runner route reaches jobs\n * through `ensureAll(['tenantApi'])`, and a registration inside a `register*`\n * function would depend on which entry point happened to be loaded.\n *\n * 15 minutes, not 1: `process-abandoned` will not remind a checkout younger\n * than an hour and `process-restock` only mails alerts whose product already\n * has stock, so a tighter beat buys nothing and costs two collection-group\n * scans a minute. Both are bounded (200 docs) and idempotent — each pass\n * stamps what it sent — so an overlapping or repeated beat cannot double-send.\n */\nconst RECOVERY_JOB_INTERVAL_MINUTES = 15\n\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'abandoned-checkout-recovery',\n intervalMinutes: RECOVERY_JOB_INTERVAL_MINUTES,\n description:\n 'Email one recovery reminder per stalled checkout (abandonedCart plans).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n // Quietly, not as an error: email is optional per deployment, and a beat\n // that logs every minute on a self-host without Resend buries everything\n // else in the log.\n if (!isEmailConfigured()) return\n const { sent } = await scanAbandonedCheckouts(gate)\n if (sent) console.info(`commerce: sent ${sent} abandoned-cart reminders`)\n },\n})\n\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'back-in-stock-alerts',\n intervalMinutes: RECOVERY_JOB_INTERVAL_MINUTES,\n description: 'Email shoppers whose requested product is in stock again.',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n if (!isEmailConfigured()) return\n const { sent } = await scanRestockAlerts(gate)\n if (sent) console.info(`commerce: sent ${sent} back-in-stock alerts`)\n },\n})\n\n/**\n * The review-request email (AGL-3736). HOURLY: a request is owed days after\n * fulfilment, so an hour's lateness is invisible, and the scan is one\n * collection-group range query over `reviewRequestDueAtMs` — a field only\n * orders still owed a request carry, so an empty beat costs one read.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'review-request-emails',\n intervalMinutes: 60,\n description:\n 'Email each buyer one review request, with a single-use link per product, ' +\n 'days after their order is fulfilled (AGL-3736).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n if (!isEmailConfigured()) return\n const { scanReviewRequests } = await import('./server/review-requests')\n const { sent } = await scanReviewRequests(gate)\n if (sent) console.info(`commerce: sent ${sent} review requests`)\n },\n})\n\n/**\n * The missing-decrement detector (AGL-2358).\n *\n * HOURLY, not on the 15-minute recovery beat: it is a detector for a rare\n * process death, not a queue drain, and nothing it finds gets less true for\n * waiting an hour. The cost of the beat is what sets the interval — one\n * collection-group read of the platform's most recent orders, plus two\n * queries per host that has any — and an hour keeps that off the same tick as\n * the two scans above.\n *\n * NOT gated on `isEmailConfigured()`, unlike its two neighbours. Those send\n * mail; this writes a console notification and a log line, both of which work\n * on a self-host with no mail provider at all — and a stock count that is\n * silently wrong is exactly what a self-hoster least wants suppressed by a\n * setting about email.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'stock-decrement-reconciliation',\n intervalMinutes: 60,\n description:\n 'Report paid orders whose stock decrement never landed (AGL-2358).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n const scan = await scanStockDecrements(gate)\n if (scan.missingLines || scan.truncatedHosts) {\n console.warn(\n `commerce: ${scan.missingLines} order lines across ${scan.hosts} ` +\n `sites have no sale ledger row (${scan.reportedOrders} newly ` +\n `reported, ${scan.truncatedHosts} sites' ledger window truncated)`,\n )\n }\n },\n})\n\n/**\n * The dropship supplier outbox drain (AGL-2473).\n *\n * EVERY MINUTE, unlike the three above, and the interval is the point rather\n * than an oversight. This is the only one of the four that a BUYER is waiting\n * on: the row it drains is a paid order that has not been routed to whoever\n * ships it, and every minute it sits is a minute the parcel is not moving. The\n * first backoff step is also 60s, so a supplier that blipped is retried on the\n * next tick.\n *\n * The cost that set fifteen minutes for the recovery pair does not apply. Those\n * are collection-GROUP scans across every site on the platform; this is one\n * ordinary equality query against a top-level collection that is EMPTY in the\n * ordinary case — an empty result bills a single read, so the whole beat is\n * ~1,440 reads a day whether or not anyone is dropshipping.\n *\n * Not gated on `isEmailConfigured()`. The supplier notification is an HTTP POST\n * to the merchant's own supplier, and a self-host with no mail provider still\n * has dropship orders to route.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'supplier-webhook-delivery',\n intervalMinutes: 1,\n description:\n 'Deliver queued dropship supplier notifications, with backoff and a ' +\n 'dead letter that tells the merchant (AGL-2473).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n const scan = await scanSupplierDeliveries(gate)\n if (scan.deadLettered) {\n console.warn(\n `commerce: ${scan.deadLettered} dropship orders could not be routed ` +\n `to their supplier and have been reported on the order`,\n )\n }\n },\n})\n\n/**\n * Stripe payment method domains for every connected custom domain (AGL-3629).\n *\n * The backfill for domains connected before registration existed, and the\n * repair for a connect whose event was lost, in one daily pass. A domain\n * already registered costs a document read and no Stripe call, so the beat is\n * cheap; a day's delay costs only the Apple Pay button on a domain connected\n * while Stripe was unreachable, and the first in-page checkout there registers\n * it anyway.\n */\nregisterPluginJob({\n pluginId: BUNDLE_ID,\n name: 'payment-method-domains',\n intervalMinutes: 24 * 60,\n description:\n 'Register connected custom domains with Stripe so Apple Pay, Google Pay ' +\n 'and Link show on them (AGL-3629).',\n lockdown: { scope: 'per-host' },\n handler: async (gate) => {\n if (!process.env.STRIPE_SECRET_KEY) return\n const { reconcileCustomDomains } = await import('./server/payment-method-domains')\n const result = await reconcileCustomDomains(gate)\n if (result.registered || result.failed) {\n console.info(\n `commerce: payment method domains — ${result.registered} registered, ` +\n `${result.failed} failed across ${result.hosts} sites`,\n )\n }\n },\n})\n\n/**\n * A visitor's payment door: it opens a Stripe Checkout Session for whoever\n * calls it, so the dispatcher holds it to the card-testing counters\n * (AGL-3363). Declared here, beside the registration, so the door cannot\n * exist without it.\n */\nconst CARD_PAYMENT_DOOR = { cardPayment: true } as const\n\n/** Registers the commerce plugin's storefront API routes. */\nexport function registerCommerceApi(): void {\n registerProductCardReader()\n // The merchant's tax rule, for every plugin that prices a charge here.\n registerCommerceTaxProfile()\n registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Merchant-settable register discount ceiling (AGL-2161). Registered on\n // BOTH surfaces, like the permissions above: the POS route reads it\n // server-side, and the settings card renders it from the same schema.\n registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n // PDP/PLP template pages (AGL-418): /products/* + /collections/*.\n registerSitePageResolver(commerceSitePageResolver)\n // Seeds product grids on ordinary screens (AGL-659) — the resolver above\n // only covers /products/* and /collections/*, so /products itself, and any\n // designed page with a grid on it, needed this to render server-side.\n registerSitePageEnricher(commerceSitePageEnricher)\n registerPluginApiRoute('commerce/cart-checkout', cartCheckoutHandler, CARD_PAYMENT_DOOR)\n registerPluginApiRoute('commerce/cart', cartHandler)\n // What the cart offers besides shipping (AGL-3624): pickup locations, and\n // local delivery's fee, minimum and windows for a postal code.\n registerPluginApiRoute('commerce/local-fulfillment-options', async (req, res) =>\n (await import('./server/local-fulfillment')).localFulfillmentOptionsHandler(req, res),\n )\n // Optional lines another plugin offers at the cart (AGL-3635).\n registerPluginApiRoute('commerce/cart-extras', cartExtrasHandler)\n registerPluginApiRoute('commerce/catalog', catalogHandler)\n registerPluginApiRoute('commerce/checkout', checkoutHandler, CARD_PAYMENT_DOOR)\n registerPluginApiRoute('commerce/download', downloadHandler)\n // The pre-channels Google feed address (AGL-299), now written by the\n // catalog's feed publisher (AGL-3637); the module loads with a fetch.\n registerPluginApiRoute('commerce/feed', {\n web: async (request) => (await import('./server/legacy-feed')).legacyFeedRoute(request),\n })\n registerPluginApiRoute('commerce/newsletter', newsletterHandler)\n registerPluginApiRoute('commerce/notify-restock', notifyRestockHandler)\n // GA-safe order projection for the storefront `purchase` (AGL-1641).\n registerPluginApiRoute('commerce/order-analytics', orderAnalyticsHandler)\n // The guest order-status page's data (AGL-3610), behind the signed link in\n // every buyer email — a recipient link, so it outlives the site's gates.\n registerPluginApiRoute('commerce/order-status', orderStatusHandler, { recipientLink: true })\n // …and its \"Request a return\" button (AGL-3611), on an order that can\n // still send something back.\n registerOrderStatusActions(returnRequestStatusAction)\n // What became of a session the shopper was returned from (AGL-3606).\n registerPluginApiRoute('commerce/checkout-status', checkoutStatusHandler)\n registerPluginApiRoute('commerce/product', productHandler)\n registerPluginApiRoute('commerce/related', relatedHandler)\n registerPluginApiRoute('commerce/reservation-availability', reservationAvailabilityHandler)\n registerPluginApiRoute('commerce/reserve', reserveHandler, CARD_PAYMENT_DOOR)\n registerPluginApiRoute('commerce/gate', gateHandler)\n registerPluginApiRoute('commerce/member-feed', memberFeedHandler)\n registerPluginApiRoute('commerce/stream', streamHandler)\n registerPluginApiRoute('commerce/subscription-portal', subscriptionPortalHandler)\n registerPluginApiRoute('commerce/reviews', reviewsHandler)\n // A buyer's return request (AGL-3611), from their account or the signed\n // order-status link.\n registerPluginApiRoute('commerce/return-request', returnRequestHandler)\n registerPluginApiRoute('membership/account', membershipAccountHandler)\n registerPluginApiRoute('membership/content', membershipContentHandler)\n registerPluginApiRoute('membership/login', membershipLoginHandler)\n registerPluginApiRoute('membership/logout', membershipLogoutHandler)\n // Password recovery pair (AGL-552): request + complete.\n registerPluginApiRoute('membership/recover', membershipRecoverHandler)\n registerPluginApiRoute('membership/register', membershipRegisterHandler)\n registerPluginApiRoute('membership/reset', membershipResetHandler)\n registerPluginApiRoute('membership/wishlist', membershipWishlistHandler)\n}\n\n/**\n * Registers the commerce plugin's console-side API routes (AGL-396):\n * merchant/staff operations (Connect onboarding, refunds, draft & POS\n * orders, member posts), the supplier tracking callback, and the HTTP doors\n * for the abandoned-cart / restock passes.\n *\n * Those last two are NOT what schedules them — the `registerPluginJob` calls\n * above are (AGL-2227). This comment used to call them \"the scheduler-driven\n * jobs\", which is how they stayed dark for months: it asserted the wiring\n * instead of having it.\n */\nexport function registerCommerceConsoleApi(): void {\n // The register's gate (`managePos`) is resolved on THIS surface: every POS\n // route below runs in the console, and a key no surface registered here is\n // absent from the resolved map, which reads as refused — the site's own\n // owner was answered 403 at the register.\n registerPluginPermissions(COMMERCE_PERMISSIONS)\n // What a product looks like to a surface that is not this plugin's — a\n // campaign email that features one asks here rather than importing the model.\n registerProductCardReader()\n // …and a draft product another plugin asks for by name (AGL-3616): an AI\n // build setting a store up from a brief, unpriced unless the brief priced it.\n // The console runs AI jobs (AGL-3026).\n registerProductDraftWriter()\n // …and the operation an AI build plans for it, which that writer executes.\n registerProductAiCapability()\n // …and the store's shipping, pickup, local delivery and tax settings an AI\n // build sets up (AGL-3749), written live as the console's cards write them,\n // with the four operations that writer executes.\n registerStoreSetupAiWriter()\n registerStoreSetupAiCapabilities()\n // …and why a file somebody is SELLING may not be made public (AGL-3080).\n // The media library asks before it hands an asset its permanent CDN URL\n // back; what a product is, and which of its fields hold paid media, is\n // this plugin's to know.\n registerCommerceMediaPublishGuard()\n // …and which memberships a site sells, for a security lockdown to pause\n // (AGL-3364). The lockdown route runs on this surface.\n registerCommerceRecurringCharges()\n // …and the tax rule, for the webhook that confirms what they charged.\n registerCommerceTaxProfile()\n // …and its sales on the operator's own sales tax return (AGL-3080): the\n // tax a storefront checkout collected under the platform's registrations.\n // The staff return awaits this surface before it asks, and refuses to be\n // filed without this source — commerce declares `taxReturnSource`.\n registerTaxReturnSource(commerceTaxReturnSource, { pluginId: BUNDLE_ID })\n // …and the take its orders earn the operator, on the staff revenue report\n // (AGL-3080) — commerce declares `revenueSource`, so the report names this\n // source as unread rather than counting it as zero if it is missing.\n registerRevenueSource(commerceRevenueSource, { pluginId: BUNDLE_ID })\n // Stripe webhook sections (AGL-418): orders/carts/drafts/reservations/\n // subscriptions ride the platform webhook via the hook registry.\n registerBillingWebhookHandler(commerceBillingWebhookHandler)\n // Cancel + stock release in one transaction (AGL-1808). Server-side because\n // the release depends on the transition rule, and a client write could not\n // re-ask it under the same lock that flips the status.\n registerPluginApiRoute('commerce/cancel-order', cancelOrderHandler)\n // Whether live carrier rates can be offered on this site (AGL-3612).\n registerPluginApiRoute('commerce/shipping/carrier-rates', carrierRatesAvailabilityHandler)\n registerPluginApiRoute('commerce/connect', connectHandler)\n registerPluginApiRoute('commerce/draft-order', draftOrderHandler)\n // Fulfil + mark-delivered with the transition re-asked under the write\n // (AGL-1819) — the same stale-dialog hole cancel-order closes, minus the\n // stock release those two transitions never had. The customer REST API's\n // `PATCH /v1/sites/{id}/orders/{id}` records a shipment through the same\n // transaction (`server/api-v1/orders-and-products.ts`).\n registerPluginApiRoute('commerce/fulfill-order', fulfillOrderHandler)\n // Pickup and local delivery steps (AGL-3624) — ready, picked up, out for\n // delivery, delivered — from the console queue, the order dialog and the\n // native Aglyn app. Loaded on first call.\n registerPluginApiRoute('commerce/local-fulfillment', async (req, res) =>\n (await import('./server/local-fulfillment-status')).localFulfillmentHandler(req, res),\n )\n // \"Resend receipt\" from the order dialog (AGL-3610): by email, or by text\n // when the platform's SMS provider is configured. Rate-limited per member\n // and per order.\n registerPluginApiRoute('commerce/order-receipt-send', orderReceiptSendHandler)\n // The product editor's save and Adjust stock for the native apps (AGL-3652):\n // the console's own product-write computation under the rules' gate. Loaded\n // on first call.\n registerPluginApiRoute('commerce/products/save', async (req, res) =>\n (await import('./server/products-write')).productSaveHandler(req, res),\n )\n registerPluginApiRoute('commerce/products/stock', async (req, res) =>\n (await import('./server/products-write')).productStockHandler(req, res),\n )\n // The products list's Duplicate and Delete for the native apps (AGL-3841):\n // the console's own copy and soft delete, with the site-wide cache drop.\n registerPluginApiRoute('commerce/products/duplicate', async (req, res) =>\n (await import('./server/products-write')).productDuplicateHandler(req, res),\n )\n registerPluginApiRoute('commerce/products/delete', async (req, res) =>\n (await import('./server/products-write')).productDeleteHandler(req, res),\n )\n // The order dialog's note and restock answer for the native apps\n // (AGL-3651, AGL-3652): the console's own timeline computation, in one\n // transaction on the stored order. Loaded on first call.\n registerPluginApiRoute('commerce/order-note', async (req, res) =>\n (await import('./server/order-annotations')).orderNoteHandler(req, res),\n )\n registerPluginApiRoute('commerce/order-restock-answer', async (req, res) =>\n (await import('./server/order-annotations')).orderRestockAnswerHandler(req, res),\n )\n // The order dialog's Packing slip and Invoice for the native apps\n // (AGL-3841): the console's own two pages over the stored order, which the\n // apps print on the device's print sheet. Loaded on first call.\n registerPluginApiRoute('commerce/order-print', async (req, res) =>\n (await import('./server/order-print')).orderPrintHandler(req, res),\n )\n // Issue / void store credit (AGL-2226). Server-side because the host\n // catch-all in the Firestore rules would otherwise let a client write\n // its own `balanceCents`, which checkout applies as amount-off.\n registerPluginApiRoute('commerce/gift-cards', giftCardsHandler)\n // The merchant's own storefront sales tax, by who owes it (AGL-2440): the\n // same rows and classifier the operator's return reads, fenced to one site.\n registerPluginApiRoute('commerce/tax-summary', { web: taxSummaryHandler })\n // The payment methods card (AGL-3629): toggles, the platform's offer, the\n // payout account's capabilities and the site's wallet domains. Loaded with\n // the first request, so a console that never opens Settings never imports it.\n registerPluginApiRoute('commerce/payment-methods', {\n web: async (request) =>\n (await import('./server/payment-methods')).paymentMethodsHandler(request),\n })\n // A smart collection's rules changed or it was deleted: re-stamp which\n // products it holds (AGL-3321), the membership the storefront queries.\n registerPluginApiRoute('commerce/collection-membership', collectionMembershipHandler)\n // A site member's password help (AGL-914) and removal (AGL-3308), from the\n // Users page drawer and the Inbox. Console-auth, not the visitor cookie the\n // storefront's `membership/*` routes take, so they are served where the\n // console posts: the console's own dispatcher loads this surface only.\n registerPluginApiRoute(\n 'membership/admin-password',\n membershipAdminPasswordHandler,\n )\n registerPluginApiRoute('membership/admin-remove', membershipAdminRemoveHandler)\n registerPluginApiRoute('commerce/member-post', memberPostHandler)\n registerPluginApiRoute('commerce/pos-order', posOrderHandler)\n // A register's cloud receipt printers and the jobs a manager sends them\n // (AGL-3619), and the two doors the printers themselves poll. The doors are\n // MACHINE routes: a printer is no member and names no site cookie, so it\n // authenticates with the per-printer secret in its URL and its own device id,\n // and the route asks the plan before it hands over a job. Loaded with the\n // first poll, so a console that has no printers never imports them.\n registerPluginApiRoute('commerce/printers', printersHandler)\n registerPluginApiRoute(\n 'commerce/cloudprnt/:hostId/:printerId/:secret',\n {\n web: async (request, context) =>\n (await import('./server/printer-poll')).cloudPrntRoute(request, context),\n },\n { machine: true },\n )\n registerPluginApiRoute(\n 'commerce/epson-sdp/:hostId/:printerId/:secret',\n {\n web: async (request, context) =>\n (await import('./server/printer-poll')).epsonServerDirectPrintRoute(request, context),\n },\n { machine: true },\n )\n // The register's tenders against an open sale, its Stripe Terminal card\n // readers, and the paired customer display (AGL-3607, AGL-3608). The\n // display's `pair`/`poll`/`respond` take a display token and no console\n // session; every other action is gated like a sale.\n registerPluginApiRoute('commerce/pos-payment', posPaymentHandler)\n registerPluginApiRoute('commerce/pos-readers', posReadersHandler)\n registerPluginApiRoute('commerce/pos-display', posDisplayHandler)\n // The self-service kiosk (AGL-3623): a register's paired screen where the\n // customer orders and pays. Its device token, never a staff session; the\n // register's queue of \"pay at counter\" orders is gated like a sale.\n registerPluginApiRoute('commerce/pos-kiosk', posKioskHandler)\n // Shifts, staff PINs, the customer lookup and returns (AGL-3609).\n registerPosOpsRoutes()\n // The offline register (AGL-3625): the kit a register caches to sell cash\n // while the connection is down, and the sync that records those sales once.\n // Gated like a sale; loaded with its first call.\n registerPluginApiRoute('commerce/pos-offline-sync', async (req, res) =>\n (await import('./server/pos-offline-sync')).posOfflineSyncHandler(req, res),\n )\n // The native Aglyn POS app's Stripe Terminal SDK: a connection token scoped\n // to the site's Location, and the Location itself (AGL-3618). Gated like a\n // sale.\n registerPluginApiRoute('commerce/pos-terminal-connection-token', posTerminalConnectionTokenHandler)\n registerPluginApiRoute('commerce/process-abandoned', processAbandonedHandler)\n registerPluginApiRoute('commerce/process-restock', processRestockHandler)\n registerPluginApiRoute('commerce/refund', refundHandler)\n // Returns (AGL-3611): approve, decline, receive with restock, refund\n // through the route above, a label from a shipping plugin.\n registerPluginApiRoute('commerce/returns', returnsHandler)\n // The merchant's outbound order webhooks (AGL-3611): endpoints, secrets,\n // a test ping and a resend from the delivery log.\n registerPluginApiRoute('commerce/order-webhooks', orderWebhooksHandler)\n registerPluginApiRoute('commerce/supplier-update', supplierUpdateHandler)\n // Stamps the open orders that predate the shipping fields, before an\n // export for shipping (AGL-3613).\n registerPluginApiRoute('commerce/orders-shipping-prepare', async (req, res) =>\n (await import('./server/orders-shipping-prepare')).ordersShippingPrepareHandler(req, res),\n )\n // ShipStation's Custom Store endpoint (AGL-3613): ShipStation's servers\n // pull the order feed and post shipments back with the site's own Basic\n // credentials, so it is a MACHINE's route and asks the site's commerce,\n // plan, release and lockdown gates itself once the credentials prove the\n // site. Loaded on its first call, never with the console API surface.\n registerPluginApiRoute(\n 'commerce/shipstation/:hostId',\n {\n web: async (request, context) =>\n (await import('./server/shipstation')).shipStationRoute(request, context),\n },\n { machine: true },\n )\n // ShippingEasy's shipment callback (AGL-3633): ShippingEasy's servers post\n // each label bought for an order this store sent them, signed with the\n // merchant's own API secret, so it is a MACHINE's route like ShipStation's\n // above and asks the same gates once the signature proves the site.\n registerPluginApiRoute(\n 'commerce/shippingeasy/:hostId',\n {\n web: async (request, context) =>\n (await import('./server/shippingeasy')).shippingEasyRoute(request, context),\n },\n { machine: true },\n )\n // The ShipStation and ShippingEasy cards' connect, show, new password,\n // send open orders and disconnect.\n registerPluginApiRoute('commerce/shipping-connectors', async (req, res) =>\n (await import('./server/shipping-connectors')).shippingConnectorsHandler(req, res),\n )\n // The store's sales as figure tables (AGL-2915), for the AI plugin's\n // insights to read by id rather than by reading orders. The console runs\n // insight jobs, so the console surface registers them.\n registerOrderFigureReaders(() => firebaseAdmin.app().firestore())\n}\n\n// Shared with the (still app-side) membership/account route.\nexport { mintDownloadToken } from './server/download'\n\n// Site-member session primitives, shared with the (still app-side)\n// membership/* routes until those migrate too (AGL-396).\nexport * from './server/membership'\n\n// Type-only (AGL-3080): the plugin's entitlement keys, declared by module\n// augmentation, for every program that loads this entry point.\nexport type { commercePlanEntitlements } from './plan-entitlements'\n"],"names":["registerBillingWebhookHandler","registerPluginApiRoute","registerPluginJob","registerSitePageEnricher","registerSitePageResolver","registerPluginPermissions","registerPluginConfigSchema","isEmailConfigured","firebaseAdmin","BUNDLE_ID","registerCommerceMediaPublishGuard","registerCommerceRecurringCharges","registerProductCardReader","registerProductAiCapability","registerStoreSetupAiCapabilities","registerStoreSetupAiWriter","registerProductDraftWriter","registerCommerceTaxProfile","registerTaxReturnSource","commerceTaxReturnSource","taxSummaryHandler","registerRevenueSource","commerceRevenueSource","commerceBillingWebhookHandler","registerOrderFigureReaders","COMMERCE_PERMISSIONS","COMMERCE_CONFIG_SCHEMA","commerceSitePageEnricher","commerceSitePageResolver","cartCheckoutHandler","cartHandler","cartExtrasHandler","catalogHandler","checkoutHandler","downloadHandler","newsletterHandler","notifyRestockHandler","productHandler","relatedHandler","reservationAvailabilityHandler","gateHandler","memberFeedHandler","membershipAccountHandler","membershipAdminPasswordHandler","membershipAdminRemoveHandler","membershipContentHandler","membershipLoginHandler","membershipLogoutHandler","membershipRecoverHandler","membershipRegisterHandler","membershipResetHandler","membershipWishlistHandler","reserveHandler","streamHandler","subscriptionPortalHandler","reviewsHandler","connectHandler","cancelOrderHandler","carrierRatesAvailabilityHandler","collectionMembershipHandler","draftOrderHandler","fulfillOrderHandler","orderReceiptSendHandler","orderStatusHandler","registerOrderStatusActions","returnRequestStatusAction","giftCardsHandler","memberPostHandler","orderAnalyticsHandler","checkoutStatusHandler","posOrderHandler","printersHandler","posPaymentHandler","posReadersHandler","posTerminalConnectionTokenHandler","posDisplayHandler","posKioskHandler","registerPosOpsRoutes","processAbandonedHandler","scanAbandonedCheckouts","processRestockHandler","scanRestockAlerts","scanStockDecrements","refundHandler","orderWebhooksHandler","returnRequestHandler","returnsHandler","scanSupplierDeliveries","supplierUpdateHandler","RECOVERY_JOB_INTERVAL_MINUTES","pluginId","name","intervalMinutes","description","lockdown","scope","handler","gate","sent","console","info","scanReviewRequests","scan","missingLines","truncatedHosts","warn","hosts","reportedOrders","deadLettered","process","env","STRIPE_SECRET_KEY","reconcileCustomDomains","result","registered","failed","CARD_PAYMENT_DOOR","cardPayment","registerCommerceApi","req","res","localFulfillmentOptionsHandler","web","request","legacyFeedRoute","recipientLink","registerCommerceConsoleApi","localFulfillmentHandler","productSaveHandler","productStockHandler","productDuplicateHandler","productDeleteHandler","orderNoteHandler","orderRestockAnswerHandler","orderPrintHandler","paymentMethodsHandler","context","cloudPrntRoute","machine","epsonServerDirectPrintRoute","posOfflineSyncHandler","ordersShippingPrepareHandler","shipStationRoute","shippingEasyRoute","shippingConnectorsHandler","app","firestore","mintDownloadToken"],"mappings":"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"}
|
|
@@ -21,3 +21,19 @@ export declare const EASYPOST_ORDER_PRESET: TransferResourcePreset;
|
|
|
21
21
|
export declare const GENERIC_SHIPPING_ORDER_PRESET: TransferResourcePreset;
|
|
22
22
|
/** The shipping presets, in the order the export dialog lists them. */
|
|
23
23
|
export declare const ORDER_SHIPPING_PRESETS: readonly TransferResourcePreset[];
|
|
24
|
+
/** One of the shipping exports as the apps list it: the preset's id, its name and what it is for. */
|
|
25
|
+
export interface ShippingPresetChoice {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
/** The shipping exports the orders list's "Export for shipping" offers, as the apps list them. */
|
|
31
|
+
export declare const ORDER_SHIPPING_PRESET_CHOICES: readonly ShippingPresetChoice[];
|
|
32
|
+
/** What the shipping export's filter is called in "The current filter: …". */
|
|
33
|
+
export declare const ORDERS_TO_SHIP_FILTER_LABEL = "orders still to ship";
|
|
34
|
+
/**
|
|
35
|
+
* The orders still to ship (AGL-3613): paid or partly fulfilled orders that
|
|
36
|
+
* need shipping, newest first, as the orders resource's `readPage` reads a
|
|
37
|
+
* filter.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ORDERS_TO_SHIP_FILTER_VALUE: unknown;
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
/*
|
|
17
18
|
* THE SHIPPING PRESETS (AGL-3613): the orders a label tool still has to ship,
|
|
18
19
|
* laid out under each tool's own column names so the file uploads without
|
|
19
20
|
* remapping. Verified 2026-10-06 against each tool's help:
|
|
@@ -152,5 +153,37 @@ export const GENERIC_SHIPPING_ORDER_PRESET = {
|
|
|
152
153
|
EASYPOST_ORDER_PRESET,
|
|
153
154
|
GENERIC_SHIPPING_ORDER_PRESET
|
|
154
155
|
];
|
|
156
|
+
/** The shipping exports the orders list's "Export for shipping" offers, as the apps list them. */ export const ORDER_SHIPPING_PRESET_CHOICES = ORDER_SHIPPING_PRESETS.map(({ id, label, description })=>_extends({
|
|
157
|
+
id,
|
|
158
|
+
label
|
|
159
|
+
}, description ? {
|
|
160
|
+
description
|
|
161
|
+
} : {}));
|
|
162
|
+
/** What the shipping export's filter is called in "The current filter: …". */ export const ORDERS_TO_SHIP_FILTER_LABEL = 'orders still to ship';
|
|
163
|
+
/**
|
|
164
|
+
* The orders still to ship (AGL-3613): paid or partly fulfilled orders that
|
|
165
|
+
* need shipping, newest first, as the orders resource's `readPage` reads a
|
|
166
|
+
* filter.
|
|
167
|
+
*/ export const ORDERS_TO_SHIP_FILTER_VALUE = {
|
|
168
|
+
filters: [
|
|
169
|
+
{
|
|
170
|
+
path: 'requiresShipping',
|
|
171
|
+
op: '==',
|
|
172
|
+
value: true
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
path: 'status',
|
|
176
|
+
op: 'in',
|
|
177
|
+
value: [
|
|
178
|
+
'paid',
|
|
179
|
+
'partially_fulfilled'
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
orderBy: {
|
|
184
|
+
path: 'createdAtMs',
|
|
185
|
+
direction: 'desc'
|
|
186
|
+
}
|
|
187
|
+
};
|
|
155
188
|
|
|
156
189
|
//# sourceMappingURL=shipping-presets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/shipping-presets.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 type { TransferResourcePreset } from '@aglyn/aglyn/data-transfer'\n\n/*\n * THE SHIPPING PRESETS (AGL-3613): the orders a label tool still has to ship,\n * laid out under each tool's own column names so the file uploads without\n * remapping. Verified 2026-10-06 against each tool's help:\n *\n * - Pirate Ship takes any spreadsheet and maps its columns on the first\n * upload; weight in ounces OR pounds, numbers only.\n * - Shippo's order CSV template (`shippo_sample_csv_v3.csv`).\n * - EasyPost's label CSV: `to_address.*`, `parcel.weight_oz`, `reference`.\n * It also needs the sender, carrier and service, which a store's export\n * cannot know; the guide says to add them before uploading.\n */\nexport const PIRATE_SHIP_ORDER_PRESET: TransferResourcePreset = {\n id: 'pirate-ship',\n label: 'Pirate Ship',\n description: 'Pirate Ship’s spreadsheet upload: one row per order to ship, weight in ounces.',\n fieldIds: ['orderRef', 'shipName', 'shipEmail', 'shipPhone', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'weightOz', 'itemsToShip'],\n headers: {\n orderRef: 'Order ID',\n shipName: 'Name',\n shipEmail: 'Email',\n shipPhone: 'Phone',\n shipLine1: 'Address 1',\n shipLine2: 'Address 2',\n shipCity: 'City',\n shipState: 'State',\n shipPostalCode: 'Zip',\n shipCountry: 'Country',\n weightOz: 'Ounces',\n itemsToShip: 'Items',\n },\n}\n\nexport const SHIPPO_ORDER_PRESET: TransferResourcePreset = {\n id: 'shippo',\n label: 'Shippo',\n description: 'Shippo’s order CSV: its column names, with the order weight in ounces.',\n fieldIds: ['orderRef', 'shipName', 'shipEmail', 'shipPhone', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'weightOz', 'weightUnit', 'itemsToShip'],\n headers: {\n orderRef: 'Order Number',\n shipName: 'Recipient Name',\n shipEmail: 'Email',\n shipPhone: 'Phone',\n shipLine1: 'Street Line 1',\n shipLine2: 'Street Line 2',\n shipCity: 'City',\n shipState: 'State/Province',\n shipPostalCode: 'Zip/Postal Code',\n shipCountry: 'Country',\n weightOz: 'Order Weight',\n weightUnit: 'Order Weight Unit',\n itemsToShip: 'Item Title',\n },\n}\n\nexport const EASYPOST_ORDER_PRESET: TransferResourcePreset = {\n id: 'easypost',\n label: 'EasyPost',\n description: 'EasyPost’s label CSV: the recipient and the parcel weight. Add your from address, carrier and service before uploading.',\n fieldIds: ['orderRef', 'shipName', 'shipPhone', 'shipEmail', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'weightOz'],\n headers: {\n orderRef: 'reference',\n shipName: 'to_address.name',\n shipPhone: 'to_address.phone',\n shipEmail: 'to_address.email',\n shipLine1: 'to_address.street1',\n shipLine2: 'to_address.street2',\n shipCity: 'to_address.city',\n shipState: 'to_address.state',\n shipPostalCode: 'to_address.zip',\n shipCountry: 'to_address.country',\n weightOz: 'parcel.weight_oz',\n },\n}\n\nexport const GENERIC_SHIPPING_ORDER_PRESET: TransferResourcePreset = {\n id: 'shipping',\n label: 'Shipping (any tool)',\n description: 'Every shipping column under its own name, for any tool that maps columns on upload.',\n fieldIds: ['orderRef', 'date', 'shipName', 'shipEmail', 'shipPhone', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'itemsToShip', 'unitsToShip', 'weightOz', 'weightLb', 'total'],\n}\n\n/** The shipping presets, in the order the export dialog lists them. */\nexport const ORDER_SHIPPING_PRESETS: readonly TransferResourcePreset[] = [\n PIRATE_SHIP_ORDER_PRESET,\n SHIPPO_ORDER_PRESET,\n EASYPOST_ORDER_PRESET,\n GENERIC_SHIPPING_ORDER_PRESET,\n]\n"],"names":["PIRATE_SHIP_ORDER_PRESET","id","label","description","fieldIds","headers","orderRef","shipName","shipEmail","shipPhone","shipLine1","shipLine2","shipCity","shipState","shipPostalCode","shipCountry","weightOz","itemsToShip","SHIPPO_ORDER_PRESET","weightUnit","EASYPOST_ORDER_PRESET","GENERIC_SHIPPING_ORDER_PRESET","ORDER_SHIPPING_PRESETS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/shipping-presets.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 type { TransferResourcePreset } from '@aglyn/aglyn/data-transfer'\n\n/*\n * THE SHIPPING PRESETS (AGL-3613): the orders a label tool still has to ship,\n * laid out under each tool's own column names so the file uploads without\n * remapping. Verified 2026-10-06 against each tool's help:\n *\n * - Pirate Ship takes any spreadsheet and maps its columns on the first\n * upload; weight in ounces OR pounds, numbers only.\n * - Shippo's order CSV template (`shippo_sample_csv_v3.csv`).\n * - EasyPost's label CSV: `to_address.*`, `parcel.weight_oz`, `reference`.\n * It also needs the sender, carrier and service, which a store's export\n * cannot know; the guide says to add them before uploading.\n */\nexport const PIRATE_SHIP_ORDER_PRESET: TransferResourcePreset = {\n id: 'pirate-ship',\n label: 'Pirate Ship',\n description: 'Pirate Ship’s spreadsheet upload: one row per order to ship, weight in ounces.',\n fieldIds: ['orderRef', 'shipName', 'shipEmail', 'shipPhone', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'weightOz', 'itemsToShip'],\n headers: {\n orderRef: 'Order ID',\n shipName: 'Name',\n shipEmail: 'Email',\n shipPhone: 'Phone',\n shipLine1: 'Address 1',\n shipLine2: 'Address 2',\n shipCity: 'City',\n shipState: 'State',\n shipPostalCode: 'Zip',\n shipCountry: 'Country',\n weightOz: 'Ounces',\n itemsToShip: 'Items',\n },\n}\n\nexport const SHIPPO_ORDER_PRESET: TransferResourcePreset = {\n id: 'shippo',\n label: 'Shippo',\n description: 'Shippo’s order CSV: its column names, with the order weight in ounces.',\n fieldIds: ['orderRef', 'shipName', 'shipEmail', 'shipPhone', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'weightOz', 'weightUnit', 'itemsToShip'],\n headers: {\n orderRef: 'Order Number',\n shipName: 'Recipient Name',\n shipEmail: 'Email',\n shipPhone: 'Phone',\n shipLine1: 'Street Line 1',\n shipLine2: 'Street Line 2',\n shipCity: 'City',\n shipState: 'State/Province',\n shipPostalCode: 'Zip/Postal Code',\n shipCountry: 'Country',\n weightOz: 'Order Weight',\n weightUnit: 'Order Weight Unit',\n itemsToShip: 'Item Title',\n },\n}\n\nexport const EASYPOST_ORDER_PRESET: TransferResourcePreset = {\n id: 'easypost',\n label: 'EasyPost',\n description: 'EasyPost’s label CSV: the recipient and the parcel weight. Add your from address, carrier and service before uploading.',\n fieldIds: ['orderRef', 'shipName', 'shipPhone', 'shipEmail', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'weightOz'],\n headers: {\n orderRef: 'reference',\n shipName: 'to_address.name',\n shipPhone: 'to_address.phone',\n shipEmail: 'to_address.email',\n shipLine1: 'to_address.street1',\n shipLine2: 'to_address.street2',\n shipCity: 'to_address.city',\n shipState: 'to_address.state',\n shipPostalCode: 'to_address.zip',\n shipCountry: 'to_address.country',\n weightOz: 'parcel.weight_oz',\n },\n}\n\nexport const GENERIC_SHIPPING_ORDER_PRESET: TransferResourcePreset = {\n id: 'shipping',\n label: 'Shipping (any tool)',\n description: 'Every shipping column under its own name, for any tool that maps columns on upload.',\n fieldIds: ['orderRef', 'date', 'shipName', 'shipEmail', 'shipPhone', 'shipLine1', 'shipLine2', 'shipCity', 'shipState', 'shipPostalCode', 'shipCountry', 'itemsToShip', 'unitsToShip', 'weightOz', 'weightLb', 'total'],\n}\n\n/** The shipping presets, in the order the export dialog lists them. */\nexport const ORDER_SHIPPING_PRESETS: readonly TransferResourcePreset[] = [\n PIRATE_SHIP_ORDER_PRESET,\n SHIPPO_ORDER_PRESET,\n EASYPOST_ORDER_PRESET,\n GENERIC_SHIPPING_ORDER_PRESET,\n]\n\n/** One of the shipping exports as the apps list it: the preset's id, its name and what it is for. */\nexport interface ShippingPresetChoice {\n id: string\n label: string\n description?: string\n}\n\n/** The shipping exports the orders list's \"Export for shipping\" offers, as the apps list them. */\nexport const ORDER_SHIPPING_PRESET_CHOICES: readonly ShippingPresetChoice[] = ORDER_SHIPPING_PRESETS.map(\n ({ id, label, description }) => ({ id, label, ...(description ? { description } : {}) }),\n)\n\n/** What the shipping export's filter is called in \"The current filter: …\". */\nexport const ORDERS_TO_SHIP_FILTER_LABEL = 'orders still to ship'\n\n/**\n * The orders still to ship (AGL-3613): paid or partly fulfilled orders that\n * need shipping, newest first, as the orders resource's `readPage` reads a\n * filter.\n */\nexport const ORDERS_TO_SHIP_FILTER_VALUE: unknown = {\n filters: [\n { path: 'requiresShipping', op: '==', value: true },\n { path: 'status', op: 'in', value: ['paid', 'partially_fulfilled'] },\n ],\n orderBy: { path: 'createdAtMs', direction: 'desc' },\n}\n"],"names":["PIRATE_SHIP_ORDER_PRESET","id","label","description","fieldIds","headers","orderRef","shipName","shipEmail","shipPhone","shipLine1","shipLine2","shipCity","shipState","shipPostalCode","shipCountry","weightOz","itemsToShip","SHIPPO_ORDER_PRESET","weightUnit","EASYPOST_ORDER_PRESET","GENERIC_SHIPPING_ORDER_PRESET","ORDER_SHIPPING_PRESETS","ORDER_SHIPPING_PRESET_CHOICES","map","ORDERS_TO_SHIP_FILTER_LABEL","ORDERS_TO_SHIP_FILTER_VALUE","filters","path","op","value","orderBy","direction"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAID;;;;;;;;;;;CAWC,GACD,OAAO,MAAMA,2BAAmD;IAC9DC,IAAI;IACJC,OAAO;IACPC,aAAa;IACbC,UAAU;QAAC;QAAY;QAAY;QAAa;QAAa;QAAa;QAAa;QAAY;QAAa;QAAkB;QAAe;QAAY;KAAc;IAC3KC,SAAS;QACPC,UAAU;QACVC,UAAU;QACVC,WAAW;QACXC,WAAW;QACXC,WAAW;QACXC,WAAW;QACXC,UAAU;QACVC,WAAW;QACXC,gBAAgB;QAChBC,aAAa;QACbC,UAAU;QACVC,aAAa;IACf;AACF,EAAC;AAED,OAAO,MAAMC,sBAA8C;IACzDjB,IAAI;IACJC,OAAO;IACPC,aAAa;IACbC,UAAU;QAAC;QAAY;QAAY;QAAa;QAAa;QAAa;QAAa;QAAY;QAAa;QAAkB;QAAe;QAAY;QAAc;KAAc;IACzLC,SAAS;QACPC,UAAU;QACVC,UAAU;QACVC,WAAW;QACXC,WAAW;QACXC,WAAW;QACXC,WAAW;QACXC,UAAU;QACVC,WAAW;QACXC,gBAAgB;QAChBC,aAAa;QACbC,UAAU;QACVG,YAAY;QACZF,aAAa;IACf;AACF,EAAC;AAED,OAAO,MAAMG,wBAAgD;IAC3DnB,IAAI;IACJC,OAAO;IACPC,aAAa;IACbC,UAAU;QAAC;QAAY;QAAY;QAAa;QAAa;QAAa;QAAa;QAAY;QAAa;QAAkB;QAAe;KAAW;IAC5JC,SAAS;QACPC,UAAU;QACVC,UAAU;QACVE,WAAW;QACXD,WAAW;QACXE,WAAW;QACXC,WAAW;QACXC,UAAU;QACVC,WAAW;QACXC,gBAAgB;QAChBC,aAAa;QACbC,UAAU;IACZ;AACF,EAAC;AAED,OAAO,MAAMK,gCAAwD;IACnEpB,IAAI;IACJC,OAAO;IACPC,aAAa;IACbC,UAAU;QAAC;QAAY;QAAQ;QAAY;QAAa;QAAa;QAAa;QAAa;QAAY;QAAa;QAAkB;QAAe;QAAe;QAAe;QAAY;QAAY;KAAQ;AACzN,EAAC;AAED,qEAAqE,GACrE,OAAO,MAAMkB,yBAA4D;IACvEtB;IACAkB;IACAE;IACAC;CACD,CAAA;AASD,gGAAgG,GAChG,OAAO,MAAME,gCAAiED,uBAAuBE,GAAG,CACtG,CAAC,EAAEvB,EAAE,EAAEC,KAAK,EAAEC,WAAW,EAAE,GAAM;QAAEF;QAAIC;OAAWC,cAAc;QAAEA;IAAY,IAAI,CAAC,IACpF;AAED,4EAA4E,GAC5E,OAAO,MAAMsB,8BAA8B,uBAAsB;AAEjE;;;;CAIC,GACD,OAAO,MAAMC,8BAAuC;IAClDC,SAAS;QACP;YAAEC,MAAM;YAAoBC,IAAI;YAAMC,OAAO;QAAK;QAClD;YAAEF,MAAM;YAAUC,IAAI;YAAMC,OAAO;gBAAC;gBAAQ;aAAsB;QAAC;KACpE;IACDC,SAAS;QAAEH,MAAM;QAAeI,WAAW;IAAO;AACpD,EAAC"}
|
|
@@ -37,6 +37,11 @@ export interface OrderInvoiceStore {
|
|
|
37
37
|
/** The store's terms address, printed at the foot. */
|
|
38
38
|
termsUrl?: string | null;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The invoice's store from the site's document and its store settings, as
|
|
42
|
+
* the order dialog's Invoice button and `commerce/order-print` read them.
|
|
43
|
+
*/
|
|
44
|
+
export declare function invoiceStoreFrom(host: Record<string, unknown> | undefined | null, store: Record<string, unknown> | undefined | null): OrderInvoiceStore;
|
|
40
45
|
type InvoiceOrder = Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'totals' | 'refundedCents' | 'customerName' | 'customerEmail' | 'shippingAddress' | 'billingAddress'> & {
|
|
41
46
|
createdAtMs?: number;
|
|
42
47
|
currency?: string;
|
|
@@ -15,6 +15,20 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { formatOrderNumber, orderCreatedAtMs } from "../model/commerce-orders.js";
|
|
17
17
|
import { escapeHtml } from "./escape-html.js";
|
|
18
|
+
/**
|
|
19
|
+
* The invoice's store from the site's document and its store settings, as
|
|
20
|
+
* the order dialog's Invoice button and `commerce/order-print` read them.
|
|
21
|
+
*/ export function invoiceStoreFrom(host, store) {
|
|
22
|
+
var _ref, _ref1, _hostData_businessName, _storeData_currency, _storeData_receiptFooter, _storeData_termsUrl;
|
|
23
|
+
const hostData = host != null ? host : {};
|
|
24
|
+
const storeData = store != null ? store : {};
|
|
25
|
+
return {
|
|
26
|
+
name: String((_ref = (_ref1 = (_hostData_businessName = hostData['businessName']) != null ? _hostData_businessName : hostData['displayName']) != null ? _ref1 : hostData['name']) != null ? _ref : ''),
|
|
27
|
+
currency: String((_storeData_currency = storeData['currency']) != null ? _storeData_currency : 'USD'),
|
|
28
|
+
footer: (_storeData_receiptFooter = storeData['receiptFooter']) != null ? _storeData_receiptFooter : null,
|
|
29
|
+
termsUrl: (_storeData_termsUrl = storeData['termsUrl']) != null ? _storeData_termsUrl : null
|
|
30
|
+
};
|
|
31
|
+
}
|
|
18
32
|
/** Money in the order's currency, integer cents in. */ export function invoiceMoney(cents, currency) {
|
|
19
33
|
const amount = (Number(cents) || 0) / 100;
|
|
20
34
|
try {
|