@aglyn/plugins-commerce 1.0.0-beta.287 → 1.0.0-beta.289

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.
Files changed (109) hide show
  1. package/README.md +2 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +29 -2
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
  6. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
  7. package/src/lib/components/console/host-orders-card.component.js +5 -1
  8. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  9. package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
  10. package/src/lib/components/console/host-quotes-card.component.js +247 -0
  11. package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
  12. package/src/lib/components/console/order-webhooks-card.component.js +1 -1
  13. package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
  14. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
  15. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  16. package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
  17. package/src/lib/components/console/quote-editor-dialog.component.js +1266 -0
  18. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
  19. package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
  20. package/src/lib/components/console/quote-pay-link.component.js +213 -0
  21. package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
  22. package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
  23. package/src/lib/components/console/quote-settings-card.component.js +121 -0
  24. package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
  25. package/src/lib/components/console/tax-settings-card.component.js +4 -1
  26. package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
  27. package/src/lib/components/quote-page-body.d.ts +33 -0
  28. package/src/lib/components/quote-page-body.js +696 -0
  29. package/src/lib/components/quote-page-body.js.map +1 -0
  30. package/src/lib/components/quote-page.d.ts +29 -0
  31. package/src/lib/components/quote-page.js +96 -0
  32. package/src/lib/components/quote-page.js.map +1 -0
  33. package/src/lib/components/storefront-payment-element.d.ts +7 -1
  34. package/src/lib/components/storefront-payment-element.js +8 -3
  35. package/src/lib/components/storefront-payment-element.js.map +1 -1
  36. package/src/lib/constants/orders-list-query.js +6 -1
  37. package/src/lib/constants/orders-list-query.js.map +1 -1
  38. package/src/lib/constants/quote-page.d.ts +30 -0
  39. package/src/lib/constants/quote-page.js +35 -0
  40. package/src/lib/constants/quote-page.js.map +1 -0
  41. package/src/lib/declarations.js +5 -1
  42. package/src/lib/declarations.js.map +1 -1
  43. package/src/lib/model/commerce-orders.d.ts +25 -2
  44. package/src/lib/model/commerce-orders.js +10 -1
  45. package/src/lib/model/commerce-orders.js.map +1 -1
  46. package/src/lib/model/commerce-quote-pay.d.ts +229 -0
  47. package/src/lib/model/commerce-quote-pay.js +393 -0
  48. package/src/lib/model/commerce-quote-pay.js.map +1 -0
  49. package/src/lib/model/commerce-quotes.d.ts +279 -0
  50. package/src/lib/model/commerce-quotes.js +522 -0
  51. package/src/lib/model/commerce-quotes.js.map +1 -0
  52. package/src/lib/model/index.d.ts +2 -0
  53. package/src/lib/model/index.js +2 -0
  54. package/src/lib/model/index.js.map +1 -1
  55. package/src/lib/model/plugin-permissions.d.ts +8 -1
  56. package/src/lib/model/plugin-permissions.js +21 -1
  57. package/src/lib/model/plugin-permissions.js.map +1 -1
  58. package/src/lib/model/workspace-site-permissions.d.ts +26 -0
  59. package/src/lib/model/workspace-site-permissions.js +81 -0
  60. package/src/lib/model/workspace-site-permissions.js.map +1 -0
  61. package/src/lib/server/api-v1/orders-and-products.js +6 -2
  62. package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
  63. package/src/lib/server/billing-webhook.js +127 -52
  64. package/src/lib/server/billing-webhook.js.map +1 -1
  65. package/src/lib/server/checkout-session-expiry.d.ts +36 -0
  66. package/src/lib/server/checkout-session-expiry.js +63 -0
  67. package/src/lib/server/checkout-session-expiry.js.map +1 -0
  68. package/src/lib/server/commerce-site-keys.d.ts +19 -0
  69. package/src/lib/server/commerce-site-keys.js +23 -2
  70. package/src/lib/server/commerce-site-keys.js.map +1 -1
  71. package/src/lib/server/draft-order.js +16 -8
  72. package/src/lib/server/draft-order.js.map +1 -1
  73. package/src/lib/server/order-webhooks.js +8 -8
  74. package/src/lib/server/order-webhooks.js.map +1 -1
  75. package/src/lib/server/person-eraser.d.ts +7 -0
  76. package/src/lib/server/person-eraser.js +24 -2
  77. package/src/lib/server/person-eraser.js.map +1 -1
  78. package/src/lib/server/pos-ops-gate.d.ts +13 -5
  79. package/src/lib/server/pos-ops-gate.js +12 -14
  80. package/src/lib/server/pos-ops-gate.js.map +1 -1
  81. package/src/lib/server/pos-refund-authority.d.ts +3 -1
  82. package/src/lib/server/pos-refund-authority.js +3 -2
  83. package/src/lib/server/pos-refund-authority.js.map +1 -1
  84. package/src/lib/server/pos-return.js +6 -7
  85. package/src/lib/server/pos-return.js.map +1 -1
  86. package/src/lib/server/pos-staff-pin.js +7 -7
  87. package/src/lib/server/pos-staff-pin.js.map +1 -1
  88. package/src/lib/server/quote-link-token.d.ts +39 -0
  89. package/src/lib/server/quote-link-token.js +97 -0
  90. package/src/lib/server/quote-link-token.js.map +1 -0
  91. package/src/lib/server/quote-pay.d.ts +83 -0
  92. package/src/lib/server/quote-pay.js +606 -0
  93. package/src/lib/server/quote-pay.js.map +1 -0
  94. package/src/lib/server/quote.d.ts +49 -0
  95. package/src/lib/server/quote.js +513 -0
  96. package/src/lib/server/quote.js.map +1 -0
  97. package/src/lib/server/refund.js +26 -36
  98. package/src/lib/server/refund.js.map +1 -1
  99. package/src/lib/server/related.js +2 -0
  100. package/src/lib/server/related.js.map +1 -1
  101. package/src/lib/server/site-page-resolver.d.ts +2 -0
  102. package/src/lib/server/site-page-resolver.js +75 -0
  103. package/src/lib/server/site-page-resolver.js.map +1 -1
  104. package/src/lib/server.js +14 -0
  105. package/src/lib/server.js.map +1 -1
  106. package/src/lib/site.js +6 -0
  107. package/src/lib/site.js.map +1 -1
  108. package/src/lib/transfer/tracking.server.js +6 -0
  109. package/src/lib/transfer/tracking.server.js.map +1 -1
