@aglyn/plugins-commerce 1.0.0-beta.290 → 1.0.0-beta.292
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/README.md +4 -2
- package/package.json +13 -13
- package/src/lib/components/cart-fulfillment.d.ts +3 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -1
- package/src/lib/components/cart.js +14 -4
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +2 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.d.ts +6 -0
- package/src/lib/components/console/host-orders-card.component.js +3 -2
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/invoice-send.component.d.ts +44 -0
- package/src/lib/components/console/invoice-send.component.js +237 -0
- package/src/lib/components/console/invoice-send.component.js.map +1 -0
- package/src/lib/components/console/order-detail-dialog.component.d.ts +5 -0
- package/src/lib/components/console/order-detail-dialog.component.js +9 -2
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-invoice.component.d.ts +9 -0
- package/src/lib/components/console/order-invoice.component.js +27 -2
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +1 -1
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
- package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
- package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +31 -5
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
- package/src/lib/components/console/product-register-fields.component.js +8 -197
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +6 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
- package/src/lib/components/console/quote-editor-dialog.component.js +102 -4
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
- package/src/lib/components/console/quote-pay-link.component.js +29 -3
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
- package/src/lib/components/console/quote-proofs.component.d.ts +11 -0
- package/src/lib/components/console/quote-proofs.component.js +68 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -1
- package/src/lib/components/console/quote-send.component.d.ts +42 -0
- package/src/lib/components/console/quote-send.component.js +304 -0
- package/src/lib/components/console/quote-send.component.js.map +1 -0
- package/src/lib/components/console/quote-start.component.d.ts +45 -0
- package/src/lib/components/console/quote-start.component.js +182 -0
- package/src/lib/components/console/quote-start.component.js.map +1 -0
- package/src/lib/components/product-add-ons.d.ts +28 -0
- package/src/lib/components/product-add-ons.js +136 -0
- package/src/lib/components/product-add-ons.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +16 -0
- package/src/lib/components/product-detail.js +112 -19
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +8 -2
- package/src/lib/components/product-grid.js +6 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/components/product-options-field-body.d.ts +44 -0
- package/src/lib/components/product-options-field-body.js +260 -0
- package/src/lib/components/product-options-field-body.js.map +1 -0
- package/src/lib/components/product-options-field.d.ts +32 -0
- package/src/lib/components/product-options-field.js +128 -0
- package/src/lib/components/product-options-field.js.map +1 -0
- package/src/lib/components/quote-page-body.js +39 -20
- package/src/lib/components/quote-page-body.js.map +1 -1
- package/src/lib/model/commerce-cart.d.ts +15 -3
- package/src/lib/model/commerce-cart.js +10 -3
- package/src/lib/model/commerce-cart.js.map +1 -1
- package/src/lib/model/commerce-invoices.d.ts +139 -0
- package/src/lib/model/commerce-invoices.js +343 -0
- package/src/lib/model/commerce-invoices.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +35 -5
- package/src/lib/model/commerce-orders.js +3 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
- package/src/lib/model/commerce-pos-kiosk.js +3 -2
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
- package/src/lib/model/commerce-product-field.d.ts +23 -0
- package/src/lib/model/commerce-product-field.js +26 -0
- package/src/lib/model/commerce-product-field.js.map +1 -0
- package/src/lib/model/commerce-quote-email.d.ts +81 -0
- package/src/lib/model/commerce-quote-email.js +170 -0
- package/src/lib/model/commerce-quote-email.js.map +1 -0
- package/src/lib/model/commerce-quote-pay.d.ts +49 -6
- package/src/lib/model/commerce-quote-pay.js +69 -36
- package/src/lib/model/commerce-quote-pay.js.map +1 -1
- package/src/lib/model/commerce-quote-request.d.ts +93 -0
- package/src/lib/model/commerce-quote-request.js +207 -0
- package/src/lib/model/commerce-quote-request.js.map +1 -0
- package/src/lib/model/commerce-quotes.d.ts +81 -0
- package/src/lib/model/commerce-quotes.js +1 -1
- package/src/lib/model/commerce-quotes.js.map +1 -1
- package/src/lib/model/commerce.d.ts +18 -0
- package/src/lib/model/commerce.js +26 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +3 -0
- package/src/lib/model/index.js +3 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/product-modifiers.d.ts +74 -4
- package/src/lib/model/product-modifiers.js +131 -10
- package/src/lib/model/product-modifiers.js.map +1 -1
- package/src/lib/plugin.js +19 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +164 -98
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +14 -2
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.d.ts +5 -0
- package/src/lib/server/cart.js +41 -4
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +2 -0
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +3 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +28 -3
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/commerce-pdf.d.ts +5 -1
- package/src/lib/server/commerce-pdf.js +37 -10
- package/src/lib/server/commerce-pdf.js.map +1 -1
- package/src/lib/server/draft-order.js +23 -3
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/invoice-email.d.ts +76 -0
- package/src/lib/server/invoice-email.js +183 -0
- package/src/lib/server/invoice-email.js.map +1 -0
- package/src/lib/server/local-fulfillment.js +5 -1
- package/src/lib/server/local-fulfillment.js.map +1 -1
- package/src/lib/server/order-invoice-send.d.ts +43 -0
- package/src/lib/server/order-invoice-send.js +218 -0
- package/src/lib/server/order-invoice-send.js.map +1 -0
- package/src/lib/server/order-status.d.ts +11 -0
- package/src/lib/server/order-status.js +44 -8
- package/src/lib/server/order-status.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +3 -2
- package/src/lib/server/person-eraser.js +25 -6
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-offline-sync.js +1 -1
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-order.js +1 -1
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/product.d.ts +26 -0
- package/src/lib/server/product.js +18 -0
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.js +5 -2
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/quote-email.d.ts +85 -0
- package/src/lib/server/quote-email.js +211 -0
- package/src/lib/server/quote-email.js.map +1 -0
- package/src/lib/server/quote-link-token.d.ts +11 -0
- package/src/lib/server/quote-link-token.js +21 -0
- package/src/lib/server/quote-link-token.js.map +1 -1
- package/src/lib/server/quote-pay.d.ts +8 -3
- package/src/lib/server/quote-pay.js +105 -43
- package/src/lib/server/quote-pay.js.map +1 -1
- package/src/lib/server/quote.d.ts +11 -0
- package/src/lib/server/quote.js +309 -12
- package/src/lib/server/quote.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +2 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +5 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/site.js +8 -0
- package/src/lib/site.js.map +1 -1
- package/src/lib/tenant-emails.js +243 -0
- package/src/lib/tenant-emails.js.map +1 -1
- package/src/lib/utils/add-to-cart.d.ts +8 -0
- package/src/lib/utils/add-to-cart.js +10 -2
- package/src/lib/utils/add-to-cart.js.map +1 -1
- package/src/lib/utils/commerce-document-pdf.d.ts +5 -0
- package/src/lib/utils/commerce-document-pdf.js +6 -1
- package/src/lib/utils/commerce-document-pdf.js.map +1 -1
- package/src/lib/utils/invoice-email-content.d.ts +45 -0
- package/src/lib/utils/invoice-email-content.js +126 -0
- package/src/lib/utils/invoice-email-content.js.map +1 -0
- package/src/lib/utils/order-invoice.d.ts +1 -1
- package/src/lib/utils/order-invoice.js +14 -6
- package/src/lib/utils/order-invoice.js.map +1 -1
- package/src/lib/utils/quote-email-content.d.ts +63 -0
- package/src/lib/utils/quote-email-content.js +157 -0
- package/src/lib/utils/quote-email-content.js.map +1 -0
|
@@ -18,6 +18,8 @@ import { readClientIp } from "@aglyn/aglyn/app-utils/request-ip";
|
|
|
18
18
|
import { consumeRateLimit, firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
19
19
|
import * as CommerceModel from "../model/index.js";
|
|
20
20
|
import { verifyOrderStatusToken } from "./order-status-token.js";
|
|
21
|
+
import { mintQuoteLinkToken } from "./quote-link-token.js";
|
|
22
|
+
import { quotePageHref } from "../constants/quote-page.js";
|
|
21
23
|
import { resolvePluginTrackingPages } from "@aglyn/aglyn/plugin-manager/plugin-tracking-pages";
|
|
22
24
|
/** How long the page waits for a tracking service's own page link (AGL-3635). */ export const TRACKING_PAGE_TIMEOUT_MS = 1500;
|
|
23
25
|
/** Status lookups one address may make per site, per ten minutes. */ export const ORDER_STATUS_LOOKUPS_PER_WINDOW = 60;
|
|
@@ -41,6 +43,28 @@ async function actionsFor(input) {
|
|
|
41
43
|
}));
|
|
42
44
|
return answers.flat().filter((action)=>action && action.id && action.label && /^(https:\/\/|\/)/.test(String(action.url)));
|
|
43
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Pay, on the status page of an order still to be paid that its lasting pay
|
|
48
|
+
* link can take payment for (AGL-4099): a draft order or a quote, opening the
|
|
49
|
+
* same `/pay-quote` page its invoice links to. None for anything else.
|
|
50
|
+
*/ export function orderStatusPayAction(input) {
|
|
51
|
+
const { hostId, orderId, order } = input;
|
|
52
|
+
if (order.status !== 'pending' || !CommerceModel.orderPayLinkKind(order)) return null;
|
|
53
|
+
if (CommerceModel.invoicePayRefusal(order, orderId)) return null;
|
|
54
|
+
const token = mintQuoteLinkToken(hostId, orderId, CommerceModel.orderPayLinkVersion(order));
|
|
55
|
+
if (!token) return null;
|
|
56
|
+
const charge = CommerceModel.invoiceCheckoutCharge(order, orderId);
|
|
57
|
+
const later = [
|
|
58
|
+
charge.shippingOptions.length > 0 ? 'shipping' : '',
|
|
59
|
+
charge.taxAtPayment ? 'tax' : ''
|
|
60
|
+
].filter(Boolean).join(' and ');
|
|
61
|
+
const amount = CommerceModel.formatOrderMoney(charge.totalCents, input.currency);
|
|
62
|
+
return {
|
|
63
|
+
id: 'pay',
|
|
64
|
+
label: later ? `Pay ${amount} plus ${later}` : `Pay ${amount}`,
|
|
65
|
+
url: quotePageHref(orderId, token)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
44
68
|
/**
|
|
45
69
|
* The guest order-status page's data (AGL-3610): `GET
|
|
46
70
|
* /api/commerce/order-status?hostId=…&o=<orderId>&t=<token>` on the store.
|
|
@@ -92,7 +116,7 @@ async function actionsFor(input) {
|
|
|
92
116
|
});
|
|
93
117
|
if (!verifyOrderStatusToken(hostId, orderId, token)) return notFound();
|
|
94
118
|
try {
|
|
95
|
-
var _orderSnapshot_data,
|
|
119
|
+
var _orderSnapshot_data, _settingsSnapshot_get, _resolveHostToken;
|
|
96
120
|
const firestore = firebaseAdmin.app().firestore();
|
|
97
121
|
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
98
122
|
const [hostSnapshot, settingsSnapshot, orderSnapshot] = await Promise.all([
|
|
@@ -102,18 +126,30 @@ async function actionsFor(input) {
|
|
|
102
126
|
]);
|
|
103
127
|
if (!hostSnapshot.exists || !orderSnapshot.exists) return notFound();
|
|
104
128
|
const order = CommerceModel.liftLegacyOrder((_orderSnapshot_data = orderSnapshot.data()) != null ? _orderSnapshot_data : {});
|
|
129
|
+
const currency = String((_settingsSnapshot_get = settingsSnapshot.get('currency')) != null ? _settingsSnapshot_get : 'USD');
|
|
130
|
+
const pay = orderStatusPayAction({
|
|
131
|
+
hostId,
|
|
132
|
+
orderId,
|
|
133
|
+
order,
|
|
134
|
+
currency
|
|
135
|
+
});
|
|
105
136
|
const view = CommerceModel.buildOrderStatusView({
|
|
106
137
|
order,
|
|
107
138
|
orderId,
|
|
108
139
|
storeName: String((_resolveHostToken = resolveHostToken('businessName', hostSnapshot.data())) != null ? _resolveHostToken : ''),
|
|
109
|
-
currency
|
|
140
|
+
currency,
|
|
110
141
|
number: CommerceModel.formatOrderNumber(order, orderId),
|
|
111
|
-
actions:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
actions: [
|
|
143
|
+
...pay ? [
|
|
144
|
+
pay
|
|
145
|
+
] : [],
|
|
146
|
+
...await actionsFor({
|
|
147
|
+
hostId,
|
|
148
|
+
orderId,
|
|
149
|
+
order,
|
|
150
|
+
token
|
|
151
|
+
})
|
|
152
|
+
]
|
|
117
153
|
});
|
|
118
154
|
// A tracking service's own page for each parcel, where the merchant
|
|
119
155
|
// follows them through one (AGL-3635); the carrier's link otherwise.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-status.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 PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n resolveHostToken,\n type HostTokenSource,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\nimport { consumeRateLimit, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\nimport { verifyOrderStatusToken } from './order-status-token'\nimport { resolvePluginTrackingPages } from '@aglyn/aglyn/plugin-manager/plugin-tracking-pages'\n\n/** How long the page waits for a tracking service's own page link (AGL-3635). */\nexport const TRACKING_PAGE_TIMEOUT_MS = 1_500\n\n/** Status lookups one address may make per site, per ten minutes. */\nexport const ORDER_STATUS_LOOKUPS_PER_WINDOW = 60\nexport const ORDER_STATUS_WINDOW_MS = 10 * 60_000\n\n/**\n * Adds buyer actions to the status page (AGL-3610) — the typed extension\n * point for \"Request a return\", which the order-processing work (AGL-3611)\n * owns. A provider sees the verified order and answers the actions that apply\n * to it (none, usually); the page draws each as a button to its `url`. Kept\n * inside commerce: returns are this plugin's, so no cross-plugin seam is\n * needed, and a provider that throws costs the page its buttons, not the page.\n */\nexport type OrderStatusActionsProvider = (input: {\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n /** The verified status token, for a provider building a signed follow-up link. */\n token: string\n}) => CommerceModel.OrderStatusAction[] | Promise<CommerceModel.OrderStatusAction[]>\n\nconst actionProviders: OrderStatusActionsProvider[] = []\n\nexport function registerOrderStatusActions(provider: OrderStatusActionsProvider): void {\n if (!actionProviders.includes(provider)) actionProviders.push(provider)\n}\n\n/** Specs only. */\nexport function clearOrderStatusActions(): void {\n actionProviders.length = 0\n}\n\nasync function actionsFor(\n input: Parameters<OrderStatusActionsProvider>[0],\n): Promise<CommerceModel.OrderStatusAction[]> {\n const answers = await Promise.all(\n actionProviders.map(async (provider) => {\n try {\n return (await provider(input)) ?? []\n } catch (error) {\n console.error('[order-status] actions provider failed', error)\n return []\n }\n }),\n )\n return answers\n .flat()\n .filter(\n (action) =>\n action && action.id && action.label && /^(https:\\/\\/|\\/)/.test(String(action.url)),\n )\n}\n\n/**\n * The guest order-status page's data (AGL-3610): `GET\n * /api/commerce/order-status?hostId=…&o=<orderId>&t=<token>` on the store.\n *\n * The token is the whole credential — `verifyOrderStatusToken`, constant\n * time — and a wrong one answers exactly as a missing order does (404), so\n * the route cannot be used to learn which order ids exist. Rate-limited per\n * address per site, durable, BEFORE the token check so guessing costs the\n * same as looking. The answer is `buildOrderStatusView`, an allow-listed\n * projection, never the order document; `Cache-Control: private, no-store`\n * and `X-Robots-Tag: noindex` keep it out of shared caches and search.\n *\n * Registered as a `recipientLink` route: an emailed link has to keep working\n * for the buyer it was sent to, so the per-site enablement gates do not\n * stand between them and their order.\n */\nexport const orderStatusHandler: PluginApiHandler = async (req, res) => {\n res.setHeader('Cache-Control', 'private, no-store')\n res.setHeader('X-Robots-Tag', 'noindex, nofollow')\n if (req.method !== 'GET') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.query.hostId ?? '')\n const orderId = String(req.query.o ?? '').slice(0, 200)\n const token = String(req.query.t ?? '').slice(0, 200)\n if (!hostId || !orderId || !token || /^__.*__$/.test(hostId) || /^__.*__$/.test(orderId) || orderId.includes('/')) {\n return res.status(400).json({ error: 'This link is incomplete.' })\n }\n const ip =\n readClientIp(req.headers, { remoteAddress: req.socket?.remoteAddress }) ??\n 'no-address'\n const rate = await consumeRateLimit(`commerce-order-status:${hostId}:${ip}`, {\n limit: ORDER_STATUS_LOOKUPS_PER_WINDOW,\n windowMs: ORDER_STATUS_WINDOW_MS,\n })\n if (!rate.allowed) {\n res.setHeader(\n 'Retry-After',\n String(Math.max(1, Math.ceil((rate.resetMs - Date.now()) / 1000))),\n )\n return res.status(429).json({ error: 'Too many lookups. Try again in a few minutes.' })\n }\n const notFound = () =>\n res.status(404).json({ error: 'We could not find that order. Check the link in your email.' })\n if (!verifyOrderStatusToken(hostId, orderId, token)) return notFound()\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, settingsSnapshot, orderSnapshot] = await Promise.all([\n hostRef.get(),\n hostRef.collection('settings').doc('store').get(),\n hostRef.collection('orders').doc(orderId).get(),\n ])\n if (!hostSnapshot.exists || !orderSnapshot.exists) return notFound()\n const order = CommerceModel.liftLegacyOrder((orderSnapshot.data() ?? {}) as never)\n const view = CommerceModel.buildOrderStatusView({\n order,\n orderId,\n storeName: String(\n resolveHostToken('businessName', hostSnapshot.data() as HostTokenSource) ?? '',\n ),\n currency: String(settingsSnapshot.get('currency') ?? 'USD'),\n number: CommerceModel.formatOrderNumber(order, orderId),\n actions: await actionsFor({ hostId, orderId, order, token }),\n })\n // A tracking service's own page for each parcel, where the merchant\n // follows them through one (AGL-3635); the carrier's link otherwise.\n const pages = await resolvePluginTrackingPages(\n view.shipments\n .filter((shipment) => shipment.trackingNumber)\n .map((shipment) => ({\n hostId,\n recordId: orderId,\n carrier: shipment.carrier,\n trackingNumber: String(shipment.trackingNumber),\n })),\n { timeoutMs: TRACKING_PAGE_TIMEOUT_MS },\n )\n for (const shipment of view.shipments) {\n const page = shipment.trackingNumber ? pages.get(shipment.trackingNumber) : undefined\n if (page) shipment.trackingUrl = page\n }\n return res.status(200).json(view)\n } catch (error) {\n console.error('orderStatus failed', error)\n return res.status(500).json({ error: 'Your order could not be loaded. Try again.' })\n }\n}\n"],"names":["resolveHostToken","readClientIp","consumeRateLimit","firebaseAdmin","CommerceModel","verifyOrderStatusToken","resolvePluginTrackingPages","TRACKING_PAGE_TIMEOUT_MS","ORDER_STATUS_LOOKUPS_PER_WINDOW","ORDER_STATUS_WINDOW_MS","actionProviders","registerOrderStatusActions","provider","includes","push","clearOrderStatusActions","length","actionsFor","input","answers","Promise","all","map","error","console","flat","filter","action","id","label","test","String","url","orderStatusHandler","req","res","setHeader","method","status","json","hostId","query","orderId","o","slice","token","t","ip","headers","remoteAddress","socket","rate","limit","windowMs","allowed","Math","max","ceil","resetMs","Date","now","notFound","orderSnapshot","settingsSnapshot","firestore","app","hostRef","collection","doc","hostSnapshot","get","exists","order","liftLegacyOrder","data","view","buildOrderStatusView","storeName","currency","number","formatOrderNumber","actions","pages","shipments","shipment","trackingNumber","recordId","carrier","timeoutMs","page","undefined","trackingUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-status.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 PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n resolveHostToken,\n type HostTokenSource,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\nimport { consumeRateLimit, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\nimport { verifyOrderStatusToken } from './order-status-token'\nimport { mintQuoteLinkToken } from './quote-link-token'\nimport { quotePageHref } from '../constants/quote-page'\nimport { resolvePluginTrackingPages } from '@aglyn/aglyn/plugin-manager/plugin-tracking-pages'\n\n/** How long the page waits for a tracking service's own page link (AGL-3635). */\nexport const TRACKING_PAGE_TIMEOUT_MS = 1_500\n\n/** Status lookups one address may make per site, per ten minutes. */\nexport const ORDER_STATUS_LOOKUPS_PER_WINDOW = 60\nexport const ORDER_STATUS_WINDOW_MS = 10 * 60_000\n\n/**\n * Adds buyer actions to the status page (AGL-3610) — the typed extension\n * point for \"Request a return\", which the order-processing work (AGL-3611)\n * owns. A provider sees the verified order and answers the actions that apply\n * to it (none, usually); the page draws each as a button to its `url`. Kept\n * inside commerce: returns are this plugin's, so no cross-plugin seam is\n * needed, and a provider that throws costs the page its buttons, not the page.\n */\nexport type OrderStatusActionsProvider = (input: {\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n /** The verified status token, for a provider building a signed follow-up link. */\n token: string\n}) => CommerceModel.OrderStatusAction[] | Promise<CommerceModel.OrderStatusAction[]>\n\nconst actionProviders: OrderStatusActionsProvider[] = []\n\nexport function registerOrderStatusActions(provider: OrderStatusActionsProvider): void {\n if (!actionProviders.includes(provider)) actionProviders.push(provider)\n}\n\n/** Specs only. */\nexport function clearOrderStatusActions(): void {\n actionProviders.length = 0\n}\n\nasync function actionsFor(\n input: Parameters<OrderStatusActionsProvider>[0],\n): Promise<CommerceModel.OrderStatusAction[]> {\n const answers = await Promise.all(\n actionProviders.map(async (provider) => {\n try {\n return (await provider(input)) ?? []\n } catch (error) {\n console.error('[order-status] actions provider failed', error)\n return []\n }\n }),\n )\n return answers\n .flat()\n .filter(\n (action) =>\n action && action.id && action.label && /^(https:\\/\\/|\\/)/.test(String(action.url)),\n )\n}\n\n/**\n * Pay, on the status page of an order still to be paid that its lasting pay\n * link can take payment for (AGL-4099): a draft order or a quote, opening the\n * same `/pay-quote` page its invoice links to. None for anything else.\n */\nexport function orderStatusPayAction(input: {\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n currency: string\n}): CommerceModel.OrderStatusAction | null {\n const { hostId, orderId, order } = input\n if (order.status !== 'pending' || !CommerceModel.orderPayLinkKind(order)) return null\n if (CommerceModel.invoicePayRefusal(order, orderId)) return null\n const token = mintQuoteLinkToken(hostId, orderId, CommerceModel.orderPayLinkVersion(order))\n if (!token) return null\n const charge = CommerceModel.invoiceCheckoutCharge(order, orderId)\n const later = [charge.shippingOptions.length > 0 ? 'shipping' : '', charge.taxAtPayment ? 'tax' : '']\n .filter(Boolean)\n .join(' and ')\n const amount = CommerceModel.formatOrderMoney(charge.totalCents, input.currency)\n return { id: 'pay', label: later ? `Pay ${amount} plus ${later}` : `Pay ${amount}`, url: quotePageHref(orderId, token) }\n}\n\n/**\n * The guest order-status page's data (AGL-3610): `GET\n * /api/commerce/order-status?hostId=…&o=<orderId>&t=<token>` on the store.\n *\n * The token is the whole credential — `verifyOrderStatusToken`, constant\n * time — and a wrong one answers exactly as a missing order does (404), so\n * the route cannot be used to learn which order ids exist. Rate-limited per\n * address per site, durable, BEFORE the token check so guessing costs the\n * same as looking. The answer is `buildOrderStatusView`, an allow-listed\n * projection, never the order document; `Cache-Control: private, no-store`\n * and `X-Robots-Tag: noindex` keep it out of shared caches and search.\n *\n * Registered as a `recipientLink` route: an emailed link has to keep working\n * for the buyer it was sent to, so the per-site enablement gates do not\n * stand between them and their order.\n */\nexport const orderStatusHandler: PluginApiHandler = async (req, res) => {\n res.setHeader('Cache-Control', 'private, no-store')\n res.setHeader('X-Robots-Tag', 'noindex, nofollow')\n if (req.method !== 'GET') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.query.hostId ?? '')\n const orderId = String(req.query.o ?? '').slice(0, 200)\n const token = String(req.query.t ?? '').slice(0, 200)\n if (!hostId || !orderId || !token || /^__.*__$/.test(hostId) || /^__.*__$/.test(orderId) || orderId.includes('/')) {\n return res.status(400).json({ error: 'This link is incomplete.' })\n }\n const ip =\n readClientIp(req.headers, { remoteAddress: req.socket?.remoteAddress }) ??\n 'no-address'\n const rate = await consumeRateLimit(`commerce-order-status:${hostId}:${ip}`, {\n limit: ORDER_STATUS_LOOKUPS_PER_WINDOW,\n windowMs: ORDER_STATUS_WINDOW_MS,\n })\n if (!rate.allowed) {\n res.setHeader(\n 'Retry-After',\n String(Math.max(1, Math.ceil((rate.resetMs - Date.now()) / 1000))),\n )\n return res.status(429).json({ error: 'Too many lookups. Try again in a few minutes.' })\n }\n const notFound = () =>\n res.status(404).json({ error: 'We could not find that order. Check the link in your email.' })\n if (!verifyOrderStatusToken(hostId, orderId, token)) return notFound()\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, settingsSnapshot, orderSnapshot] = await Promise.all([\n hostRef.get(),\n hostRef.collection('settings').doc('store').get(),\n hostRef.collection('orders').doc(orderId).get(),\n ])\n if (!hostSnapshot.exists || !orderSnapshot.exists) return notFound()\n const order = CommerceModel.liftLegacyOrder((orderSnapshot.data() ?? {}) as never)\n const currency = String(settingsSnapshot.get('currency') ?? 'USD')\n const pay = orderStatusPayAction({ hostId, orderId, order, currency })\n const view = CommerceModel.buildOrderStatusView({\n order,\n orderId,\n storeName: String(\n resolveHostToken('businessName', hostSnapshot.data() as HostTokenSource) ?? '',\n ),\n currency,\n number: CommerceModel.formatOrderNumber(order, orderId),\n actions: [...(pay ? [pay] : []), ...(await actionsFor({ hostId, orderId, order, token }))],\n })\n // A tracking service's own page for each parcel, where the merchant\n // follows them through one (AGL-3635); the carrier's link otherwise.\n const pages = await resolvePluginTrackingPages(\n view.shipments\n .filter((shipment) => shipment.trackingNumber)\n .map((shipment) => ({\n hostId,\n recordId: orderId,\n carrier: shipment.carrier,\n trackingNumber: String(shipment.trackingNumber),\n })),\n { timeoutMs: TRACKING_PAGE_TIMEOUT_MS },\n )\n for (const shipment of view.shipments) {\n const page = shipment.trackingNumber ? pages.get(shipment.trackingNumber) : undefined\n if (page) shipment.trackingUrl = page\n }\n return res.status(200).json(view)\n } catch (error) {\n console.error('orderStatus failed', error)\n return res.status(500).json({ error: 'Your order could not be loaded. Try again.' })\n }\n}\n"],"names":["resolveHostToken","readClientIp","consumeRateLimit","firebaseAdmin","CommerceModel","verifyOrderStatusToken","mintQuoteLinkToken","quotePageHref","resolvePluginTrackingPages","TRACKING_PAGE_TIMEOUT_MS","ORDER_STATUS_LOOKUPS_PER_WINDOW","ORDER_STATUS_WINDOW_MS","actionProviders","registerOrderStatusActions","provider","includes","push","clearOrderStatusActions","length","actionsFor","input","answers","Promise","all","map","error","console","flat","filter","action","id","label","test","String","url","orderStatusPayAction","hostId","orderId","order","status","orderPayLinkKind","invoicePayRefusal","token","orderPayLinkVersion","charge","invoiceCheckoutCharge","later","shippingOptions","taxAtPayment","Boolean","join","amount","formatOrderMoney","totalCents","currency","orderStatusHandler","req","res","setHeader","method","json","query","o","slice","t","ip","headers","remoteAddress","socket","rate","limit","windowMs","allowed","Math","max","ceil","resetMs","Date","now","notFound","orderSnapshot","settingsSnapshot","firestore","app","hostRef","collection","doc","hostSnapshot","get","exists","liftLegacyOrder","data","pay","view","buildOrderStatusView","storeName","number","formatOrderNumber","actions","pages","shipments","shipment","trackingNumber","recordId","carrier","timeoutMs","page","undefined","trackingUrl"],"mappings":"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"}
|
|
@@ -48,8 +48,9 @@ export declare function erasedOrderPatch(data: FirebaseFirestore.DocumentData |
|
|
|
48
48
|
/**
|
|
49
49
|
* A quote's customer, erased (AGL-4093). An unpaid quote keeps its customer
|
|
50
50
|
* in its `quote` section rather than on the order's own fields, so it is
|
|
51
|
-
* found by `quote.customer.emailLower` and loses the person
|
|
52
|
-
* addresses; its lines and totals stay,
|
|
51
|
+
* found by `quote.customer.emailLower` and loses the person, both
|
|
52
|
+
* addresses and the address each email went to; its lines and totals stay,
|
|
53
|
+
* as an order's do.
|
|
53
54
|
*/
|
|
54
55
|
export declare function erasedQuotePatch(data: FirebaseFirestore.DocumentData | undefined, now: number): Record<string, unknown>;
|
|
55
56
|
export declare function erasedCheckInPatch(data: FirebaseFirestore.DocumentData | undefined, now: number): Record<string, unknown>;
|
|
@@ -19,9 +19,19 @@ import { FieldValue } from "firebase-admin/firestore";
|
|
|
19
19
|
import { firebaseAdmin } from "@aglyn/tenant-data-admin/server/firebase-admin";
|
|
20
20
|
const LABEL = 'commerce person eraser';
|
|
21
21
|
/** Where a quote keeps its customer's address (`commerce-quotes.ts`). */ const QUOTE_EMAIL_FIELD = 'quote.customer.emailLower';
|
|
22
|
+
/**
|
|
23
|
+
* Each time the order's invoice was emailed names where it went (AGL-4099);
|
|
24
|
+
* the record of the send stays, without the address.
|
|
25
|
+
*/ function erasedInvoiceSends(data) {
|
|
26
|
+
return Array.isArray(data == null ? void 0 : data['invoiceSends']) ? {
|
|
27
|
+
invoiceSends: data['invoiceSends'].map((send)=>_extends({}, send, {
|
|
28
|
+
to: ''
|
|
29
|
+
}))
|
|
30
|
+
} : {};
|
|
31
|
+
}
|
|
22
32
|
/** The patch that takes the buyer off one order, as it stands. */ export function erasedOrderPatch(data, now) {
|
|
23
33
|
const addressTokens = new Set(Array.isArray(data == null ? void 0 : data['customerEmailTokens']) ? data['customerEmailTokens'] : []);
|
|
24
|
-
return _extends({
|
|
34
|
+
return _extends({}, erasedInvoiceSends(data), {
|
|
25
35
|
customerEmail: null,
|
|
26
36
|
customerName: null,
|
|
27
37
|
shippingAddress: FieldValue.delete(),
|
|
@@ -36,19 +46,28 @@ const LABEL = 'commerce person eraser';
|
|
|
36
46
|
/** The patch that takes the customer off one check-in at the window (AGL-4043). */ /**
|
|
37
47
|
* A quote's customer, erased (AGL-4093). An unpaid quote keeps its customer
|
|
38
48
|
* in its `quote` section rather than on the order's own fields, so it is
|
|
39
|
-
* found by `quote.customer.emailLower` and loses the person
|
|
40
|
-
* addresses; its lines and totals stay,
|
|
49
|
+
* found by `quote.customer.emailLower` and loses the person, both
|
|
50
|
+
* addresses and the address each email went to; its lines and totals stay,
|
|
51
|
+
* as an order's do.
|
|
41
52
|
*/ export function erasedQuotePatch(data, now) {
|
|
42
53
|
var _ref;
|
|
43
54
|
const quote = (_ref = data == null ? void 0 : data['quote']) != null ? _ref : {};
|
|
44
|
-
|
|
55
|
+
// Each time it was emailed names where it went (AGL-4097); the record of
|
|
56
|
+
// the send stays, without the address.
|
|
57
|
+
const sends = Array.isArray(quote['sends']) ? {
|
|
58
|
+
sends: quote['sends'].map((send)=>_extends({}, send, {
|
|
59
|
+
to: ''
|
|
60
|
+
}))
|
|
61
|
+
} : {};
|
|
62
|
+
return _extends({
|
|
45
63
|
quote: _extends({}, quote, {
|
|
46
64
|
customer: {},
|
|
47
65
|
billTo: null,
|
|
48
66
|
shipTo: null
|
|
49
|
-
})
|
|
67
|
+
}, sends)
|
|
68
|
+
}, erasedInvoiceSends(data), {
|
|
50
69
|
customerErasedAtMs: now
|
|
51
|
-
};
|
|
70
|
+
});
|
|
52
71
|
}
|
|
53
72
|
export function erasedCheckInPatch(data, now) {
|
|
54
73
|
var _ref, _ref1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/person-eraser.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 {\n PluginPersonEraser,\n PluginPersonErasureReport,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { updateWhereEquals } from '@aglyn/tenant-data-admin/server/paged-sweeps'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\n\n/**\n * COMMERCE'S SHARE OF A PERSON ERASURE (AGL-2623, AGL-3080).\n *\n * An order is the merchant's record of a sale: the amounts, the line items\n * and the tax are theirs to keep and the law expects them kept. So the person\n * is taken OFF every order they placed on a site of the workspace — name,\n * email, both addresses — and a stamp says when; the order itself stays.\n *\n * The address also lives in what the orders list queries by (AGL-3321): its\n * lower-cased key, its search prefixes, and the quick search's token array,\n * which holds those prefixes beside the order number's and the item names'.\n * The first two go; the prefixes are taken back out of the third. A prefix\n * the number or an item name shares with the address goes with it — a search\n * for that prefix misses this order until the orders backfill restamps it —\n * and nothing left on the order answers to the address.\n *\n * A check-in at the window (AGL-4043) copies the buyer's name, address and\n * phone, so the same person is taken off each of theirs, by the stored\n * lower-cased address; the check-in itself — who was due, who checked them\n * in, the wristband — stays, as the order does.\n *\n * A DRY RUN counts the orders and check-ins and writes nothing.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface CommercePersonEraserDeps {\n firestore(): Firestore\n}\n\nconst LABEL = 'commerce person eraser'\n\n/** Where a quote keeps its customer's address (`commerce-quotes.ts`). */\nconst QUOTE_EMAIL_FIELD = 'quote.customer.emailLower'\n\n/** The patch that takes the buyer off one order, as it stands. */\nexport function erasedOrderPatch(\n data: FirebaseFirestore.DocumentData | undefined,\n now: number,\n): Record<string, unknown> {\n const addressTokens = new Set(\n Array.isArray(data?.['customerEmailTokens']) ? data['customerEmailTokens'] : [],\n )\n return {\n customerEmail: null,\n customerName: null,\n shippingAddress: FieldValue.delete(),\n billingAddress: FieldValue.delete(),\n customerErasedAtMs: now,\n customerEmailLower: null,\n customerEmailTokens: [],\n ...(Array.isArray(data?.['searchTokens'])\n ? {\n searchTokens: (data['searchTokens'] as unknown[]).filter(\n (token) => !addressTokens.has(token),\n ),\n }\n : {}),\n }\n}\n\n/** The patch that takes the customer off one check-in at the window (AGL-4043). */\n/**\n * A quote's customer, erased (AGL-4093). An unpaid quote keeps its customer\n * in its `quote` section rather than on the order's own fields, so it is\n * found by `quote.customer.emailLower` and loses the person and both\n * addresses; its lines and totals stay, as an order's do.\n */\nexport function erasedQuotePatch(\n data: FirebaseFirestore.DocumentData | undefined,\n now: number,\n): Record<string, unknown> {\n const quote = (data?.['quote'] ?? {}) as Record<string, unknown>\n return {\n quote: { ...quote, customer: {}, billTo: null, shipTo: null },\n customerErasedAtMs: now,\n }\n}\n\nexport function erasedCheckInPatch(\n data: FirebaseFirestore.DocumentData | undefined,\n now: number,\n): Record<string, unknown> {\n // One key per order or stay, so the people one order brought still read\n // as one party, and as nobody's name.\n const source = String(data?.['orderId'] ?? data?.['reservationId'] ?? '')\n return {\n customerKey: `erased:${source}`,\n customerName: null,\n customerEmail: null,\n customerEmailLower: null,\n customerPhone: null,\n contactId: null,\n searchTokens: [],\n customerErasedAtMs: now,\n }\n}\n\nexport function createCommercePersonEraser(deps: CommercePersonEraserDeps): PluginPersonEraser {\n return async ({ orgId, email, dryRun, atMs }): Promise<PluginPersonErasureReport> => {\n const db = deps.firestore()\n const hosts = await db.collection('hosts').where('orgId', '==', orgId).get()\n let orders = 0\n let checkIns = 0\n let quotes = 0\n const emailLower = email.trim().toLowerCase()\n for (const host of hosts.docs) {\n const placed = host.ref.collection('orders')\n const checked = host.ref.collection('checkIns')\n if (dryRun) {\n orders += (await placed.where('customerEmail', '==', email).get()).size\n checkIns += (await checked.where('customerEmailLower', '==', emailLower).get()).size\n quotes += (await placed.where(QUOTE_EMAIL_FIELD, '==', emailLower).get()).size\n continue\n }\n checkIns += await updateWhereEquals(\n db,\n checked,\n 'customerEmailLower',\n emailLower,\n (data) => erasedCheckInPatch(data, atMs),\n LABEL,\n )\n orders += await updateWhereEquals(\n db,\n placed,\n 'customerEmail',\n email,\n (data) => erasedOrderPatch(data, atMs),\n LABEL,\n )\n quotes += await updateWhereEquals(\n db,\n placed,\n QUOTE_EMAIL_FIELD,\n emailLower,\n (data) => erasedQuotePatch(data, atMs),\n LABEL,\n )\n }\n return { orders, checkIns, quotes }\n }\n}\n\n/** The eraser over the platform's own Firestore, as the declarations register it. */\nexport const commercePersonEraser: PluginPersonEraser = (request) =>\n createCommercePersonEraser({ firestore: () => firebaseAdmin.app().firestore() })(request)\n"],"names":["updateWhereEquals","FieldValue","firebaseAdmin","LABEL","QUOTE_EMAIL_FIELD","erasedOrderPatch","data","now","addressTokens","Set","Array","isArray","customerEmail","customerName","shippingAddress","delete","billingAddress","customerErasedAtMs","customerEmailLower","customerEmailTokens","searchTokens","filter","token","has","erasedQuotePatch","quote","customer","billTo","shipTo","erasedCheckInPatch","source","String","customerKey","customerPhone","contactId","createCommercePersonEraser","deps","orgId","email","dryRun","atMs","db","firestore","hosts","collection","where","get","orders","checkIns","quotes","emailLower","trim","toLowerCase","host","docs","placed","ref","checked","size","commercePersonEraser","request","app"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/person-eraser.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 {\n PluginPersonEraser,\n PluginPersonErasureReport,\n} from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport { updateWhereEquals } from '@aglyn/tenant-data-admin/server/paged-sweeps'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin/server/firebase-admin'\n\n/**\n * COMMERCE'S SHARE OF A PERSON ERASURE (AGL-2623, AGL-3080).\n *\n * An order is the merchant's record of a sale: the amounts, the line items\n * and the tax are theirs to keep and the law expects them kept. So the person\n * is taken OFF every order they placed on a site of the workspace — name,\n * email, both addresses — and a stamp says when; the order itself stays.\n *\n * The address also lives in what the orders list queries by (AGL-3321): its\n * lower-cased key, its search prefixes, and the quick search's token array,\n * which holds those prefixes beside the order number's and the item names'.\n * The first two go; the prefixes are taken back out of the third. A prefix\n * the number or an item name shares with the address goes with it — a search\n * for that prefix misses this order until the orders backfill restamps it —\n * and nothing left on the order answers to the address.\n *\n * A check-in at the window (AGL-4043) copies the buyer's name, address and\n * phone, so the same person is taken off each of theirs, by the stored\n * lower-cased address; the check-in itself — who was due, who checked them\n * in, the wristband — stays, as the order does.\n *\n * A DRY RUN counts the orders and check-ins and writes nothing.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface CommercePersonEraserDeps {\n firestore(): Firestore\n}\n\nconst LABEL = 'commerce person eraser'\n\n/** Where a quote keeps its customer's address (`commerce-quotes.ts`). */\nconst QUOTE_EMAIL_FIELD = 'quote.customer.emailLower'\n\n/**\n * Each time the order's invoice was emailed names where it went (AGL-4099);\n * the record of the send stays, without the address.\n */\nfunction erasedInvoiceSends(data: FirebaseFirestore.DocumentData | undefined): Record<string, unknown> {\n return Array.isArray(data?.['invoiceSends'])\n ? { invoiceSends: (data['invoiceSends'] as Array<Record<string, unknown>>).map((send) => ({ ...send, to: '' })) }\n : {}\n}\n\n/** The patch that takes the buyer off one order, as it stands. */\nexport function erasedOrderPatch(\n data: FirebaseFirestore.DocumentData | undefined,\n now: number,\n): Record<string, unknown> {\n const addressTokens = new Set(\n Array.isArray(data?.['customerEmailTokens']) ? data['customerEmailTokens'] : [],\n )\n return {\n ...erasedInvoiceSends(data),\n customerEmail: null,\n customerName: null,\n shippingAddress: FieldValue.delete(),\n billingAddress: FieldValue.delete(),\n customerErasedAtMs: now,\n customerEmailLower: null,\n customerEmailTokens: [],\n ...(Array.isArray(data?.['searchTokens'])\n ? {\n searchTokens: (data['searchTokens'] as unknown[]).filter(\n (token) => !addressTokens.has(token),\n ),\n }\n : {}),\n }\n}\n\n/** The patch that takes the customer off one check-in at the window (AGL-4043). */\n/**\n * A quote's customer, erased (AGL-4093). An unpaid quote keeps its customer\n * in its `quote` section rather than on the order's own fields, so it is\n * found by `quote.customer.emailLower` and loses the person, both\n * addresses and the address each email went to; its lines and totals stay,\n * as an order's do.\n */\nexport function erasedQuotePatch(\n data: FirebaseFirestore.DocumentData | undefined,\n now: number,\n): Record<string, unknown> {\n const quote = (data?.['quote'] ?? {}) as Record<string, unknown>\n // Each time it was emailed names where it went (AGL-4097); the record of\n // the send stays, without the address.\n const sends = Array.isArray(quote['sends'])\n ? { sends: (quote['sends'] as Array<Record<string, unknown>>).map((send) => ({ ...send, to: '' })) }\n : {}\n return {\n quote: { ...quote, customer: {}, billTo: null, shipTo: null, ...sends },\n ...erasedInvoiceSends(data),\n customerErasedAtMs: now,\n }\n}\n\nexport function erasedCheckInPatch(\n data: FirebaseFirestore.DocumentData | undefined,\n now: number,\n): Record<string, unknown> {\n // One key per order or stay, so the people one order brought still read\n // as one party, and as nobody's name.\n const source = String(data?.['orderId'] ?? data?.['reservationId'] ?? '')\n return {\n customerKey: `erased:${source}`,\n customerName: null,\n customerEmail: null,\n customerEmailLower: null,\n customerPhone: null,\n contactId: null,\n searchTokens: [],\n customerErasedAtMs: now,\n }\n}\n\nexport function createCommercePersonEraser(deps: CommercePersonEraserDeps): PluginPersonEraser {\n return async ({ orgId, email, dryRun, atMs }): Promise<PluginPersonErasureReport> => {\n const db = deps.firestore()\n const hosts = await db.collection('hosts').where('orgId', '==', orgId).get()\n let orders = 0\n let checkIns = 0\n let quotes = 0\n const emailLower = email.trim().toLowerCase()\n for (const host of hosts.docs) {\n const placed = host.ref.collection('orders')\n const checked = host.ref.collection('checkIns')\n if (dryRun) {\n orders += (await placed.where('customerEmail', '==', email).get()).size\n checkIns += (await checked.where('customerEmailLower', '==', emailLower).get()).size\n quotes += (await placed.where(QUOTE_EMAIL_FIELD, '==', emailLower).get()).size\n continue\n }\n checkIns += await updateWhereEquals(\n db,\n checked,\n 'customerEmailLower',\n emailLower,\n (data) => erasedCheckInPatch(data, atMs),\n LABEL,\n )\n orders += await updateWhereEquals(\n db,\n placed,\n 'customerEmail',\n email,\n (data) => erasedOrderPatch(data, atMs),\n LABEL,\n )\n quotes += await updateWhereEquals(\n db,\n placed,\n QUOTE_EMAIL_FIELD,\n emailLower,\n (data) => erasedQuotePatch(data, atMs),\n LABEL,\n )\n }\n return { orders, checkIns, quotes }\n }\n}\n\n/** The eraser over the platform's own Firestore, as the declarations register it. */\nexport const commercePersonEraser: PluginPersonEraser = (request) =>\n createCommercePersonEraser({ firestore: () => firebaseAdmin.app().firestore() })(request)\n"],"names":["updateWhereEquals","FieldValue","firebaseAdmin","LABEL","QUOTE_EMAIL_FIELD","erasedInvoiceSends","data","Array","isArray","invoiceSends","map","send","to","erasedOrderPatch","now","addressTokens","Set","customerEmail","customerName","shippingAddress","delete","billingAddress","customerErasedAtMs","customerEmailLower","customerEmailTokens","searchTokens","filter","token","has","erasedQuotePatch","quote","sends","customer","billTo","shipTo","erasedCheckInPatch","source","String","customerKey","customerPhone","contactId","createCommercePersonEraser","deps","orgId","email","dryRun","atMs","db","firestore","hosts","collection","where","get","orders","checkIns","quotes","emailLower","trim","toLowerCase","host","docs","placed","ref","checked","size","commercePersonEraser","request","app"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAMD,SAASA,iBAAiB,QAAQ,+CAA8C;AAChF,SAASC,UAAU,QAAQ,2BAA0B;AACrD,SAASC,aAAa,QAAQ,iDAAgD;AAgC9E,MAAMC,QAAQ;AAEd,uEAAuE,GACvE,MAAMC,oBAAoB;AAE1B;;;CAGC,GACD,SAASC,mBAAmBC,IAAgD;IAC1E,OAAOC,MAAMC,OAAO,CAACF,wBAAAA,IAAM,CAAC,eAAe,IACvC;QAAEG,cAAc,AAACH,IAAI,CAAC,eAAe,CAAoCI,GAAG,CAAC,CAACC,OAAU,aAAKA;gBAAMC,IAAI;;IAAO,IAC9G,CAAC;AACP;AAEA,gEAAgE,GAChE,OAAO,SAASC,iBACdP,IAAgD,EAChDQ,GAAW;IAEX,MAAMC,gBAAgB,IAAIC,IACxBT,MAAMC,OAAO,CAACF,wBAAAA,IAAM,CAAC,sBAAsB,IAAIA,IAAI,CAAC,sBAAsB,GAAG,EAAE;IAEjF,OAAO,aACFD,mBAAmBC;QACtBW,eAAe;QACfC,cAAc;QACdC,iBAAiBlB,WAAWmB,MAAM;QAClCC,gBAAgBpB,WAAWmB,MAAM;QACjCE,oBAAoBR;QACpBS,oBAAoB;QACpBC,qBAAqB,EAAE;OACnBjB,MAAMC,OAAO,CAACF,wBAAAA,IAAM,CAAC,eAAe,IACpC;QACEmB,cAAc,AAACnB,IAAI,CAAC,eAAe,CAAeoB,MAAM,CACtD,CAACC,QAAU,CAACZ,cAAca,GAAG,CAACD;IAElC,IACA,CAAC;AAET;AAEA,iFAAiF,GACjF;;;;;;CAMC,GACD,OAAO,SAASE,iBACdvB,IAAgD,EAChDQ,GAAW;;IAEX,MAAMgB,gBAASxB,wBAAAA,IAAM,CAAC,QAAQ,mBAAI,CAAC;IACnC,yEAAyE;IACzE,uCAAuC;IACvC,MAAMyB,QAAQxB,MAAMC,OAAO,CAACsB,KAAK,CAAC,QAAQ,IACtC;QAAEC,OAAO,AAACD,KAAK,CAAC,QAAQ,CAAoCpB,GAAG,CAAC,CAACC,OAAU,aAAKA;gBAAMC,IAAI;;IAAO,IACjG,CAAC;IACL,OAAO;QACLkB,OAAO,aAAKA;YAAOE,UAAU,CAAC;YAAGC,QAAQ;YAAMC,QAAQ;WAASH;OAC7D1B,mBAAmBC;QACtBgB,oBAAoBR;;AAExB;AAEA,OAAO,SAASqB,mBACd7B,IAAgD,EAChDQ,GAAW;QAIWR;IAFtB,wEAAwE;IACxE,sCAAsC;IACtC,MAAM8B,SAASC,QAAO/B,gBAAAA,wBAAAA,IAAM,CAAC,UAAU,oBAAIA,wBAAAA,IAAM,CAAC,gBAAgB,YAA5CA,OAAgD;IACtE,OAAO;QACLgC,aAAa,CAAC,OAAO,EAAEF,QAAQ;QAC/BlB,cAAc;QACdD,eAAe;QACfM,oBAAoB;QACpBgB,eAAe;QACfC,WAAW;QACXf,cAAc,EAAE;QAChBH,oBAAoBR;IACtB;AACF;AAEA,OAAO,SAAS2B,2BAA2BC,IAA8B;IACvE,OAAO,OAAO,EAAEC,KAAK,EAAEC,KAAK,EAAEC,MAAM,EAAEC,IAAI,EAAE;QAC1C,MAAMC,KAAKL,KAAKM,SAAS;QACzB,MAAMC,QAAQ,MAAMF,GAAGG,UAAU,CAAC,SAASC,KAAK,CAAC,SAAS,MAAMR,OAAOS,GAAG;QAC1E,IAAIC,SAAS;QACb,IAAIC,WAAW;QACf,IAAIC,SAAS;QACb,MAAMC,aAAaZ,MAAMa,IAAI,GAAGC,WAAW;QAC3C,KAAK,MAAMC,QAAQV,MAAMW,IAAI,CAAE;YAC7B,MAAMC,SAASF,KAAKG,GAAG,CAACZ,UAAU,CAAC;YACnC,MAAMa,UAAUJ,KAAKG,GAAG,CAACZ,UAAU,CAAC;YACpC,IAAIL,QAAQ;gBACVQ,UAAU,AAAC,CAAA,MAAMQ,OAAOV,KAAK,CAAC,iBAAiB,MAAMP,OAAOQ,GAAG,EAAC,EAAGY,IAAI;gBACvEV,YAAY,AAAC,CAAA,MAAMS,QAAQZ,KAAK,CAAC,sBAAsB,MAAMK,YAAYJ,GAAG,EAAC,EAAGY,IAAI;gBACpFT,UAAU,AAAC,CAAA,MAAMM,OAAOV,KAAK,CAAC/C,mBAAmB,MAAMoD,YAAYJ,GAAG,EAAC,EAAGY,IAAI;gBAC9E;YACF;YACAV,YAAY,MAAMtD,kBAChB+C,IACAgB,SACA,sBACAP,YACA,CAAClD,OAAS6B,mBAAmB7B,MAAMwC,OACnC3C;YAEFkD,UAAU,MAAMrD,kBACd+C,IACAc,QACA,iBACAjB,OACA,CAACtC,OAASO,iBAAiBP,MAAMwC,OACjC3C;YAEFoD,UAAU,MAAMvD,kBACd+C,IACAc,QACAzD,mBACAoD,YACA,CAAClD,OAASuB,iBAAiBvB,MAAMwC,OACjC3C;QAEJ;QACA,OAAO;YAAEkD;YAAQC;YAAUC;QAAO;IACpC;AACF;AAEA,mFAAmF,GACnF,OAAO,MAAMU,uBAA2C,CAACC,UACvDzB,2BAA2B;QAAEO,WAAW,IAAM9C,cAAciE,GAAG,GAAGnB,SAAS;IAAG,GAAGkB,SAAQ"}
|
|
@@ -657,7 +657,7 @@ async function syncOne(context, raw) {
|
|
|
657
657
|
});
|
|
658
658
|
}
|
|
659
659
|
const variant = (_product_variants_find = product.variants.find((item)=>item.id === line.variantId)) != null ? _product_variants_find : product.variants[0];
|
|
660
|
-
const chosen = CommerceModel.resolveLineModifiers(product, line.modifiers);
|
|
660
|
+
const chosen = CommerceModel.resolveLineModifiers(product, line.modifiers, variant == null ? void 0 : variant.id);
|
|
661
661
|
const currentCents = variant && CommerceModel.variantHasPrice(variant) && chosen.ok ? Math.round(Number(variant.priceUsd) * 100) + chosen.extraCents : null;
|
|
662
662
|
if (currentCents !== line.unitAmountCents) {
|
|
663
663
|
flags.add('price-changed');
|