@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
|
@@ -44,6 +44,22 @@ import { withOrderListFields } from "./order-list-fields.js";
|
|
|
44
44
|
const version = Math.floor(Number(quote == null ? void 0 : quote.linkVersion));
|
|
45
45
|
return Number.isFinite(version) && version >= 1 ? version : 1;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The pay link version any order is on (AGL-4099). A quote keeps its version
|
|
49
|
+
* where slice 2 put it, `quote.linkVersion`, so every link already sent keeps
|
|
50
|
+
* verifying; any other order keeps it as `payLink.version`. Absent reads as
|
|
51
|
+
* the first either way.
|
|
52
|
+
*/ export function orderPayLinkVersion(order) {
|
|
53
|
+
var _order_payLink;
|
|
54
|
+
if (order == null ? void 0 : order.quote) return quoteLinkVersion(order.quote);
|
|
55
|
+
const version = Math.floor(Number(order == null ? void 0 : (_order_payLink = order.payLink) == null ? void 0 : _order_payLink.version));
|
|
56
|
+
return Number.isFinite(version) && version >= 1 ? version : 1;
|
|
57
|
+
}
|
|
58
|
+
/** Payments the order's pay link could not take: a quote's on the quote, any other order's on `payLink`. */ export function orderPayLinkUnmatched(order) {
|
|
59
|
+
var _order_payLink;
|
|
60
|
+
const list = (order == null ? void 0 : order.quote) ? order.quote.unmatchedPayments : order == null ? void 0 : (_order_payLink = order.payLink) == null ? void 0 : _order_payLink.unmatchedPayments;
|
|
61
|
+
return Array.isArray(list) ? list.filter((entry)=>entry && typeof entry === 'object') : [];
|
|
62
|
+
}
|
|
47
63
|
const text = (value)=>{
|
|
48
64
|
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
49
65
|
return trimmed ? trimmed : null;
|
|
@@ -52,7 +68,7 @@ const cents = (value)=>{
|
|
|
52
68
|
const number = Math.round(Number(value));
|
|
53
69
|
return Number.isFinite(number) ? number : 0;
|
|
54
70
|
};
|
|
55
|
-
function
|
|
71
|
+
/** An address as the pay link's page prints it, or null when it says nothing. */ export function quotePageAddress(address) {
|
|
56
72
|
if (!address) return null;
|
|
57
73
|
const view = {
|
|
58
74
|
name: text(address.name),
|
|
@@ -65,7 +81,7 @@ function pageAddress(address) {
|
|
|
65
81
|
};
|
|
66
82
|
return Object.values(view).some(Boolean) ? view : null;
|
|
67
83
|
}
|
|
68
|
-
function
|
|
84
|
+
/** Where an order stands as the pay link's page says it. */ export function quotePageStatus(status) {
|
|
69
85
|
if (status === 'pending') return {
|
|
70
86
|
status: 'unpaid',
|
|
71
87
|
label: 'Unpaid'
|
|
@@ -106,7 +122,7 @@ function discountLabel(discount) {
|
|
|
106
122
|
const { order } = input;
|
|
107
123
|
const quote = order.quote;
|
|
108
124
|
const lines = (_order_lineItems = order.lineItems) != null ? _order_lineItems : [];
|
|
109
|
-
const status =
|
|
125
|
+
const status = quotePageStatus(order.status);
|
|
110
126
|
const unpaid = status.status === 'unpaid';
|
|
111
127
|
const charge = quoteCheckoutCharge(order, input.orderId);
|
|
112
128
|
const stored = order.totals;
|
|
@@ -127,6 +143,7 @@ function discountLabel(discount) {
|
|
|
127
143
|
const taxAtPayment = unpaid && (quote == null ? void 0 : (_quote_tax = quote.tax) == null ? void 0 : _quote_tax.kind) === 'at-payment';
|
|
128
144
|
return {
|
|
129
145
|
state: 'quote',
|
|
146
|
+
kind: 'quote',
|
|
130
147
|
storeName: input.storeName,
|
|
131
148
|
number: formatQuoteNumber(order, input.orderId),
|
|
132
149
|
dateMs: Number((_ref = quote == null ? void 0 : quote.updatedAtMs) != null ? _ref : quote == null ? void 0 : quote.createdAtMs) || null,
|
|
@@ -134,29 +151,15 @@ function discountLabel(discount) {
|
|
|
134
151
|
currency: input.currency || 'USD',
|
|
135
152
|
customerName: (_text = text(quote == null ? void 0 : (_quote_customer = quote.customer) == null ? void 0 : _quote_customer.name)) != null ? _text : text(quote == null ? void 0 : (_quote_customer1 = quote.customer) == null ? void 0 : _quote_customer1.company),
|
|
136
153
|
company: text(quote == null ? void 0 : (_quote_customer2 = quote.customer) == null ? void 0 : _quote_customer2.name) ? text(quote == null ? void 0 : (_quote_customer3 = quote.customer) == null ? void 0 : _quote_customer3.company) : null,
|
|
137
|
-
billTo:
|
|
138
|
-
shipTo:
|
|
139
|
-
lines: lines
|
|
140
|
-
var _line_name;
|
|
141
|
-
const included = line.included === true;
|
|
142
|
-
const quantity = Math.max(0, cents(line.quantity));
|
|
143
|
-
const unitAmountCents = included ? 0 : Math.max(0, cents(line.unitAmountCents));
|
|
144
|
-
return {
|
|
145
|
-
name: String((_line_name = line.name) != null ? _line_name : ''),
|
|
146
|
-
variantLabel: text(line.variantLabel),
|
|
147
|
-
description: text(line.description),
|
|
148
|
-
sku: text(line.sku),
|
|
149
|
-
quantity,
|
|
150
|
-
unitAmountCents,
|
|
151
|
-
amountCents: unitAmountCents * quantity,
|
|
152
|
-
included
|
|
153
|
-
};
|
|
154
|
-
}),
|
|
154
|
+
billTo: quotePageAddress(quote == null ? void 0 : quote.billTo),
|
|
155
|
+
shipTo: quotePageAddress(quote == null ? void 0 : quote.shipTo),
|
|
156
|
+
lines: quotePageLines(lines),
|
|
155
157
|
totals,
|
|
156
158
|
discountLabel: discountLabel(quote == null ? void 0 : quote.discount),
|
|
157
159
|
// Once paid, the tax is what was charged: Stripe's figure under Stripe Tax.
|
|
158
160
|
taxLabel: unpaid || (quote == null ? void 0 : (_quote_tax1 = quote.tax) == null ? void 0 : _quote_tax1.kind) !== 'at-payment' ? describeQuoteTax(quote == null ? void 0 : quote.tax) : 'Tax',
|
|
159
161
|
taxAtPayment,
|
|
162
|
+
shippingAtPayment: false,
|
|
160
163
|
notes: text(quote == null ? void 0 : quote.notes),
|
|
161
164
|
terms: text(quote == null ? void 0 : quote.terms),
|
|
162
165
|
status: status.status,
|
|
@@ -185,6 +188,24 @@ function discountLabel(discount) {
|
|
|
185
188
|
}) === null
|
|
186
189
|
};
|
|
187
190
|
}
|
|
191
|
+
/** Order lines as the pay link's page lists them: what each charges, nothing about stock or cost. */ export function quotePageLines(lines) {
|
|
192
|
+
return lines.map((line)=>{
|
|
193
|
+
var _line_name;
|
|
194
|
+
const included = line.included === true;
|
|
195
|
+
const quantity = Math.max(0, cents(line.quantity));
|
|
196
|
+
const unitAmountCents = included ? 0 : Math.max(0, cents(line.unitAmountCents));
|
|
197
|
+
return {
|
|
198
|
+
name: String((_line_name = line.name) != null ? _line_name : ''),
|
|
199
|
+
variantLabel: text(line.variantLabel),
|
|
200
|
+
description: text(line.description),
|
|
201
|
+
sku: text(line.sku),
|
|
202
|
+
quantity,
|
|
203
|
+
unitAmountCents,
|
|
204
|
+
amountCents: unitAmountCents * quantity,
|
|
205
|
+
included
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
188
209
|
/** The latest answer, as the page may show it back to the link's holder: never the address or browser. */ function pageResponse(quote) {
|
|
189
210
|
var _ref;
|
|
190
211
|
var _latest_files;
|
|
@@ -230,13 +251,24 @@ function lineSummary(lines) {
|
|
|
230
251
|
*/ export function quoteCheckoutCharge(order, orderId) {
|
|
231
252
|
var _order_lineItems, _ref, _ref1;
|
|
232
253
|
var _order_quote, _order_quote1, _order_quote_shipping, _order_quote2;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
return payLinkCheckoutCharge({
|
|
255
|
+
lineItems: (_order_lineItems = order.lineItems) != null ? _order_lineItems : [],
|
|
256
|
+
tax: (_ref = (_order_quote = order.quote) == null ? void 0 : _order_quote.tax) != null ? _ref : {
|
|
257
|
+
kind: 'none',
|
|
258
|
+
reason: 'not-collected'
|
|
259
|
+
},
|
|
260
|
+
discount: (_ref1 = (_order_quote1 = order.quote) == null ? void 0 : _order_quote1.discount) != null ? _ref1 : null,
|
|
261
|
+
shippingCents: Math.max(0, cents((_order_quote2 = order.quote) == null ? void 0 : (_order_quote_shipping = _order_quote2.shipping) == null ? void 0 : _order_quote_shipping.amountCents)),
|
|
262
|
+
label: formatQuoteNumber(order, orderId)
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* What a pay link's Checkout Session charges for stored lines, a discount,
|
|
267
|
+
* fixed shipping and a tax basis, under a label (`Quote #1042`,
|
|
268
|
+
* `Invoice #1042`): {@link quoteCheckoutCharge}'s pricing, shared with an
|
|
269
|
+
* invoiced order's (AGL-4099), so both are charged the same way.
|
|
270
|
+
*/ export function payLinkCheckoutCharge(input) {
|
|
271
|
+
const { lineItems, tax, discount, shippingCents, label } = input;
|
|
240
272
|
const totals = computeQuoteTotals({
|
|
241
273
|
lines: lineItems,
|
|
242
274
|
discount,
|
|
@@ -244,7 +276,6 @@ function lineSummary(lines) {
|
|
|
244
276
|
tax
|
|
245
277
|
});
|
|
246
278
|
const amounts = quoteLineAmounts(lineItems, discount);
|
|
247
|
-
const label = formatQuoteNumber(order, orderId);
|
|
248
279
|
const groups = [
|
|
249
280
|
true,
|
|
250
281
|
false
|
|
@@ -323,20 +354,22 @@ function lineSummary(lines) {
|
|
|
323
354
|
}
|
|
324
355
|
}
|
|
325
356
|
export function quotePaymentDisposition(order, payment) {
|
|
326
|
-
var
|
|
327
|
-
|
|
328
|
-
|
|
357
|
+
var _order_quote;
|
|
358
|
+
// Any order's pay link answers the same way (AGL-4099): an invoiced order
|
|
359
|
+
// reads its version and its recorded payments off `payLink`, and its
|
|
360
|
+
// session carries no quote edit to compare.
|
|
361
|
+
const recorded = orderPayLinkUnmatched(order).some((entry)=>(entry == null ? void 0 : entry.sessionId) === payment.sessionId);
|
|
329
362
|
if (recorded) return 'redelivery';
|
|
330
363
|
if (order.status !== 'pending') {
|
|
331
364
|
const same = order.checkoutSessionId && order.checkoutSessionId === payment.sessionId || payment.paymentIntentId && order.paymentIntentId === payment.paymentIntentId;
|
|
332
365
|
return same ? 'redelivery' : 'already-paid';
|
|
333
366
|
}
|
|
334
367
|
const sentVersion = payment.linkVersion;
|
|
335
|
-
if (sentVersion != null && sentVersion !== '' && Number(sentVersion) !==
|
|
368
|
+
if (sentVersion != null && sentVersion !== '' && Number(sentVersion) !== orderPayLinkVersion(order)) {
|
|
336
369
|
return 'superseded';
|
|
337
370
|
}
|
|
338
371
|
const sentEdit = payment.quoteUpdatedAtMs;
|
|
339
|
-
if (sentEdit != null && sentEdit !== '' && Number(sentEdit) !== Number((
|
|
372
|
+
if (sentEdit != null && sentEdit !== '' && Number(sentEdit) !== Number((_order_quote = order.quote) == null ? void 0 : _order_quote.updatedAtMs)) {
|
|
340
373
|
return 'superseded';
|
|
341
374
|
}
|
|
342
375
|
return 'pay';
|
|
@@ -350,9 +383,9 @@ export function quotePaymentDisposition(order, payment) {
|
|
|
350
383
|
reason
|
|
351
384
|
};
|
|
352
385
|
}
|
|
353
|
-
/** The timeline line for a payment the quote could not take. */ export function quoteUnmatchedPaymentDetail(entry) {
|
|
386
|
+
/** The timeline line for a payment the quote (or an order's invoice, AGL-4099) could not take. */ export function quoteUnmatchedPaymentDetail(entry, noun = 'quote') {
|
|
354
387
|
const amount = `$${(entry.amountCents / 100).toFixed(2)}`;
|
|
355
|
-
return entry.reason === 'superseded' ? `A payment of ${amount} came through a pay link that was replaced, or for an earlier ` +
|
|
388
|
+
return entry.reason === 'superseded' ? `A payment of ${amount} came through a pay link that was replaced, or for an earlier ` + `version of this ${noun}. The money is in Stripe and this ${noun} records no sale for it; ` + 'refund it in Stripe, or reconcile it by hand.' : `A second payment of ${amount} came in after this ${noun} was paid. The money is in Stripe ` + 'and no second sale was recorded; refund it in Stripe, or reconcile it by hand.';
|
|
356
389
|
}
|
|
357
390
|
/**
|
|
358
391
|
* The order a paid quote becomes, the whole document as it will stand. A
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-quote-pay.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 { visitorUploadIsInline } from '@aglyn/aglyn/app-utils/visitor-upload'\nimport type { ProductType } from './commerce'\nimport {\n appendOrderEvent,\n formatOrderNumber,\n type HostOrder,\n type OrderAddress,\n type OrderLineItem,\n type OrderStatus,\n} from './commerce-orders'\nimport {\n computeQuoteTotals,\n describeQuoteTax,\n formatQuoteNumber,\n quoteLineAmounts,\n quotePaymentBlockedByApproval,\n type OrderQuote,\n type QuoteDiscount,\n type QuoteTaxBasis,\n type QuoteUnmatchedPayment,\n} from './commerce-quotes'\nimport { currentQuoteProofs, latestQuoteResponse, quoteRespondRefusal } from './commerce-quote-proofs'\nimport type { StorefrontTaxMode } from './commerce-tax-decision'\nimport { engineLineTaxPercentages } from './commerce-tax-engine'\nimport { withOrderListFields, type OrderListFields } from './order-list-fields'\n\n/*==========================================\n * PAYING A QUOTE (AGL-4094).\n *\n * The customer opens the quote on the business's own site from a signed pay\n * link, sees it, and pays it. Everything here is pure, so the page's view,\n * the pay route's charge and the webhook's paid order all come from one\n * reading of the stored quote, and none of them from anything the browser\n * sends:\n *\n * - {@link buildQuotePageView}: the ONE projection of a quote that leaves the\n * server for a visitor holding the link, allow-listed field by field.\n * - {@link quoteCheckoutCharge}: what a Checkout Session charges, priced from\n * the stored lines, discount, shipping and tax basis.\n * - {@link quotePayRefusal}: why a quote cannot be paid now, if it cannot.\n * - {@link quotePaymentDisposition} and {@link paidQuoteOrder}: what a\n * completed payment does to the quote, and the order it becomes.\n *\n * The customer side has no expiry and no open or closed states: a quote and\n * its link stay until the quote is paid or the business replaces the link.\n *=========================================*/\n\n/** Stripe's smallest card charge; a quote that totals less cannot be paid by card. */\nexport const QUOTE_MIN_CHARGE_CENTS = 50\n\n/** The pay link version a quote is on; absent reads as the first. */\nexport function quoteLinkVersion(quote: Pick<OrderQuote, 'linkVersion'> | null | undefined): number {\n const version = Math.floor(Number(quote?.linkVersion))\n return Number.isFinite(version) && version >= 1 ? version : 1\n}\n\n/*==========================================\n * THE CUSTOMER'S PAGE\n *=========================================*/\n\n/** An address as the quote page prints it: no phone. */\nexport interface QuotePageAddress {\n name: string | null\n line1: string | null\n line2: string | null\n city: string | null\n state: string | null\n postalCode: string | null\n country: string | null\n}\n\nexport interface QuotePageLine {\n name: string\n /** The option and add-ons chosen, as order lines print them. */\n variantLabel: string | null\n description: string | null\n sku: string | null\n quantity: number\n unitAmountCents: number\n /** What the line charges: 0 for an included line. */\n amountCents: number\n /** Included at no charge. */\n included: boolean\n}\n\n/** Where the quote stands, as its customer sees it. */\nexport type QuotePageStatus = 'unpaid' | 'paid' | 'canceled' | 'refunded'\n\n/** A proof as the quote page lists it: no storage path, nothing about who attached it. */\nexport interface QuotePageProof {\n id: string\n name: string\n contentType: string\n size: number\n /** Shown in the page as a picture; everything else is a download. */\n inline: boolean\n}\n\n/** The customer's latest answer to the revision the page shows. */\nexport interface QuotePageResponse {\n action: 'approve' | 'changes'\n atMs: number\n /** With an approval: the name it was given under. */\n name: string | null\n comment: string | null\n /** How many files came with it. */\n files: number\n}\n\n/**\n * What the quote page shows. Allow-listed, never spread: the order document\n * carries the customer's email and phone, Stripe ids, the business's own\n * timeline and who wrote the quote, none of which a forwarded link may show.\n * New fields are added here deliberately, or not at all.\n */\nexport interface QuotePageView {\n state: 'quote'\n storeName: string\n /** `Quote #1042`. */\n number: string\n /** When the quote was last written, which is the date it carries. */\n dateMs: number | null\n /**\n * The edit of the quote this view shows. Pay sends it back, and a quote\n * changed since is refused, so nobody pays for a version they have not seen.\n */\n version: number\n currency: string\n customerName: string | null\n company: string | null\n billTo: QuotePageAddress | null\n shipTo: QuotePageAddress | null\n lines: QuotePageLine[]\n totals: {\n itemsCents: number\n discountCents: number\n shippingCents: number\n taxCents: number\n totalCents: number\n }\n /** `10% off`, or null for an amount or no discount. */\n discountLabel: string | null\n /** The tax line's label, or `Tax is added at payment`. */\n taxLabel: string\n /** Stripe Tax works the tax out when the customer pays; `taxCents` is 0 until then. */\n taxAtPayment: boolean\n notes: string | null\n terms: string | null\n status: QuotePageStatus\n statusLabel: string\n paidAtMs: number | null\n /** The order's status page, once paid. */\n receiptUrl: string | null\n /** The business asks the customer to approve the proof before paying. */\n approvalRequired: boolean\n /** A Pay button belongs on the page. */\n payable: boolean\n /** The revision the page shows (AGL-4096). */\n revision: number\n /** That revision's proofs. */\n proofs: QuotePageProof[]\n /** The customer's latest answer to it, if any. */\n response: QuotePageResponse | null\n /** Approve and Request changes belong on the page. */\n respondable: boolean\n}\n\n/** What a signed link for an older version of the quote opens: nothing of the quote. */\nexport interface QuotePageReplacedView {\n state: 'replaced'\n storeName: string\n}\n\nconst text = (value: unknown): string | null => {\n const trimmed = typeof value === 'string' ? value.trim() : ''\n return trimmed ? trimmed : null\n}\n\nconst cents = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) ? number : 0\n}\n\nfunction pageAddress(address: OrderAddress | null | undefined): QuotePageAddress | null {\n if (!address) return null\n const view: QuotePageAddress = {\n name: text(address.name),\n line1: text(address.line1),\n line2: text(address.line2),\n city: text(address.city),\n state: text(address.state),\n postalCode: text(address.postalCode),\n country: text(address.country),\n }\n return Object.values(view).some(Boolean) ? view : null\n}\n\nfunction pageStatus(status: OrderStatus | string | undefined): { status: QuotePageStatus; label: string } {\n if (status === 'pending') return { status: 'unpaid', label: 'Unpaid' }\n if (status === 'cancelled') return { status: 'canceled', label: 'Canceled' }\n if (status === 'refunded') return { status: 'refunded', label: 'Refunded' }\n return { status: 'paid', label: 'Paid' }\n}\n\nfunction discountLabel(discount: QuoteDiscount | null | undefined): string | null {\n if (discount?.kind !== 'percent' || !(Number(discount.pct) > 0)) return null\n return `${Number(discount.pct)}% off`\n}\n\n/** When the quote was paid: its stamp, else the timeline's `paid` event. */\nfunction paidAt(order: Pick<HostOrder, 'status' | 'quote' | 'timeline'>): number | null {\n if (order.status === 'pending') return null\n const stamped = Number(order.quote?.paidAtMs)\n if (Number.isFinite(stamped) && stamped > 0) return stamped\n const event = (order.timeline ?? []).find((entry) => entry?.event === 'paid')\n return event ? Number(event.atMs) || null : null\n}\n\n/**\n * The quote page's view of an order document on the `quote` channel. Totals\n * are the stored lines' through {@link computeQuoteTotals} until the quote is\n * paid, and the paid order's own totals (what Stripe charged) after.\n */\nexport function buildQuotePageView(input: {\n order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'totals' | 'quote' | 'timeline'>\n orderId: string\n storeName: string\n currency: string\n /** The order's status page, for a paid quote. */\n receiptUrl?: string | null\n}): QuotePageView {\n const { order } = input\n const quote = order.quote\n const lines = order.lineItems ?? []\n const status = pageStatus(order.status)\n const unpaid = status.status === 'unpaid'\n const charge = quoteCheckoutCharge(order, input.orderId)\n const stored = order.totals\n const totals = unpaid || !stored\n ? {\n itemsCents: charge.itemsCents,\n discountCents: charge.discountCents,\n shippingCents: charge.shippingCents,\n taxCents: charge.taxCents,\n totalCents: charge.totalCents,\n }\n : {\n itemsCents: cents(stored.itemsCents),\n discountCents: cents(stored.discountCents),\n shippingCents: cents(stored.shippingCents),\n taxCents: cents(stored.taxCents),\n totalCents: cents(stored.totalCents),\n }\n const approvalRequired = unpaid && quotePaymentBlockedByApproval(quote)\n const taxAtPayment = unpaid && quote?.tax?.kind === 'at-payment'\n return {\n state: 'quote',\n storeName: input.storeName,\n number: formatQuoteNumber(order, input.orderId),\n dateMs: Number(quote?.updatedAtMs ?? quote?.createdAtMs) || null,\n version: Number(quote?.updatedAtMs) || 0,\n currency: input.currency || 'USD',\n customerName: text(quote?.customer?.name) ?? text(quote?.customer?.company),\n company: text(quote?.customer?.name) ? text(quote?.customer?.company) : null,\n billTo: pageAddress(quote?.billTo),\n shipTo: pageAddress(quote?.shipTo),\n lines: lines.map((line) => {\n const included = line.included === true\n const quantity = Math.max(0, cents(line.quantity))\n const unitAmountCents = included ? 0 : Math.max(0, cents(line.unitAmountCents))\n return {\n name: String(line.name ?? ''),\n variantLabel: text(line.variantLabel),\n description: text(line.description),\n sku: text(line.sku),\n quantity,\n unitAmountCents,\n amountCents: unitAmountCents * quantity,\n included,\n }\n }),\n totals,\n discountLabel: discountLabel(quote?.discount),\n // Once paid, the tax is what was charged: Stripe's figure under Stripe Tax.\n taxLabel: unpaid || quote?.tax?.kind !== 'at-payment' ? describeQuoteTax(quote?.tax) : 'Tax',\n taxAtPayment,\n notes: text(quote?.notes),\n terms: text(quote?.terms),\n status: status.status,\n statusLabel: status.label,\n paidAtMs: paidAt(order),\n receiptUrl: unpaid ? null : (input.receiptUrl ?? null),\n approvalRequired,\n payable: unpaid && !approvalRequired && quotePayRefusal(order, input.orderId) === null,\n revision: Math.max(1, Number(quote?.revision ?? 1) || 1),\n proofs: currentQuoteProofs(quote).map((proof) => ({\n id: String(proof.id),\n name: String(proof.name ?? 'Proof'),\n contentType: String(proof.contentType ?? ''),\n size: Math.max(0, cents(proof.size)),\n inline: visitorUploadIsInline(String(proof.contentType ?? '')),\n })),\n response: pageResponse(quote),\n // The page is only ever a quote's, whatever the caller picked off the order.\n respondable: quoteRespondRefusal({ channel: 'quote', status: order.status, quote }) === null,\n }\n}\n\n/** The latest answer, as the page may show it back to the link's holder: never the address or browser. */\nfunction pageResponse(quote: OrderQuote | null | undefined): QuotePageResponse | null {\n const latest = latestQuoteResponse(quote)\n if (!latest) return null\n return {\n action: latest.action === 'approve' ? 'approve' : 'changes',\n atMs: Number(latest.atMs) || 0,\n name: latest.action === 'approve' ? text(latest.name) : null,\n comment: latest.action === 'changes' ? text(latest.comment) : null,\n files: latest.files?.length ?? 0,\n }\n}\n\n/*==========================================\n * WHAT A PAYMENT CHARGES\n *=========================================*/\n\n/** One line of a quote's Checkout Session. */\nexport interface QuoteCheckoutLine {\n name: string\n /** The quote's lines this one charges for, as the hosted page lists them. */\n description: string\n amountCents: number\n /** False for tax-exempt products: no rate is applied and Stripe Tax is told it is nontaxable. */\n taxable: boolean\n}\n\n/** What a quote's Checkout Session charges, priced from the stored quote alone. */\nexport interface QuoteCheckoutCharge {\n /** The goods grouped by whether they are taxed, so one rate taxes exactly the quote's taxable amount. */\n lines: QuoteCheckoutLine[]\n itemsCents: number\n discountCents: number\n /** The goods after the discount: what the business is paid for them. */\n goodsCents: number\n taxableCents: number\n shippingCents: number\n /** The quote's tax; 0 when Stripe Tax works it out at payment. */\n taxCents: number\n /**\n * The percentage applied to the taxed line as a Stripe tax rate, or null\n * when no rate is applied (no tax, Stripe Tax, or tax charged as its own line).\n */\n taxPct: number | null\n taxAtPayment: boolean\n /** Goods, shipping and the quote's tax. Stripe Tax adds to it at payment. */\n totalCents: number\n /** What the platform fee prices the goods as. */\n feeProductType: ProductType\n}\n\nconst LINE_DESCRIPTION_MAX = 500\n\nfunction lineSummary(lines: readonly OrderLineItem[]): string {\n const parts = lines.map((line) => {\n const label = line.variantLabel ? `${line.name} (${line.variantLabel})` : line.name\n return `${Math.max(1, cents(line.quantity))} × ${label}`\n })\n const summary = parts.join(', ')\n return summary.length > LINE_DESCRIPTION_MAX ? `${summary.slice(0, LINE_DESCRIPTION_MAX - 1)}…` : summary\n}\n\n/** What the fee ladder prices the quote's charged goods as: physical when any line ships. */\nfunction feeProductTypeOf(lines: readonly OrderLineItem[]): ProductType {\n const charged = lines.filter((line) => !line.included)\n const types = new Set(charged.map((line) => line.productType ?? 'physical'))\n if (types.has('physical') || types.size === 0) return 'physical'\n return types.has('service') ? 'service' : 'digital'\n}\n\n/**\n * The charge for a quote, from its stored lines, discount, shipping and tax\n * basis through {@link computeQuoteTotals} — the same function that priced it\n * when it was saved, so the page, the session and the stored quote agree.\n *\n * The goods are charged as at most two lines, the taxed and the tax-exempt,\n * each after its share of the discount. A store rate is applied to the taxed\n * line as one Stripe tax rate, so Stripe's tax is the rate on exactly the\n * amount the quote taxed, and the quote's figure is what is collected. A tax\n * service's figure is carried the same way, as the percentage of the taxed\n * line it amounts to. Included lines charge nothing and are not sent.\n */\nexport function quoteCheckoutCharge(\n order: Pick<HostOrder, 'number' | 'lineItems' | 'quote'>,\n orderId: string,\n): QuoteCheckoutCharge {\n const lineItems = order.lineItems ?? []\n const tax: QuoteTaxBasis = order.quote?.tax ?? { kind: 'none', reason: 'not-collected' }\n const discount = order.quote?.discount ?? null\n const shippingCents = Math.max(0, cents(order.quote?.shipping?.amountCents))\n const totals = computeQuoteTotals({ lines: lineItems, discount, shippingCents, tax })\n const amounts = quoteLineAmounts(lineItems, discount)\n const label = formatQuoteNumber(order, orderId)\n const groups = [true, false].map((taxable) => {\n const members = lineItems\n .map((line, index) => ({ line, net: amounts.netCents[index] }))\n .filter(({ line, net }) => net > 0 && (line.taxExempt !== true) === taxable)\n return {\n taxable,\n amountCents: members.reduce((sum, { net }) => sum + net, 0),\n description: lineSummary(members.map(({ line }) => line)),\n }\n })\n const charged = groups.filter((group) => group.amountCents > 0)\n const lines: QuoteCheckoutLine[] = charged.map((group) => ({\n name: charged.length > 1 && !group.taxable ? `${label} (tax-exempt items)` : label,\n description: group.description,\n amountCents: group.amountCents,\n taxable: group.taxable,\n }))\n const taxableCents = groups[0].amountCents\n const taxPct =\n totals.taxCents > 0 && taxableCents > 0\n ? tax.kind === 'rate'\n ? Number(tax.pct)\n : engineLineTaxPercentages([{ amountCents: taxableCents, taxCents: totals.taxCents }], 0)[0]\n : null\n // A tax service can tax a quote whose goods are all exempt (on its\n // shipping, say): with no taxed line to carry a rate, its figure is\n // charged as a line of its own, so the customer still pays what the quote says.\n if (totals.taxCents > 0 && taxPct === null) {\n lines.push({\n name: tax.providerLabel ? `Sales tax (${tax.providerLabel})` : 'Sales tax',\n description: label,\n amountCents: totals.taxCents,\n taxable: false,\n })\n }\n return {\n lines,\n itemsCents: totals.itemsCents,\n discountCents: totals.discountCents,\n goodsCents: totals.itemsCents - totals.discountCents,\n taxableCents,\n shippingCents,\n taxCents: totals.taxCents,\n taxPct,\n taxAtPayment: totals.taxAtPayment,\n totalCents: totals.totalCents,\n feeProductType: feeProductTypeOf(lineItems),\n }\n}\n\n/*==========================================\n * WHETHER IT CAN BE PAID NOW\n *=========================================*/\n\nexport type QuotePayRefusal =\n | 'paid'\n | 'closed'\n | 'approval-required'\n | 'tax-undecided'\n | 'nothing-to-pay'\n\n/** Why a quote cannot be paid now, or null when it can. */\nexport function quotePayRefusal(\n order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'quote'>,\n orderId: string,\n): QuotePayRefusal | null {\n if (order.status === 'cancelled' || order.status === 'refunded') return 'closed'\n if (order.status !== 'pending') return 'paid'\n if (quotePaymentBlockedByApproval(order.quote)) return 'approval-required'\n if (order.quote?.tax?.kind === 'none' && order.quote.tax.reason === 'undecided') return 'tax-undecided'\n if (quoteCheckoutCharge(order, orderId).totalCents < QUOTE_MIN_CHARGE_CENTS) return 'nothing-to-pay'\n return null\n}\n\n/** What the customer is told for a {@link QuotePayRefusal}, naming the business. */\nexport function quotePayRefusalMessage(reason: QuotePayRefusal, storeName: string): string {\n const business = storeName.trim() || 'the business'\n switch (reason) {\n case 'paid':\n return 'This quote has been paid.'\n case 'closed':\n return `This quote is closed. Contact ${business} with any questions.`\n case 'approval-required':\n return `${business} asks you to approve the proof before you pay.`\n case 'tax-undecided':\n return `${business} has not finished setting up payments. Contact them to pay this quote.`\n case 'nothing-to-pay':\n return `This quote has nothing to pay by card. Contact ${business} with any questions.`\n }\n}\n\n/*==========================================\n * WHEN A PAYMENT COMPLETES\n *=========================================*/\n\n/** What a completed Checkout Session says about the quote it paid. */\nexport interface QuotePaymentFacts {\n sessionId: string\n paymentIntentId: string | null\n /** The link version and quote edit the session was minted for, from its metadata. */\n linkVersion?: string | number | null\n quoteUpdatedAtMs?: string | number | null\n paidAtMs: number\n /** The payer, as Stripe collected them. */\n email: string | null\n name: string | null\n /** What Stripe charged in all, and its shipping and tax parts. */\n amountTotalCents: number | null\n shippingCents: number\n taxCents: number\n /** The platform fee the session was minted with. */\n feeCents: number\n taxMode: StorefrontTaxMode\n /** The address Stripe collected, when the session asked for one. */\n shippingAddress: OrderAddress | null\n}\n\n/**\n * What a completed payment does to a quote:\n *\n * - `pay`: the quote is unpaid and the payment is for it as it stands, so it\n * becomes the order.\n * - `superseded`: the quote is unpaid, but the payment was made through a pay\n * link the business replaced, or for a version of the quote it has since\n * changed. Taking it would record a sale at a price or for a link the\n * business withdrew.\n * - `already-paid`: another payment paid the quote first.\n * - `redelivery`: Stripe sending this same payment again; nothing to do.\n *\n * The two refusals keep the money visible: they are recorded on the quote and\n * the business is told, so a payment is never fulfilled twice and never lost.\n */\nexport type QuotePaymentDisposition = 'pay' | 'superseded' | 'already-paid' | 'redelivery'\n\nexport function quotePaymentDisposition(\n order: Pick<HostOrder, 'status' | 'quote' | 'checkoutSessionId' | 'paymentIntentId'>,\n payment: Pick<QuotePaymentFacts, 'sessionId' | 'paymentIntentId' | 'linkVersion' | 'quoteUpdatedAtMs'>,\n): QuotePaymentDisposition {\n const recorded = (order.quote?.unmatchedPayments ?? []).some(\n (entry) => entry?.sessionId === payment.sessionId,\n )\n if (recorded) return 'redelivery'\n if (order.status !== 'pending') {\n const same =\n (order.checkoutSessionId && order.checkoutSessionId === payment.sessionId) ||\n (payment.paymentIntentId && order.paymentIntentId === payment.paymentIntentId)\n return same ? 'redelivery' : 'already-paid'\n }\n const sentVersion = payment.linkVersion\n if (sentVersion != null && sentVersion !== '' && Number(sentVersion) !== quoteLinkVersion(order.quote)) {\n return 'superseded'\n }\n const sentEdit = payment.quoteUpdatedAtMs\n if (sentEdit != null && sentEdit !== '' && Number(sentEdit) !== Number(order.quote?.updatedAtMs)) {\n return 'superseded'\n }\n return 'pay'\n}\n\n/** A payment the quote could not take, as {@link QuoteUnmatchedPayment} records it. */\nexport function quoteUnmatchedPayment(\n facts: Pick<QuotePaymentFacts, 'sessionId' | 'paymentIntentId' | 'amountTotalCents' | 'paidAtMs'>,\n reason: QuoteUnmatchedPayment['reason'],\n): QuoteUnmatchedPayment {\n return {\n sessionId: facts.sessionId,\n paymentIntentId: facts.paymentIntentId,\n amountCents: Math.max(0, cents(facts.amountTotalCents)),\n atMs: facts.paidAtMs,\n reason,\n }\n}\n\n/** The timeline line for a payment the quote could not take. */\nexport function quoteUnmatchedPaymentDetail(entry: QuoteUnmatchedPayment): string {\n const amount = `$${(entry.amountCents / 100).toFixed(2)}`\n return entry.reason === 'superseded'\n ? `A payment of ${amount} came through a pay link that was replaced, or for an earlier ` +\n 'version of this quote. The money is in Stripe and this quote records no sale for it; ' +\n 'refund it in Stripe, or reconcile it by hand.'\n : `A second payment of ${amount} came in after this quote was paid. The money is in Stripe ` +\n 'and no second sale was recorded; refund it in Stripe, or reconcile it by hand.'\n}\n\n/**\n * The order a paid quote becomes, the whole document as it will stand. A\n * quote carries none of the fields order lists read (`commerce-quotes.ts`);\n * paying it writes them:\n *\n * - `createdAtMs` is the time it was paid, the moment it became an order, so\n * it lists and counts on the day the money came in.\n * - The customer moves to the top-level fields every order reader asks\n * (`customerEmail`, `customerName`, `customerPhone`, `customerRecord`), the\n * payer's own email first, as a paid draft takes it.\n * - The address Stripe collected ships it, else the quote's ship-to; the\n * quote's bill-to bills it.\n * - The totals are what Stripe charged: its shipping and tax where it charged\n * any, the platform fee the session was minted with, and its total.\n * - The list fields (`withOrderListFields`) are stamped from all of that.\n */\nexport function paidQuoteOrder(\n order: HostOrder,\n orderId: string,\n facts: QuotePaymentFacts,\n): HostOrder & OrderListFields & { updatedAtMs: number; requiresShipping: boolean } {\n const quote = order.quote as OrderQuote\n const customer = quote?.customer ?? {}\n const stored = order.totals ?? { itemsCents: 0, discountCents: 0, shippingCents: 0, taxCents: 0, feeCents: 0, totalCents: 0 }\n const shippingCents = facts.shippingCents > 0 ? facts.shippingCents : cents(stored.shippingCents)\n const taxCents = facts.taxCents > 0 ? facts.taxCents : cents(stored.taxCents)\n const totalCents =\n facts.amountTotalCents != null && Number.isFinite(facts.amountTotalCents)\n ? facts.amountTotalCents\n : cents(stored.itemsCents) - cents(stored.discountCents) + shippingCents + taxCents\n const shippingAddress = facts.shippingAddress ?? quote?.shipTo ?? null\n const label = formatOrderNumber(order, orderId)\n const paid: HostOrder & { updatedAtMs: number } = {\n ...order,\n status: 'paid',\n createdAtMs: facts.paidAtMs,\n customerEmail: text(facts.email) ?? text(customer.email),\n customerName: text(customer.name) ?? text(facts.name) ?? text(customer.company),\n ...(text(customer.phone) ? { customerPhone: text(customer.phone) } : {}),\n ...(customer.record?.kind && customer.record?.id ? { customerRecord: { ...customer.record } } : {}),\n ...(shippingAddress ? { shippingAddress: { ...shippingAddress } } : {}),\n ...(quote?.billTo ? { billingAddress: { ...quote.billTo } } : {}),\n totals: {\n itemsCents: cents(stored.itemsCents),\n discountCents: cents(stored.discountCents),\n shippingCents,\n taxCents,\n feeCents: Math.max(0, cents(facts.feeCents)),\n totalCents,\n },\n taxMode: facts.taxMode,\n paymentIntentId: facts.paymentIntentId ?? undefined,\n checkoutSessionId: facts.sessionId,\n timeline: appendOrderEvent(order, 'paid', `${label} paid through its pay link`, facts.paidAtMs),\n quote: { ...quote, paidAtMs: facts.paidAtMs },\n // New to a shipping tool's feed from the moment it is paid.\n updatedAtMs: facts.paidAtMs,\n }\n if (!paid.paymentIntentId) delete paid.paymentIntentId\n return withOrderListFields(orderId, paid)\n}\n"],"names":["visitorUploadIsInline","appendOrderEvent","formatOrderNumber","computeQuoteTotals","describeQuoteTax","formatQuoteNumber","quoteLineAmounts","quotePaymentBlockedByApproval","currentQuoteProofs","latestQuoteResponse","quoteRespondRefusal","engineLineTaxPercentages","withOrderListFields","QUOTE_MIN_CHARGE_CENTS","quoteLinkVersion","quote","version","Math","floor","Number","linkVersion","isFinite","text","value","trimmed","trim","cents","number","round","pageAddress","address","view","name","line1","line2","city","state","postalCode","country","Object","values","some","Boolean","pageStatus","status","label","discountLabel","discount","kind","pct","paidAt","order","stamped","paidAtMs","event","timeline","find","entry","atMs","buildQuotePageView","input","lines","lineItems","unpaid","charge","quoteCheckoutCharge","orderId","stored","totals","itemsCents","discountCents","shippingCents","taxCents","totalCents","approvalRequired","taxAtPayment","tax","storeName","dateMs","updatedAtMs","createdAtMs","currency","customerName","customer","company","billTo","shipTo","map","line","included","quantity","max","unitAmountCents","String","variantLabel","description","sku","amountCents","taxLabel","notes","terms","statusLabel","receiptUrl","payable","quotePayRefusal","revision","proofs","proof","id","contentType","size","inline","response","pageResponse","respondable","channel","latest","action","comment","files","length","LINE_DESCRIPTION_MAX","lineSummary","parts","summary","join","slice","feeProductTypeOf","charged","filter","types","Set","productType","has","reason","shipping","amounts","groups","taxable","members","index","net","netCents","taxExempt","reduce","sum","group","taxableCents","taxPct","push","providerLabel","goodsCents","feeProductType","quotePayRefusalMessage","business","quotePaymentDisposition","payment","recorded","unmatchedPayments","sessionId","same","checkoutSessionId","paymentIntentId","sentVersion","sentEdit","quoteUpdatedAtMs","quoteUnmatchedPayment","facts","amountTotalCents","quoteUnmatchedPaymentDetail","amount","toFixed","paidQuoteOrder","feeCents","shippingAddress","paid","customerEmail","email","phone","customerPhone","record","customerRecord","billingAddress","taxMode","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-quote-pay.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 { visitorUploadIsInline } from '@aglyn/aglyn/app-utils/visitor-upload'\nimport type { ProductType } from './commerce'\nimport {\n appendOrderEvent,\n formatOrderNumber,\n type HostOrder,\n type OrderAddress,\n type OrderLineItem,\n type OrderStatus,\n} from './commerce-orders'\nimport {\n computeQuoteTotals,\n describeQuoteTax,\n formatQuoteNumber,\n quoteLineAmounts,\n quotePaymentBlockedByApproval,\n type OrderQuote,\n type QuoteDiscount,\n type QuoteTaxBasis,\n type QuoteUnmatchedPayment,\n} from './commerce-quotes'\nimport { currentQuoteProofs, latestQuoteResponse, quoteRespondRefusal } from './commerce-quote-proofs'\nimport type { StorefrontTaxMode } from './commerce-tax-decision'\nimport { engineLineTaxPercentages } from './commerce-tax-engine'\nimport { withOrderListFields, type OrderListFields } from './order-list-fields'\n\n/*==========================================\n * PAYING A QUOTE (AGL-4094).\n *\n * The customer opens the quote on the business's own site from a signed pay\n * link, sees it, and pays it. Everything here is pure, so the page's view,\n * the pay route's charge and the webhook's paid order all come from one\n * reading of the stored quote, and none of them from anything the browser\n * sends:\n *\n * - {@link buildQuotePageView}: the ONE projection of a quote that leaves the\n * server for a visitor holding the link, allow-listed field by field.\n * - {@link quoteCheckoutCharge}: what a Checkout Session charges, priced from\n * the stored lines, discount, shipping and tax basis.\n * - {@link quotePayRefusal}: why a quote cannot be paid now, if it cannot.\n * - {@link quotePaymentDisposition} and {@link paidQuoteOrder}: what a\n * completed payment does to the quote, and the order it becomes.\n *\n * The customer side has no expiry and no open or closed states: a quote and\n * its link stay until the quote is paid or the business replaces the link.\n *=========================================*/\n\n/** Stripe's smallest card charge; a quote that totals less cannot be paid by card. */\nexport const QUOTE_MIN_CHARGE_CENTS = 50\n\n/** The pay link version a quote is on; absent reads as the first. */\nexport function quoteLinkVersion(quote: Pick<OrderQuote, 'linkVersion'> | null | undefined): number {\n const version = Math.floor(Number(quote?.linkVersion))\n return Number.isFinite(version) && version >= 1 ? version : 1\n}\n\n/**\n * The pay link version any order is on (AGL-4099). A quote keeps its version\n * where slice 2 put it, `quote.linkVersion`, so every link already sent keeps\n * verifying; any other order keeps it as `payLink.version`. Absent reads as\n * the first either way.\n */\nexport function orderPayLinkVersion(order: Partial<Pick<HostOrder, 'quote' | 'payLink'>> | null | undefined): number {\n if (order?.quote) return quoteLinkVersion(order.quote)\n const version = Math.floor(Number(order?.payLink?.version))\n return Number.isFinite(version) && version >= 1 ? version : 1\n}\n\n/** Payments the order's pay link could not take: a quote's on the quote, any other order's on `payLink`. */\nexport function orderPayLinkUnmatched(\n order: Partial<Pick<HostOrder, 'quote' | 'payLink'>> | null | undefined,\n): QuoteUnmatchedPayment[] {\n const list = order?.quote ? order.quote.unmatchedPayments : order?.payLink?.unmatchedPayments\n return Array.isArray(list) ? list.filter((entry) => entry && typeof entry === 'object') : []\n}\n\n/*==========================================\n * THE CUSTOMER'S PAGE\n *=========================================*/\n\n/** An address as the quote page prints it: no phone. */\nexport interface QuotePageAddress {\n name: string | null\n line1: string | null\n line2: string | null\n city: string | null\n state: string | null\n postalCode: string | null\n country: string | null\n}\n\nexport interface QuotePageLine {\n name: string\n /** The option and add-ons chosen, as order lines print them. */\n variantLabel: string | null\n description: string | null\n sku: string | null\n quantity: number\n unitAmountCents: number\n /** What the line charges: 0 for an included line. */\n amountCents: number\n /** Included at no charge. */\n included: boolean\n}\n\n/** Where the quote stands, as its customer sees it. */\nexport type QuotePageStatus = 'unpaid' | 'paid' | 'canceled' | 'refunded'\n\n/** A proof as the quote page lists it: no storage path, nothing about who attached it. */\nexport interface QuotePageProof {\n id: string\n name: string\n contentType: string\n size: number\n /** Shown in the page as a picture; everything else is a download. */\n inline: boolean\n}\n\n/** The customer's latest answer to the revision the page shows. */\nexport interface QuotePageResponse {\n action: 'approve' | 'changes'\n atMs: number\n /** With an approval: the name it was given under. */\n name: string | null\n comment: string | null\n /** How many files came with it. */\n files: number\n}\n\n/**\n * What the quote page shows. Allow-listed, never spread: the order document\n * carries the customer's email and phone, Stripe ids, the business's own\n * timeline and who wrote the quote, none of which a forwarded link may show.\n * New fields are added here deliberately, or not at all.\n */\nexport interface QuotePageView {\n state: 'quote'\n /**\n * What the page shows (AGL-4099): a quote, or an invoice for an order the\n * business sent one for. An invoice has no proofs and no approval, and the\n * page says \"invoice\" where a quote's says \"quote\".\n */\n kind: 'quote' | 'invoice'\n storeName: string\n /** `Quote #1042`, or `Invoice #1042`. */\n number: string\n /** When the quote was last written, which is the date it carries. */\n dateMs: number | null\n /**\n * The edit of the quote this view shows. Pay sends it back, and a quote\n * changed since is refused, so nobody pays for a version they have not seen.\n */\n version: number\n currency: string\n customerName: string | null\n company: string | null\n billTo: QuotePageAddress | null\n shipTo: QuotePageAddress | null\n lines: QuotePageLine[]\n totals: {\n itemsCents: number\n discountCents: number\n shippingCents: number\n taxCents: number\n totalCents: number\n }\n /** `10% off`, or null for an amount or no discount. */\n discountLabel: string | null\n /** The tax line's label, or `Tax is added at payment`. */\n taxLabel: string\n /** Stripe Tax works the tax out when the customer pays; `taxCents` is 0 until then. */\n taxAtPayment: boolean\n /**\n * The customer picks a shipping rate when they pay (an invoiced draft\n * order, AGL-4099); `shippingCents` is 0 until then. A quote's shipping is\n * priced on it, so this is false for every quote.\n */\n shippingAtPayment: boolean\n notes: string | null\n terms: string | null\n status: QuotePageStatus\n statusLabel: string\n paidAtMs: number | null\n /** The order's status page, once paid. */\n receiptUrl: string | null\n /** The business asks the customer to approve the proof before paying. */\n approvalRequired: boolean\n /** A Pay button belongs on the page. */\n payable: boolean\n /** The revision the page shows (AGL-4096). */\n revision: number\n /** That revision's proofs. */\n proofs: QuotePageProof[]\n /** The customer's latest answer to it, if any. */\n response: QuotePageResponse | null\n /** Approve and Request changes belong on the page. */\n respondable: boolean\n}\n\n/** What a signed link for an older version of the quote opens: nothing of the quote. */\nexport interface QuotePageReplacedView {\n state: 'replaced'\n storeName: string\n}\n\nconst text = (value: unknown): string | null => {\n const trimmed = typeof value === 'string' ? value.trim() : ''\n return trimmed ? trimmed : null\n}\n\nconst cents = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) ? number : 0\n}\n\n/** An address as the pay link's page prints it, or null when it says nothing. */\nexport function quotePageAddress(address: OrderAddress | null | undefined): QuotePageAddress | null {\n if (!address) return null\n const view: QuotePageAddress = {\n name: text(address.name),\n line1: text(address.line1),\n line2: text(address.line2),\n city: text(address.city),\n state: text(address.state),\n postalCode: text(address.postalCode),\n country: text(address.country),\n }\n return Object.values(view).some(Boolean) ? view : null\n}\n\n/** Where an order stands as the pay link's page says it. */\nexport function quotePageStatus(status: OrderStatus | string | undefined): { status: QuotePageStatus; label: string } {\n if (status === 'pending') return { status: 'unpaid', label: 'Unpaid' }\n if (status === 'cancelled') return { status: 'canceled', label: 'Canceled' }\n if (status === 'refunded') return { status: 'refunded', label: 'Refunded' }\n return { status: 'paid', label: 'Paid' }\n}\n\nfunction discountLabel(discount: QuoteDiscount | null | undefined): string | null {\n if (discount?.kind !== 'percent' || !(Number(discount.pct) > 0)) return null\n return `${Number(discount.pct)}% off`\n}\n\n/** When the quote was paid: its stamp, else the timeline's `paid` event. */\nfunction paidAt(order: Pick<HostOrder, 'status' | 'quote' | 'timeline'>): number | null {\n if (order.status === 'pending') return null\n const stamped = Number(order.quote?.paidAtMs)\n if (Number.isFinite(stamped) && stamped > 0) return stamped\n const event = (order.timeline ?? []).find((entry) => entry?.event === 'paid')\n return event ? Number(event.atMs) || null : null\n}\n\n/**\n * The quote page's view of an order document on the `quote` channel. Totals\n * are the stored lines' through {@link computeQuoteTotals} until the quote is\n * paid, and the paid order's own totals (what Stripe charged) after.\n */\nexport function buildQuotePageView(input: {\n order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'totals' | 'quote' | 'timeline'>\n orderId: string\n storeName: string\n currency: string\n /** The order's status page, for a paid quote. */\n receiptUrl?: string | null\n}): QuotePageView {\n const { order } = input\n const quote = order.quote\n const lines = order.lineItems ?? []\n const status = quotePageStatus(order.status)\n const unpaid = status.status === 'unpaid'\n const charge = quoteCheckoutCharge(order, input.orderId)\n const stored = order.totals\n const totals = unpaid || !stored\n ? {\n itemsCents: charge.itemsCents,\n discountCents: charge.discountCents,\n shippingCents: charge.shippingCents,\n taxCents: charge.taxCents,\n totalCents: charge.totalCents,\n }\n : {\n itemsCents: cents(stored.itemsCents),\n discountCents: cents(stored.discountCents),\n shippingCents: cents(stored.shippingCents),\n taxCents: cents(stored.taxCents),\n totalCents: cents(stored.totalCents),\n }\n const approvalRequired = unpaid && quotePaymentBlockedByApproval(quote)\n const taxAtPayment = unpaid && quote?.tax?.kind === 'at-payment'\n return {\n state: 'quote',\n kind: 'quote',\n storeName: input.storeName,\n number: formatQuoteNumber(order, input.orderId),\n dateMs: Number(quote?.updatedAtMs ?? quote?.createdAtMs) || null,\n version: Number(quote?.updatedAtMs) || 0,\n currency: input.currency || 'USD',\n customerName: text(quote?.customer?.name) ?? text(quote?.customer?.company),\n company: text(quote?.customer?.name) ? text(quote?.customer?.company) : null,\n billTo: quotePageAddress(quote?.billTo),\n shipTo: quotePageAddress(quote?.shipTo),\n lines: quotePageLines(lines),\n totals,\n discountLabel: discountLabel(quote?.discount),\n // Once paid, the tax is what was charged: Stripe's figure under Stripe Tax.\n taxLabel: unpaid || quote?.tax?.kind !== 'at-payment' ? describeQuoteTax(quote?.tax) : 'Tax',\n taxAtPayment,\n shippingAtPayment: false,\n notes: text(quote?.notes),\n terms: text(quote?.terms),\n status: status.status,\n statusLabel: status.label,\n paidAtMs: paidAt(order),\n receiptUrl: unpaid ? null : (input.receiptUrl ?? null),\n approvalRequired,\n payable: unpaid && !approvalRequired && quotePayRefusal(order, input.orderId) === null,\n revision: Math.max(1, Number(quote?.revision ?? 1) || 1),\n proofs: currentQuoteProofs(quote).map((proof) => ({\n id: String(proof.id),\n name: String(proof.name ?? 'Proof'),\n contentType: String(proof.contentType ?? ''),\n size: Math.max(0, cents(proof.size)),\n inline: visitorUploadIsInline(String(proof.contentType ?? '')),\n })),\n response: pageResponse(quote),\n // The page is only ever a quote's, whatever the caller picked off the order.\n respondable: quoteRespondRefusal({ channel: 'quote', status: order.status, quote }) === null,\n }\n}\n\n/** Order lines as the pay link's page lists them: what each charges, nothing about stock or cost. */\nexport function quotePageLines(lines: readonly OrderLineItem[]): QuotePageLine[] {\n return lines.map((line) => {\n const included = line.included === true\n const quantity = Math.max(0, cents(line.quantity))\n const unitAmountCents = included ? 0 : Math.max(0, cents(line.unitAmountCents))\n return {\n name: String(line.name ?? ''),\n variantLabel: text(line.variantLabel),\n description: text(line.description),\n sku: text(line.sku),\n quantity,\n unitAmountCents,\n amountCents: unitAmountCents * quantity,\n included,\n }\n })\n}\n\n/** The latest answer, as the page may show it back to the link's holder: never the address or browser. */\nfunction pageResponse(quote: OrderQuote | null | undefined): QuotePageResponse | null {\n const latest = latestQuoteResponse(quote)\n if (!latest) return null\n return {\n action: latest.action === 'approve' ? 'approve' : 'changes',\n atMs: Number(latest.atMs) || 0,\n name: latest.action === 'approve' ? text(latest.name) : null,\n comment: latest.action === 'changes' ? text(latest.comment) : null,\n files: latest.files?.length ?? 0,\n }\n}\n\n/*==========================================\n * WHAT A PAYMENT CHARGES\n *=========================================*/\n\n/** One line of a quote's Checkout Session. */\nexport interface QuoteCheckoutLine {\n name: string\n /** The quote's lines this one charges for, as the hosted page lists them. */\n description: string\n amountCents: number\n /** False for tax-exempt products: no rate is applied and Stripe Tax is told it is nontaxable. */\n taxable: boolean\n}\n\n/** What a quote's Checkout Session charges, priced from the stored quote alone. */\nexport interface QuoteCheckoutCharge {\n /** The goods grouped by whether they are taxed, so one rate taxes exactly the quote's taxable amount. */\n lines: QuoteCheckoutLine[]\n itemsCents: number\n discountCents: number\n /** The goods after the discount: what the business is paid for them. */\n goodsCents: number\n taxableCents: number\n shippingCents: number\n /** The quote's tax; 0 when Stripe Tax works it out at payment. */\n taxCents: number\n /**\n * The percentage applied to the taxed line as a Stripe tax rate, or null\n * when no rate is applied (no tax, Stripe Tax, or tax charged as its own line).\n */\n taxPct: number | null\n taxAtPayment: boolean\n /** Goods, shipping and the quote's tax. Stripe Tax adds to it at payment. */\n totalCents: number\n /** What the platform fee prices the goods as. */\n feeProductType: ProductType\n}\n\nconst LINE_DESCRIPTION_MAX = 500\n\nfunction lineSummary(lines: readonly OrderLineItem[]): string {\n const parts = lines.map((line) => {\n const label = line.variantLabel ? `${line.name} (${line.variantLabel})` : line.name\n return `${Math.max(1, cents(line.quantity))} × ${label}`\n })\n const summary = parts.join(', ')\n return summary.length > LINE_DESCRIPTION_MAX ? `${summary.slice(0, LINE_DESCRIPTION_MAX - 1)}…` : summary\n}\n\n/** What the fee ladder prices the quote's charged goods as: physical when any line ships. */\nfunction feeProductTypeOf(lines: readonly OrderLineItem[]): ProductType {\n const charged = lines.filter((line) => !line.included)\n const types = new Set(charged.map((line) => line.productType ?? 'physical'))\n if (types.has('physical') || types.size === 0) return 'physical'\n return types.has('service') ? 'service' : 'digital'\n}\n\n/**\n * The charge for a quote, from its stored lines, discount, shipping and tax\n * basis through {@link computeQuoteTotals} — the same function that priced it\n * when it was saved, so the page, the session and the stored quote agree.\n *\n * The goods are charged as at most two lines, the taxed and the tax-exempt,\n * each after its share of the discount. A store rate is applied to the taxed\n * line as one Stripe tax rate, so Stripe's tax is the rate on exactly the\n * amount the quote taxed, and the quote's figure is what is collected. A tax\n * service's figure is carried the same way, as the percentage of the taxed\n * line it amounts to. Included lines charge nothing and are not sent.\n */\nexport function quoteCheckoutCharge(\n order: Pick<HostOrder, 'number' | 'lineItems' | 'quote'>,\n orderId: string,\n): QuoteCheckoutCharge {\n return payLinkCheckoutCharge({\n lineItems: order.lineItems ?? [],\n tax: order.quote?.tax ?? { kind: 'none', reason: 'not-collected' },\n discount: order.quote?.discount ?? null,\n shippingCents: Math.max(0, cents(order.quote?.shipping?.amountCents)),\n label: formatQuoteNumber(order, orderId),\n })\n}\n\n/**\n * What a pay link's Checkout Session charges for stored lines, a discount,\n * fixed shipping and a tax basis, under a label (`Quote #1042`,\n * `Invoice #1042`): {@link quoteCheckoutCharge}'s pricing, shared with an\n * invoiced order's (AGL-4099), so both are charged the same way.\n */\nexport function payLinkCheckoutCharge(input: {\n lineItems: readonly OrderLineItem[]\n tax: QuoteTaxBasis\n discount: QuoteDiscount | null\n shippingCents: number\n label: string\n}): QuoteCheckoutCharge {\n const { lineItems, tax, discount, shippingCents, label } = input\n const totals = computeQuoteTotals({ lines: lineItems, discount, shippingCents, tax })\n const amounts = quoteLineAmounts(lineItems, discount)\n const groups = [true, false].map((taxable) => {\n const members = lineItems\n .map((line, index) => ({ line, net: amounts.netCents[index] }))\n .filter(({ line, net }) => net > 0 && (line.taxExempt !== true) === taxable)\n return {\n taxable,\n amountCents: members.reduce((sum, { net }) => sum + net, 0),\n description: lineSummary(members.map(({ line }) => line)),\n }\n })\n const charged = groups.filter((group) => group.amountCents > 0)\n const lines: QuoteCheckoutLine[] = charged.map((group) => ({\n name: charged.length > 1 && !group.taxable ? `${label} (tax-exempt items)` : label,\n description: group.description,\n amountCents: group.amountCents,\n taxable: group.taxable,\n }))\n const taxableCents = groups[0].amountCents\n const taxPct =\n totals.taxCents > 0 && taxableCents > 0\n ? tax.kind === 'rate'\n ? Number(tax.pct)\n : engineLineTaxPercentages([{ amountCents: taxableCents, taxCents: totals.taxCents }], 0)[0]\n : null\n // A tax service can tax a quote whose goods are all exempt (on its\n // shipping, say): with no taxed line to carry a rate, its figure is\n // charged as a line of its own, so the customer still pays what the quote says.\n if (totals.taxCents > 0 && taxPct === null) {\n lines.push({\n name: tax.providerLabel ? `Sales tax (${tax.providerLabel})` : 'Sales tax',\n description: label,\n amountCents: totals.taxCents,\n taxable: false,\n })\n }\n return {\n lines,\n itemsCents: totals.itemsCents,\n discountCents: totals.discountCents,\n goodsCents: totals.itemsCents - totals.discountCents,\n taxableCents,\n shippingCents,\n taxCents: totals.taxCents,\n taxPct,\n taxAtPayment: totals.taxAtPayment,\n totalCents: totals.totalCents,\n feeProductType: feeProductTypeOf(lineItems),\n }\n}\n\n/*==========================================\n * WHETHER IT CAN BE PAID NOW\n *=========================================*/\n\nexport type QuotePayRefusal =\n | 'paid'\n | 'closed'\n | 'approval-required'\n | 'tax-undecided'\n | 'nothing-to-pay'\n\n/** Why a quote cannot be paid now, or null when it can. */\nexport function quotePayRefusal(\n order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'quote'>,\n orderId: string,\n): QuotePayRefusal | null {\n if (order.status === 'cancelled' || order.status === 'refunded') return 'closed'\n if (order.status !== 'pending') return 'paid'\n if (quotePaymentBlockedByApproval(order.quote)) return 'approval-required'\n if (order.quote?.tax?.kind === 'none' && order.quote.tax.reason === 'undecided') return 'tax-undecided'\n if (quoteCheckoutCharge(order, orderId).totalCents < QUOTE_MIN_CHARGE_CENTS) return 'nothing-to-pay'\n return null\n}\n\n/** What the customer is told for a {@link QuotePayRefusal}, naming the business. */\nexport function quotePayRefusalMessage(reason: QuotePayRefusal, storeName: string): string {\n const business = storeName.trim() || 'the business'\n switch (reason) {\n case 'paid':\n return 'This quote has been paid.'\n case 'closed':\n return `This quote is closed. Contact ${business} with any questions.`\n case 'approval-required':\n return `${business} asks you to approve the proof before you pay.`\n case 'tax-undecided':\n return `${business} has not finished setting up payments. Contact them to pay this quote.`\n case 'nothing-to-pay':\n return `This quote has nothing to pay by card. Contact ${business} with any questions.`\n }\n}\n\n/*==========================================\n * WHEN A PAYMENT COMPLETES\n *=========================================*/\n\n/** What a completed Checkout Session says about the quote it paid. */\nexport interface QuotePaymentFacts {\n sessionId: string\n paymentIntentId: string | null\n /** The link version and quote edit the session was minted for, from its metadata. */\n linkVersion?: string | number | null\n quoteUpdatedAtMs?: string | number | null\n paidAtMs: number\n /** The payer, as Stripe collected them. */\n email: string | null\n name: string | null\n /** What Stripe charged in all, and its shipping and tax parts. */\n amountTotalCents: number | null\n shippingCents: number\n taxCents: number\n /** The platform fee the session was minted with. */\n feeCents: number\n taxMode: StorefrontTaxMode\n /** The address Stripe collected, when the session asked for one. */\n shippingAddress: OrderAddress | null\n}\n\n/**\n * What a completed payment does to a quote:\n *\n * - `pay`: the quote is unpaid and the payment is for it as it stands, so it\n * becomes the order.\n * - `superseded`: the quote is unpaid, but the payment was made through a pay\n * link the business replaced, or for a version of the quote it has since\n * changed. Taking it would record a sale at a price or for a link the\n * business withdrew.\n * - `already-paid`: another payment paid the quote first.\n * - `redelivery`: Stripe sending this same payment again; nothing to do.\n *\n * The two refusals keep the money visible: they are recorded on the quote and\n * the business is told, so a payment is never fulfilled twice and never lost.\n */\nexport type QuotePaymentDisposition = 'pay' | 'superseded' | 'already-paid' | 'redelivery'\n\nexport function quotePaymentDisposition(\n order: Pick<HostOrder, 'status' | 'quote' | 'checkoutSessionId' | 'paymentIntentId'> &\n Partial<Pick<HostOrder, 'payLink'>>,\n payment: Pick<QuotePaymentFacts, 'sessionId' | 'paymentIntentId' | 'linkVersion' | 'quoteUpdatedAtMs'>,\n): QuotePaymentDisposition {\n // Any order's pay link answers the same way (AGL-4099): an invoiced order\n // reads its version and its recorded payments off `payLink`, and its\n // session carries no quote edit to compare.\n const recorded = orderPayLinkUnmatched(order).some((entry) => entry?.sessionId === payment.sessionId)\n if (recorded) return 'redelivery'\n if (order.status !== 'pending') {\n const same =\n (order.checkoutSessionId && order.checkoutSessionId === payment.sessionId) ||\n (payment.paymentIntentId && order.paymentIntentId === payment.paymentIntentId)\n return same ? 'redelivery' : 'already-paid'\n }\n const sentVersion = payment.linkVersion\n if (sentVersion != null && sentVersion !== '' && Number(sentVersion) !== orderPayLinkVersion(order)) {\n return 'superseded'\n }\n const sentEdit = payment.quoteUpdatedAtMs\n if (sentEdit != null && sentEdit !== '' && Number(sentEdit) !== Number(order.quote?.updatedAtMs)) {\n return 'superseded'\n }\n return 'pay'\n}\n\n/** A payment the quote could not take, as {@link QuoteUnmatchedPayment} records it. */\nexport function quoteUnmatchedPayment(\n facts: Pick<QuotePaymentFacts, 'sessionId' | 'paymentIntentId' | 'amountTotalCents' | 'paidAtMs'>,\n reason: QuoteUnmatchedPayment['reason'],\n): QuoteUnmatchedPayment {\n return {\n sessionId: facts.sessionId,\n paymentIntentId: facts.paymentIntentId,\n amountCents: Math.max(0, cents(facts.amountTotalCents)),\n atMs: facts.paidAtMs,\n reason,\n }\n}\n\n/** The timeline line for a payment the quote (or an order's invoice, AGL-4099) could not take. */\nexport function quoteUnmatchedPaymentDetail(\n entry: QuoteUnmatchedPayment,\n noun: 'quote' | 'invoice' = 'quote',\n): string {\n const amount = `$${(entry.amountCents / 100).toFixed(2)}`\n return entry.reason === 'superseded'\n ? `A payment of ${amount} came through a pay link that was replaced, or for an earlier ` +\n `version of this ${noun}. The money is in Stripe and this ${noun} records no sale for it; ` +\n 'refund it in Stripe, or reconcile it by hand.'\n : `A second payment of ${amount} came in after this ${noun} was paid. The money is in Stripe ` +\n 'and no second sale was recorded; refund it in Stripe, or reconcile it by hand.'\n}\n\n/**\n * The order a paid quote becomes, the whole document as it will stand. A\n * quote carries none of the fields order lists read (`commerce-quotes.ts`);\n * paying it writes them:\n *\n * - `createdAtMs` is the time it was paid, the moment it became an order, so\n * it lists and counts on the day the money came in.\n * - The customer moves to the top-level fields every order reader asks\n * (`customerEmail`, `customerName`, `customerPhone`, `customerRecord`), the\n * payer's own email first, as a paid draft takes it.\n * - The address Stripe collected ships it, else the quote's ship-to; the\n * quote's bill-to bills it.\n * - The totals are what Stripe charged: its shipping and tax where it charged\n * any, the platform fee the session was minted with, and its total.\n * - The list fields (`withOrderListFields`) are stamped from all of that.\n */\nexport function paidQuoteOrder(\n order: HostOrder,\n orderId: string,\n facts: QuotePaymentFacts,\n): HostOrder & OrderListFields & { updatedAtMs: number; requiresShipping: boolean } {\n const quote = order.quote as OrderQuote\n const customer = quote?.customer ?? {}\n const stored = order.totals ?? { itemsCents: 0, discountCents: 0, shippingCents: 0, taxCents: 0, feeCents: 0, totalCents: 0 }\n const shippingCents = facts.shippingCents > 0 ? facts.shippingCents : cents(stored.shippingCents)\n const taxCents = facts.taxCents > 0 ? facts.taxCents : cents(stored.taxCents)\n const totalCents =\n facts.amountTotalCents != null && Number.isFinite(facts.amountTotalCents)\n ? facts.amountTotalCents\n : cents(stored.itemsCents) - cents(stored.discountCents) + shippingCents + taxCents\n const shippingAddress = facts.shippingAddress ?? quote?.shipTo ?? null\n const label = formatOrderNumber(order, orderId)\n const paid: HostOrder & { updatedAtMs: number } = {\n ...order,\n status: 'paid',\n createdAtMs: facts.paidAtMs,\n customerEmail: text(facts.email) ?? text(customer.email),\n customerName: text(customer.name) ?? text(facts.name) ?? text(customer.company),\n ...(text(customer.phone) ? { customerPhone: text(customer.phone) } : {}),\n ...(customer.record?.kind && customer.record?.id ? { customerRecord: { ...customer.record } } : {}),\n ...(shippingAddress ? { shippingAddress: { ...shippingAddress } } : {}),\n ...(quote?.billTo ? { billingAddress: { ...quote.billTo } } : {}),\n totals: {\n itemsCents: cents(stored.itemsCents),\n discountCents: cents(stored.discountCents),\n shippingCents,\n taxCents,\n feeCents: Math.max(0, cents(facts.feeCents)),\n totalCents,\n },\n taxMode: facts.taxMode,\n paymentIntentId: facts.paymentIntentId ?? undefined,\n checkoutSessionId: facts.sessionId,\n timeline: appendOrderEvent(order, 'paid', `${label} paid through its pay link`, facts.paidAtMs),\n quote: { ...quote, paidAtMs: facts.paidAtMs },\n // New to a shipping tool's feed from the moment it is paid.\n updatedAtMs: facts.paidAtMs,\n }\n if (!paid.paymentIntentId) delete paid.paymentIntentId\n return withOrderListFields(orderId, paid)\n}\n"],"names":["visitorUploadIsInline","appendOrderEvent","formatOrderNumber","computeQuoteTotals","describeQuoteTax","formatQuoteNumber","quoteLineAmounts","quotePaymentBlockedByApproval","currentQuoteProofs","latestQuoteResponse","quoteRespondRefusal","engineLineTaxPercentages","withOrderListFields","QUOTE_MIN_CHARGE_CENTS","quoteLinkVersion","quote","version","Math","floor","Number","linkVersion","isFinite","orderPayLinkVersion","order","payLink","orderPayLinkUnmatched","list","unmatchedPayments","Array","isArray","filter","entry","text","value","trimmed","trim","cents","number","round","quotePageAddress","address","view","name","line1","line2","city","state","postalCode","country","Object","values","some","Boolean","quotePageStatus","status","label","discountLabel","discount","kind","pct","paidAt","stamped","paidAtMs","event","timeline","find","atMs","buildQuotePageView","input","lines","lineItems","unpaid","charge","quoteCheckoutCharge","orderId","stored","totals","itemsCents","discountCents","shippingCents","taxCents","totalCents","approvalRequired","taxAtPayment","tax","storeName","dateMs","updatedAtMs","createdAtMs","currency","customerName","customer","company","billTo","shipTo","quotePageLines","taxLabel","shippingAtPayment","notes","terms","statusLabel","receiptUrl","payable","quotePayRefusal","revision","max","proofs","map","proof","id","String","contentType","size","inline","response","pageResponse","respondable","channel","line","included","quantity","unitAmountCents","variantLabel","description","sku","amountCents","latest","action","comment","files","length","LINE_DESCRIPTION_MAX","lineSummary","parts","summary","join","slice","feeProductTypeOf","charged","types","Set","productType","has","payLinkCheckoutCharge","reason","shipping","amounts","groups","taxable","members","index","net","netCents","taxExempt","reduce","sum","group","taxableCents","taxPct","push","providerLabel","goodsCents","feeProductType","quotePayRefusalMessage","business","quotePaymentDisposition","payment","recorded","sessionId","same","checkoutSessionId","paymentIntentId","sentVersion","sentEdit","quoteUpdatedAtMs","quoteUnmatchedPayment","facts","amountTotalCents","quoteUnmatchedPaymentDetail","noun","amount","toFixed","paidQuoteOrder","feeCents","shippingAddress","paid","customerEmail","email","phone","customerPhone","record","customerRecord","billingAddress","taxMode","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { QuoteCatalogLineRequest, QuoteCustomer, QuoteEmailThread, QuoteResponseFile } from './commerce-quotes';
|
|
18
|
+
/** The resource the forms plugin reads a submission's facts under, on the core's seam. */
|
|
19
|
+
export declare const QUOTE_REQUEST_FACTS_RESOURCE = "forms.submission";
|
|
20
|
+
/** The request a quote was started from. */
|
|
21
|
+
export interface QuoteRequestRef {
|
|
22
|
+
submissionId: string;
|
|
23
|
+
formName: string;
|
|
24
|
+
/** When the request arrived, epoch ms; null when the submission did not say. */
|
|
25
|
+
receivedAtMs: number | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What the Product and options field posts beside its readable line, under
|
|
29
|
+
* `{name}__data`: the product, the variant the options make, and the options
|
|
30
|
+
* as picked. Add-ons are not taken from the storefront yet; when they are,
|
|
31
|
+
* they ride here as `modifiers` and the quote line prices them as the editor
|
|
32
|
+
* does.
|
|
33
|
+
*/
|
|
34
|
+
export interface ProductFieldValue {
|
|
35
|
+
productId: string;
|
|
36
|
+
variantId?: string;
|
|
37
|
+
options?: Record<string, string>;
|
|
38
|
+
modifiers?: Array<{
|
|
39
|
+
groupId: string;
|
|
40
|
+
optionId: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/** The compact value the field posts. */
|
|
44
|
+
export declare function encodeProductFieldValue(value: ProductFieldValue): string;
|
|
45
|
+
/**
|
|
46
|
+
* The field's posted value, read back, or null for anything that is not one:
|
|
47
|
+
* malformed JSON, no product id, ids that cannot be document ids. The options
|
|
48
|
+
* keep at most twenty names and values of up to 200 characters.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseProductFieldValue(raw: unknown): ProductFieldValue | null;
|
|
51
|
+
/** The readable line the field posts under its name: the product, then its options. */
|
|
52
|
+
export declare function productFieldLabel(productName: string, options: Record<string, string> | undefined): string;
|
|
53
|
+
/** What this plugin reads of a request's facts (the forms plugin's reader documents them). */
|
|
54
|
+
export interface QuoteRequestFacts {
|
|
55
|
+
hostId: string;
|
|
56
|
+
formName: string;
|
|
57
|
+
createdAtMs: number | null;
|
|
58
|
+
requestKind: string | null;
|
|
59
|
+
person: {
|
|
60
|
+
name: string;
|
|
61
|
+
email: string;
|
|
62
|
+
phone: string;
|
|
63
|
+
company: string;
|
|
64
|
+
};
|
|
65
|
+
/** The lead or contact the request was filed as, which the quote's customer links to. */
|
|
66
|
+
capturedRecord: {
|
|
67
|
+
kind: string;
|
|
68
|
+
id: string;
|
|
69
|
+
} | null;
|
|
70
|
+
message: string;
|
|
71
|
+
fieldData: Record<string, string>;
|
|
72
|
+
files: Array<QuoteResponseFile>;
|
|
73
|
+
emailThread: QuoteEmailThread | null;
|
|
74
|
+
}
|
|
75
|
+
/** A request's facts as the seam answered them, read defensively. */
|
|
76
|
+
export declare function readQuoteRequestFacts(raw: Readonly<Record<string, unknown>> | null | undefined): QuoteRequestFacts | null;
|
|
77
|
+
/** What the quote editor opens with when a quote is started from a request. */
|
|
78
|
+
export interface QuoteStartPrefill {
|
|
79
|
+
customer: Pick<QuoteCustomer, 'record' | 'name' | 'email' | 'phone' | 'company'>;
|
|
80
|
+
lines: QuoteCatalogLineRequest[];
|
|
81
|
+
notes: string;
|
|
82
|
+
}
|
|
83
|
+
/** The product lines a request's Product and options fields ask for, one per field. */
|
|
84
|
+
export declare function quoteLinesFromFieldData(fieldData: Readonly<Record<string, string>>): QuoteCatalogLineRequest[];
|
|
85
|
+
/** A quote's start from a request: the editor's prefill, and what the server stamps on the quote. */
|
|
86
|
+
export declare function quoteStartFromRequest(submissionId: string, facts: QuoteRequestFacts): {
|
|
87
|
+
prefill: QuoteStartPrefill;
|
|
88
|
+
request: QuoteRequestRef;
|
|
89
|
+
files: QuoteResponseFile[];
|
|
90
|
+
emailThread: QuoteEmailThread | null;
|
|
91
|
+
};
|
|
92
|
+
/** A request's files as the console may see them: no storage path. */
|
|
93
|
+
export declare function quoteRequestFileViews(files: readonly QuoteResponseFile[] | null | undefined): Array<Omit<QuoteResponseFile, 'path'>>;
|