@@ -0,0 +1,229 @@
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 { ProductType } from './commerce';
18
+ import { type HostOrder, type OrderAddress } from './commerce-orders';
19
+ import { type OrderQuote, type QuoteUnmatchedPayment } from './commerce-quotes';
20
+ import type { StorefrontTaxMode } from './commerce-tax-decision';
21
+ import { type OrderListFields } from './order-list-fields';
22
+ /** Stripe's smallest card charge; a quote that totals less cannot be paid by card. */
23
+ export declare const QUOTE_MIN_CHARGE_CENTS = 50;
24
+ /** The pay link version a quote is on; absent reads as the first. */
25
+ export declare function quoteLinkVersion(quote: Pick<OrderQuote, 'linkVersion'> | null | undefined): number;
26
+ /** An address as the quote page prints it: no phone. */
27
+ export interface QuotePageAddress {
28
+ name: string | null;
29
+ line1: string | null;
30
+ line2: string | null;
31
+ city: string | null;
32
+ state: string | null;
33
+ postalCode: string | null;
34
+ country: string | null;
35
+ }
36
+ export interface QuotePageLine {
37
+ name: string;
38
+ /** The option and add-ons chosen, as order lines print them. */
39
+ variantLabel: string | null;
40
+ description: string | null;
41
+ sku: string | null;
42
+ quantity: number;
43
+ unitAmountCents: number;
44
+ /** What the line charges: 0 for an included line. */
45
+ amountCents: number;
46
+ /** Included at no charge. */
47
+ included: boolean;
48
+ }
49
+ /** Where the quote stands, as its customer sees it. */
50
+ export type QuotePageStatus = 'unpaid' | 'paid' | 'canceled' | 'refunded';
51
+ /**
52
+ * What the quote page shows. Allow-listed, never spread: the order document
53
+ * carries the customer's email and phone, Stripe ids, the business's own
54
+ * timeline and who wrote the quote, none of which a forwarded link may show.
55
+ * New fields are added here deliberately, or not at all.
56
+ */
57
+ export interface QuotePageView {
58
+ state: 'quote';
59
+ storeName: string;
60
+ /** `Quote #1042`. */
61
+ number: string;
62
+ /** When the quote was last written, which is the date it carries. */
63
+ dateMs: number | null;
64
+ /**
65
+ * The edit of the quote this view shows. Pay sends it back, and a quote
66
+ * changed since is refused, so nobody pays for a version they have not seen.
67
+ */
68
+ version: number;
69
+ currency: string;
70
+ customerName: string | null;
71
+ company: string | null;
72
+ billTo: QuotePageAddress | null;
73
+ shipTo: QuotePageAddress | null;
74
+ lines: QuotePageLine[];
75
+ totals: {
76
+ itemsCents: number;
77
+ discountCents: number;
78
+ shippingCents: number;
79
+ taxCents: number;
80
+ totalCents: number;
81
+ };
82
+ /** `10% off`, or null for an amount or no discount. */
83
+ discountLabel: string | null;
84
+ /** The tax line's label, or `Tax is added at payment`. */
85
+ taxLabel: string;
86
+ /** Stripe Tax works the tax out when the customer pays; `taxCents` is 0 until then. */
87
+ taxAtPayment: boolean;
88
+ notes: string | null;
89
+ terms: string | null;
90
+ status: QuotePageStatus;
91
+ statusLabel: string;
92
+ paidAtMs: number | null;
93
+ /** The order's status page, once paid. */
94
+ receiptUrl: string | null;
95
+ /** The business asks the customer to approve the proof before paying. */
96
+ approvalRequired: boolean;
97
+ /** A Pay button belongs on the page. */
98
+ payable: boolean;
99
+ }
100
+ /** What a signed link for an older version of the quote opens: nothing of the quote. */
101
+ export interface QuotePageReplacedView {
102
+ state: 'replaced';
103
+ storeName: string;
104
+ }
105
+ /**
106
+ * The quote page's view of an order document on the `quote` channel. Totals
107
+ * are the stored lines' through {@link computeQuoteTotals} until the quote is
108
+ * paid, and the paid order's own totals (what Stripe charged) after.
109
+ */
110
+ export declare function buildQuotePageView(input: {
111
+ order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'totals' | 'quote' | 'timeline'>;
112
+ orderId: string;
113
+ storeName: string;
114
+ currency: string;
115
+ /** The order's status page, for a paid quote. */
116
+ receiptUrl?: string | null;
117
+ }): QuotePageView;
118
+ /** One line of a quote's Checkout Session. */
119
+ export interface QuoteCheckoutLine {
120
+ name: string;
121
+ /** The quote's lines this one charges for, as the hosted page lists them. */
122
+ description: string;
123
+ amountCents: number;
124
+ /** False for tax-exempt products: no rate is applied and Stripe Tax is told it is nontaxable. */
125
+ taxable: boolean;
126
+ }
127
+ /** What a quote's Checkout Session charges, priced from the stored quote alone. */
128
+ export interface QuoteCheckoutCharge {
129
+ /** The goods grouped by whether they are taxed, so one rate taxes exactly the quote's taxable amount. */
130
+ lines: QuoteCheckoutLine[];
131
+ itemsCents: number;
132
+ discountCents: number;
133
+ /** The goods after the discount: what the business is paid for them. */
134
+ goodsCents: number;
135
+ taxableCents: number;
136
+ shippingCents: number;
137
+ /** The quote's tax; 0 when Stripe Tax works it out at payment. */
138
+ taxCents: number;
139
+ /**
140
+ * The percentage applied to the taxed line as a Stripe tax rate, or null
141
+ * when no rate is applied (no tax, Stripe Tax, or tax charged as its own line).
142
+ */
143
+ taxPct: number | null;
144
+ taxAtPayment: boolean;
145
+ /** Goods, shipping and the quote's tax. Stripe Tax adds to it at payment. */
146
+ totalCents: number;
147
+ /** What the platform fee prices the goods as. */
148
+ feeProductType: ProductType;
149
+ }
150
+ /**
151
+ * The charge for a quote, from its stored lines, discount, shipping and tax
152
+ * basis through {@link computeQuoteTotals} — the same function that priced it
153
+ * when it was saved, so the page, the session and the stored quote agree.
154
+ *
155
+ * The goods are charged as at most two lines, the taxed and the tax-exempt,
156
+ * each after its share of the discount. A store rate is applied to the taxed
157
+ * line as one Stripe tax rate, so Stripe's tax is the rate on exactly the
158
+ * amount the quote taxed, and the quote's figure is what is collected. A tax
159
+ * service's figure is carried the same way, as the percentage of the taxed
160
+ * line it amounts to. Included lines charge nothing and are not sent.
161
+ */
162
+ export declare function quoteCheckoutCharge(order: Pick<HostOrder, 'number' | 'lineItems' | 'quote'>, orderId: string): QuoteCheckoutCharge;
163
+ export type QuotePayRefusal = 'paid' | 'closed' | 'approval-required' | 'tax-undecided' | 'nothing-to-pay';
164
+ /** Why a quote cannot be paid now, or null when it can. */
165
+ export declare function quotePayRefusal(order: Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'quote'>, orderId: string): QuotePayRefusal | null;
166
+ /** What the customer is told for a {@link QuotePayRefusal}, naming the business. */
167
+ export declare function quotePayRefusalMessage(reason: QuotePayRefusal, storeName: string): string;
168
+ /** What a completed Checkout Session says about the quote it paid. */
169
+ export interface QuotePaymentFacts {
170
+ sessionId: string;
171
+ paymentIntentId: string | null;
172
+ /** The link version and quote edit the session was minted for, from its metadata. */
173
+ linkVersion?: string | number | null;
174
+ quoteUpdatedAtMs?: string | number | null;
175
+ paidAtMs: number;
176
+ /** The payer, as Stripe collected them. */
177
+ email: string | null;
178
+ name: string | null;
179
+ /** What Stripe charged in all, and its shipping and tax parts. */
180
+ amountTotalCents: number | null;
181
+ shippingCents: number;
182
+ taxCents: number;
183
+ /** The platform fee the session was minted with. */
184
+ feeCents: number;
185
+ taxMode: StorefrontTaxMode;
186
+ /** The address Stripe collected, when the session asked for one. */
187
+ shippingAddress: OrderAddress | null;
188
+ }
189
+ /**
190
+ * What a completed payment does to a quote:
191
+ *
192
+ * - `pay`: the quote is unpaid and the payment is for it as it stands, so it
193
+ * becomes the order.
194
+ * - `superseded`: the quote is unpaid, but the payment was made through a pay
195
+ * link the business replaced, or for a version of the quote it has since
196
+ * changed. Taking it would record a sale at a price or for a link the
197
+ * business withdrew.
198
+ * - `already-paid`: another payment paid the quote first.
199
+ * - `redelivery`: Stripe sending this same payment again; nothing to do.
200
+ *
201
+ * The two refusals keep the money visible: they are recorded on the quote and
202
+ * the business is told, so a payment is never fulfilled twice and never lost.
203
+ */
204
+ export type QuotePaymentDisposition = 'pay' | 'superseded' | 'already-paid' | 'redelivery';
205
+ export declare function quotePaymentDisposition(order: Pick<HostOrder, 'status' | 'quote' | 'checkoutSessionId' | 'paymentIntentId'>, payment: Pick<QuotePaymentFacts, 'sessionId' | 'paymentIntentId' | 'linkVersion' | 'quoteUpdatedAtMs'>): QuotePaymentDisposition;
206
+ /** A payment the quote could not take, as {@link QuoteUnmatchedPayment} records it. */
207
+ export declare function quoteUnmatchedPayment(facts: Pick<QuotePaymentFacts, 'sessionId' | 'paymentIntentId' | 'amountTotalCents' | 'paidAtMs'>, reason: QuoteUnmatchedPayment['reason']): QuoteUnmatchedPayment;
208
+ /** The timeline line for a payment the quote could not take. */
209
+ export declare function quoteUnmatchedPaymentDetail(entry: QuoteUnmatchedPayment): string;
210
+ /**
211
+ * The order a paid quote becomes, the whole document as it will stand. A
212
+ * quote carries none of the fields order lists read (`commerce-quotes.ts`);
213
+ * paying it writes them:
214
+ *
215
+ * - `createdAtMs` is the time it was paid, the moment it became an order, so
216
+ * it lists and counts on the day the money came in.
217
+ * - The customer moves to the top-level fields every order reader asks
218
+ * (`customerEmail`, `customerName`, `customerPhone`, `customerRecord`), the
219
+ * payer's own email first, as a paid draft takes it.
220
+ * - The address Stripe collected ships it, else the quote's ship-to; the
221
+ * quote's bill-to bills it.
222
+ * - The totals are what Stripe charged: its shipping and tax where it charged
223
+ * any, the platform fee the session was minted with, and its total.
224
+ * - The list fields (`withOrderListFields`) are stamped from all of that.
225
+ */
226
+ export declare function paidQuoteOrder(order: HostOrder, orderId: string, facts: QuotePaymentFacts): HostOrder & OrderListFields & {
227
+ updatedAtMs: number;
228
+ requiresShipping: boolean;
229
+ };
@@ -0,0 +1,393 @@
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
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { appendOrderEvent, formatOrderNumber } from "./commerce-orders.js";
18
+ import { computeQuoteTotals, describeQuoteTax, formatQuoteNumber, quoteLineAmounts, quotePaymentBlockedByApproval } from "./commerce-quotes.js";
19
+ import { engineLineTaxPercentages } from "./commerce-tax-engine.js";
20
+ import { withOrderListFields } from "./order-list-fields.js";
21
+ /*==========================================
22
+ * PAYING A QUOTE (AGL-4094).
23
+ *
24
+ * The customer opens the quote on the business's own site from a signed pay
25
+ * link, sees it, and pays it. Everything here is pure, so the page's view,
26
+ * the pay route's charge and the webhook's paid order all come from one
27
+ * reading of the stored quote, and none of them from anything the browser
28
+ * sends:
29
+ *
30
+ * - {@link buildQuotePageView}: the ONE projection of a quote that leaves the
31
+ * server for a visitor holding the link, allow-listed field by field.
32
+ * - {@link quoteCheckoutCharge}: what a Checkout Session charges, priced from
33
+ * the stored lines, discount, shipping and tax basis.
34
+ * - {@link quotePayRefusal}: why a quote cannot be paid now, if it cannot.
35
+ * - {@link quotePaymentDisposition} and {@link paidQuoteOrder}: what a
36
+ * completed payment does to the quote, and the order it becomes.
37
+ *
38
+ * The customer side has no expiry and no open or closed states: a quote and
39
+ * its link stay until the quote is paid or the business replaces the link.
40
+ *=========================================*/ /** Stripe's smallest card charge; a quote that totals less cannot be paid by card. */ export const QUOTE_MIN_CHARGE_CENTS = 50;
41
+ /** The pay link version a quote is on; absent reads as the first. */ export function quoteLinkVersion(quote) {
42
+ const version = Math.floor(Number(quote == null ? void 0 : quote.linkVersion));
43
+ return Number.isFinite(version) && version >= 1 ? version : 1;
44
+ }
45
+ const text = (value)=>{
46
+ const trimmed = typeof value === 'string' ? value.trim() : '';
47
+ return trimmed ? trimmed : null;
48
+ };
49
+ const cents = (value)=>{
50
+ const number = Math.round(Number(value));
51
+ return Number.isFinite(number) ? number : 0;
52
+ };
53
+ function pageAddress(address) {
54
+ if (!address) return null;
55
+ const view = {
56
+ name: text(address.name),
57
+ line1: text(address.line1),
58
+ line2: text(address.line2),
59
+ city: text(address.city),
60
+ state: text(address.state),
61
+ postalCode: text(address.postalCode),
62
+ country: text(address.country)
63
+ };
64
+ return Object.values(view).some(Boolean) ? view : null;
65
+ }
66
+ function pageStatus(status) {
67
+ if (status === 'pending') return {
68
+ status: 'unpaid',
69
+ label: 'Unpaid'
70
+ };
71
+ if (status === 'cancelled') return {
72
+ status: 'canceled',
73
+ label: 'Canceled'
74
+ };
75
+ if (status === 'refunded') return {
76
+ status: 'refunded',
77
+ label: 'Refunded'
78
+ };
79
+ return {
80
+ status: 'paid',
81
+ label: 'Paid'
82
+ };
83
+ }
84
+ function discountLabel(discount) {
85
+ if ((discount == null ? void 0 : discount.kind) !== 'percent' || !(Number(discount.pct) > 0)) return null;
86
+ return `${Number(discount.pct)}% off`;
87
+ }
88
+ /** When the quote was paid: its stamp, else the timeline's `paid` event. */ function paidAt(order) {
89
+ var _order_timeline;
90
+ var _order_quote;
91
+ if (order.status === 'pending') return null;
92
+ const stamped = Number((_order_quote = order.quote) == null ? void 0 : _order_quote.paidAtMs);
93
+ if (Number.isFinite(stamped) && stamped > 0) return stamped;
94
+ const event = ((_order_timeline = order.timeline) != null ? _order_timeline : []).find((entry)=>(entry == null ? void 0 : entry.event) === 'paid');
95
+ return event ? Number(event.atMs) || null : null;
96
+ }
97
+ /**
98
+ * The quote page's view of an order document on the `quote` channel. Totals
99
+ * are the stored lines' through {@link computeQuoteTotals} until the quote is
100
+ * paid, and the paid order's own totals (what Stripe charged) after.
101
+ */ export function buildQuotePageView(input) {
102
+ var _order_lineItems, _ref, _text, _input_receiptUrl;
103
+ var _quote_tax, _quote_customer, _quote_customer1, _quote_customer2, _quote_customer3, _quote_tax1;
104
+ const { order } = input;
105
+ const quote = order.quote;
106
+ const lines = (_order_lineItems = order.lineItems) != null ? _order_lineItems : [];
107
+ const status = pageStatus(order.status);
108
+ const unpaid = status.status === 'unpaid';
109
+ const charge = quoteCheckoutCharge(order, input.orderId);
110
+ const stored = order.totals;
111
+ const totals = unpaid || !stored ? {
112
+ itemsCents: charge.itemsCents,
113
+ discountCents: charge.discountCents,
114
+ shippingCents: charge.shippingCents,
115
+ taxCents: charge.taxCents,
116
+ totalCents: charge.totalCents
117
+ } : {
118
+ itemsCents: cents(stored.itemsCents),
119
+ discountCents: cents(stored.discountCents),
120
+ shippingCents: cents(stored.shippingCents),
121
+ taxCents: cents(stored.taxCents),
122
+ totalCents: cents(stored.totalCents)
123
+ };
124
+ const approvalRequired = unpaid && quotePaymentBlockedByApproval(quote);
125
+ const taxAtPayment = unpaid && (quote == null ? void 0 : (_quote_tax = quote.tax) == null ? void 0 : _quote_tax.kind) === 'at-payment';
126
+ return {
127
+ state: 'quote',
128
+ storeName: input.storeName,
129
+ number: formatQuoteNumber(order, input.orderId),
130
+ dateMs: Number((_ref = quote == null ? void 0 : quote.updatedAtMs) != null ? _ref : quote == null ? void 0 : quote.createdAtMs) || null,
131
+ version: Number(quote == null ? void 0 : quote.updatedAtMs) || 0,
132
+ currency: input.currency || 'USD',
133
+ 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),
134
+ 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,
135
+ billTo: pageAddress(quote == null ? void 0 : quote.billTo),
136
+ shipTo: pageAddress(quote == null ? void 0 : quote.shipTo),
137
+ lines: lines.map((line)=>{
138
+ var _line_name;
139
+ const included = line.included === true;
140
+ const quantity = Math.max(0, cents(line.quantity));
141
+ const unitAmountCents = included ? 0 : Math.max(0, cents(line.unitAmountCents));
142
+ return {
143
+ name: String((_line_name = line.name) != null ? _line_name : ''),
144
+ variantLabel: text(line.variantLabel),
145
+ description: text(line.description),
146
+ sku: text(line.sku),
147
+ quantity,
148
+ unitAmountCents,
149
+ amountCents: unitAmountCents * quantity,
150
+ included
151
+ };
152
+ }),
153
+ totals,
154
+ discountLabel: discountLabel(quote == null ? void 0 : quote.discount),
155
+ // Once paid, the tax is what was charged: Stripe's figure under Stripe Tax.
156
+ 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',
157
+ taxAtPayment,
158
+ notes: text(quote == null ? void 0 : quote.notes),
159
+ terms: text(quote == null ? void 0 : quote.terms),
160
+ status: status.status,
161
+ statusLabel: status.label,
162
+ paidAtMs: paidAt(order),
163
+ receiptUrl: unpaid ? null : (_input_receiptUrl = input.receiptUrl) != null ? _input_receiptUrl : null,
164
+ approvalRequired,
165
+ payable: unpaid && !approvalRequired && quotePayRefusal(order, input.orderId) === null
166
+ };
167
+ }
168
+ const LINE_DESCRIPTION_MAX = 500;
169
+ function lineSummary(lines) {
170
+ const parts = lines.map((line)=>{
171
+ const label = line.variantLabel ? `${line.name} (${line.variantLabel})` : line.name;
172
+ return `${Math.max(1, cents(line.quantity))} × ${label}`;
173
+ });
174
+ const summary = parts.join(', ');
175
+ return summary.length > LINE_DESCRIPTION_MAX ? `${summary.slice(0, LINE_DESCRIPTION_MAX - 1)}…` : summary;
176
+ }
177
+ /** What the fee ladder prices the quote's charged goods as: physical when any line ships. */ function feeProductTypeOf(lines) {
178
+ const charged = lines.filter((line)=>!line.included);
179
+ const types = new Set(charged.map((line)=>{
180
+ var _line_productType;
181
+ return (_line_productType = line.productType) != null ? _line_productType : 'physical';
182
+ }));
183
+ if (types.has('physical') || types.size === 0) return 'physical';
184
+ return types.has('service') ? 'service' : 'digital';
185
+ }
186
+ /**
187
+ * The charge for a quote, from its stored lines, discount, shipping and tax
188
+ * basis through {@link computeQuoteTotals} — the same function that priced it
189
+ * when it was saved, so the page, the session and the stored quote agree.
190
+ *
191
+ * The goods are charged as at most two lines, the taxed and the tax-exempt,
192
+ * each after its share of the discount. A store rate is applied to the taxed
193
+ * line as one Stripe tax rate, so Stripe's tax is the rate on exactly the
194
+ * amount the quote taxed, and the quote's figure is what is collected. A tax
195
+ * service's figure is carried the same way, as the percentage of the taxed
196
+ * line it amounts to. Included lines charge nothing and are not sent.
197
+ */ export function quoteCheckoutCharge(order, orderId) {
198
+ var _order_lineItems, _ref, _ref1;
199
+ var _order_quote, _order_quote1, _order_quote_shipping, _order_quote2;
200
+ const lineItems = (_order_lineItems = order.lineItems) != null ? _order_lineItems : [];
201
+ const tax = (_ref = (_order_quote = order.quote) == null ? void 0 : _order_quote.tax) != null ? _ref : {
202
+ kind: 'none',
203
+ reason: 'not-collected'
204
+ };
205
+ const discount = (_ref1 = (_order_quote1 = order.quote) == null ? void 0 : _order_quote1.discount) != null ? _ref1 : null;
206
+ const 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));
207
+ const totals = computeQuoteTotals({
208
+ lines: lineItems,
209
+ discount,
210
+ shippingCents,
211
+ tax
212
+ });
213
+ const amounts = quoteLineAmounts(lineItems, discount);
214
+ const label = formatQuoteNumber(order, orderId);
215
+ const groups = [
216
+ true,
217
+ false
218
+ ].map((taxable)=>{
219
+ const members = lineItems.map((line, index)=>({
220
+ line,
221
+ net: amounts.netCents[index]
222
+ })).filter(({ line, net })=>net > 0 && line.taxExempt !== true === taxable);
223
+ return {
224
+ taxable,
225
+ amountCents: members.reduce((sum, { net })=>sum + net, 0),
226
+ description: lineSummary(members.map(({ line })=>line))
227
+ };
228
+ });
229
+ const charged = groups.filter((group)=>group.amountCents > 0);
230
+ const lines = charged.map((group)=>({
231
+ name: charged.length > 1 && !group.taxable ? `${label} (tax-exempt items)` : label,
232
+ description: group.description,
233
+ amountCents: group.amountCents,
234
+ taxable: group.taxable
235
+ }));
236
+ const taxableCents = groups[0].amountCents;
237
+ const taxPct = totals.taxCents > 0 && taxableCents > 0 ? tax.kind === 'rate' ? Number(tax.pct) : engineLineTaxPercentages([
238
+ {
239
+ amountCents: taxableCents,
240
+ taxCents: totals.taxCents
241
+ }
242
+ ], 0)[0] : null;
243
+ // A tax service can tax a quote whose goods are all exempt (on its
244
+ // shipping, say): with no taxed line to carry a rate, its figure is
245
+ // charged as a line of its own, so the customer still pays what the quote says.
246
+ if (totals.taxCents > 0 && taxPct === null) {
247
+ lines.push({
248
+ name: tax.providerLabel ? `Sales tax (${tax.providerLabel})` : 'Sales tax',
249
+ description: label,
250
+ amountCents: totals.taxCents,
251
+ taxable: false
252
+ });
253
+ }
254
+ return {
255
+ lines,
256
+ itemsCents: totals.itemsCents,
257
+ discountCents: totals.discountCents,
258
+ goodsCents: totals.itemsCents - totals.discountCents,
259
+ taxableCents,
260
+ shippingCents,
261
+ taxCents: totals.taxCents,
262
+ taxPct,
263
+ taxAtPayment: totals.taxAtPayment,
264
+ totalCents: totals.totalCents,
265
+ feeProductType: feeProductTypeOf(lineItems)
266
+ };
267
+ }
268
+ /** Why a quote cannot be paid now, or null when it can. */ export function quotePayRefusal(order, orderId) {
269
+ var _order_quote_tax, _order_quote;
270
+ if (order.status === 'cancelled' || order.status === 'refunded') return 'closed';
271
+ if (order.status !== 'pending') return 'paid';
272
+ if (quotePaymentBlockedByApproval(order.quote)) return 'approval-required';
273
+ if (((_order_quote = order.quote) == null ? void 0 : (_order_quote_tax = _order_quote.tax) == null ? void 0 : _order_quote_tax.kind) === 'none' && order.quote.tax.reason === 'undecided') return 'tax-undecided';
274
+ if (quoteCheckoutCharge(order, orderId).totalCents < QUOTE_MIN_CHARGE_CENTS) return 'nothing-to-pay';
275
+ return null;
276
+ }
277
+ /** What the customer is told for a {@link QuotePayRefusal}, naming the business. */ export function quotePayRefusalMessage(reason, storeName) {
278
+ const business = storeName.trim() || 'the business';
279
+ switch(reason){
280
+ case 'paid':
281
+ return 'This quote has been paid.';
282
+ case 'closed':
283
+ return `This quote is closed. Contact ${business} with any questions.`;
284
+ case 'approval-required':
285
+ return `${business} asks you to approve the proof before you pay.`;
286
+ case 'tax-undecided':
287
+ return `${business} has not finished setting up payments. Contact them to pay this quote.`;
288
+ case 'nothing-to-pay':
289
+ return `This quote has nothing to pay by card. Contact ${business} with any questions.`;
290
+ }
291
+ }
292
+ export function quotePaymentDisposition(order, payment) {
293
+ var _ref;
294
+ var _order_quote, _order_quote1;
295
+ const recorded = ((_ref = (_order_quote = order.quote) == null ? void 0 : _order_quote.unmatchedPayments) != null ? _ref : []).some((entry)=>(entry == null ? void 0 : entry.sessionId) === payment.sessionId);
296
+ if (recorded) return 'redelivery';
297
+ if (order.status !== 'pending') {
298
+ const same = order.checkoutSessionId && order.checkoutSessionId === payment.sessionId || payment.paymentIntentId && order.paymentIntentId === payment.paymentIntentId;
299
+ return same ? 'redelivery' : 'already-paid';
300
+ }
301
+ const sentVersion = payment.linkVersion;
302
+ if (sentVersion != null && sentVersion !== '' && Number(sentVersion) !== quoteLinkVersion(order.quote)) {
303
+ return 'superseded';
304
+ }
305
+ const sentEdit = payment.quoteUpdatedAtMs;
306
+ if (sentEdit != null && sentEdit !== '' && Number(sentEdit) !== Number((_order_quote1 = order.quote) == null ? void 0 : _order_quote1.updatedAtMs)) {
307
+ return 'superseded';
308
+ }
309
+ return 'pay';
310
+ }
311
+ /** A payment the quote could not take, as {@link QuoteUnmatchedPayment} records it. */ export function quoteUnmatchedPayment(facts, reason) {
312
+ return {
313
+ sessionId: facts.sessionId,
314
+ paymentIntentId: facts.paymentIntentId,
315
+ amountCents: Math.max(0, cents(facts.amountTotalCents)),
316
+ atMs: facts.paidAtMs,
317
+ reason
318
+ };
319
+ }
320
+ /** The timeline line for a payment the quote could not take. */ export function quoteUnmatchedPaymentDetail(entry) {
321
+ const amount = `$${(entry.amountCents / 100).toFixed(2)}`;
322
+ return entry.reason === 'superseded' ? `A payment of ${amount} came through a pay link that was replaced, or for an earlier ` + 'version of this quote. The money is in Stripe and this quote records no sale for it; ' + 'refund it in Stripe, or reconcile it by hand.' : `A second payment of ${amount} came in after this quote was paid. The money is in Stripe ` + 'and no second sale was recorded; refund it in Stripe, or reconcile it by hand.';
323
+ }
324
+ /**
325
+ * The order a paid quote becomes, the whole document as it will stand. A
326
+ * quote carries none of the fields order lists read (`commerce-quotes.ts`);
327
+ * paying it writes them:
328
+ *
329
+ * - `createdAtMs` is the time it was paid, the moment it became an order, so
330
+ * it lists and counts on the day the money came in.
331
+ * - The customer moves to the top-level fields every order reader asks
332
+ * (`customerEmail`, `customerName`, `customerPhone`, `customerRecord`), the
333
+ * payer's own email first, as a paid draft takes it.
334
+ * - The address Stripe collected ships it, else the quote's ship-to; the
335
+ * quote's bill-to bills it.
336
+ * - The totals are what Stripe charged: its shipping and tax where it charged
337
+ * any, the platform fee the session was minted with, and its total.
338
+ * - The list fields (`withOrderListFields`) are stamped from all of that.
339
+ */ export function paidQuoteOrder(order, orderId, facts) {
340
+ var _ref, _order_totals, _ref1, _facts_shippingAddress, _text, _ref2, _text1, _facts_paymentIntentId;
341
+ var _customer_record, _customer_record1;
342
+ const quote = order.quote;
343
+ const customer = (_ref = quote == null ? void 0 : quote.customer) != null ? _ref : {};
344
+ const stored = (_order_totals = order.totals) != null ? _order_totals : {
345
+ itemsCents: 0,
346
+ discountCents: 0,
347
+ shippingCents: 0,
348
+ taxCents: 0,
349
+ feeCents: 0,
350
+ totalCents: 0
351
+ };
352
+ const shippingCents = facts.shippingCents > 0 ? facts.shippingCents : cents(stored.shippingCents);
353
+ const taxCents = facts.taxCents > 0 ? facts.taxCents : cents(stored.taxCents);
354
+ const totalCents = facts.amountTotalCents != null && Number.isFinite(facts.amountTotalCents) ? facts.amountTotalCents : cents(stored.itemsCents) - cents(stored.discountCents) + shippingCents + taxCents;
355
+ const shippingAddress = (_ref1 = (_facts_shippingAddress = facts.shippingAddress) != null ? _facts_shippingAddress : quote == null ? void 0 : quote.shipTo) != null ? _ref1 : null;
356
+ const label = formatOrderNumber(order, orderId);
357
+ const paid = _extends({}, order, {
358
+ status: 'paid',
359
+ createdAtMs: facts.paidAtMs,
360
+ customerEmail: (_text = text(facts.email)) != null ? _text : text(customer.email),
361
+ customerName: (_ref2 = (_text1 = text(customer.name)) != null ? _text1 : text(facts.name)) != null ? _ref2 : text(customer.company)
362
+ }, text(customer.phone) ? {
363
+ customerPhone: text(customer.phone)
364
+ } : {}, ((_customer_record = customer.record) == null ? void 0 : _customer_record.kind) && ((_customer_record1 = customer.record) == null ? void 0 : _customer_record1.id) ? {
365
+ customerRecord: _extends({}, customer.record)
366
+ } : {}, shippingAddress ? {
367
+ shippingAddress: _extends({}, shippingAddress)
368
+ } : {}, (quote == null ? void 0 : quote.billTo) ? {
369
+ billingAddress: _extends({}, quote.billTo)
370
+ } : {}, {
371
+ totals: {
372
+ itemsCents: cents(stored.itemsCents),
373
+ discountCents: cents(stored.discountCents),
374
+ shippingCents,
375
+ taxCents,
376
+ feeCents: Math.max(0, cents(facts.feeCents)),
377
+ totalCents
378
+ },
379
+ taxMode: facts.taxMode,
380
+ paymentIntentId: (_facts_paymentIntentId = facts.paymentIntentId) != null ? _facts_paymentIntentId : undefined,
381
+ checkoutSessionId: facts.sessionId,
382
+ timeline: appendOrderEvent(order, 'paid', `${label} paid through its pay link`, facts.paidAtMs),
383
+ quote: _extends({}, quote, {
384
+ paidAtMs: facts.paidAtMs
385
+ }),
386
+ // New to a shipping tool's feed from the moment it is paid.
387
+ updatedAtMs: facts.paidAtMs
388
+ });
389
+ if (!paid.paymentIntentId) delete paid.paymentIntentId;
390
+ return withOrderListFields(orderId, paid);
391
+ }
392
+
393
+ //# sourceMappingURL=commerce-quote-pay.js.map