@aglyn/plugins-commerce 1.0.0-beta.290 → 1.0.0-beta.291
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/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/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-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 +307 -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-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-invoices.d.ts +132 -0
- package/src/lib/model/commerce-invoices.js +332 -0
- package/src/lib/model/commerce-invoices.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +29 -2
- package/src/lib/model/commerce-orders.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 +67 -0
- package/src/lib/model/commerce-quote-email.js +154 -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.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/plugin.js +19 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +115 -70
- package/src/lib/server/billing-webhook.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/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/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/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 +62 -0
- package/src/lib/utils/quote-email-content.js +154 -0
- package/src/lib/utils/quote-email-content.js.map +1 -0
|
@@ -30,7 +30,8 @@ import { orderStatusUrl } from "./order-status-token.js";
|
|
|
30
30
|
import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
31
31
|
import { verifyQuoteLinkToken } from "./quote-link-token.js";
|
|
32
32
|
/*==========================================
|
|
33
|
-
* THE CUSTOMER'S QUOTE PAGE AND ITS PAY BUTTON (AGL-4094)
|
|
33
|
+
* THE CUSTOMER'S QUOTE PAGE AND ITS PAY BUTTON (AGL-4094), AND AN
|
|
34
|
+
* INVOICE'S (AGL-4099).
|
|
34
35
|
*
|
|
35
36
|
* GET /api/commerce/quote-view?hostId=&q=<quoteId>&t=<token>
|
|
36
37
|
* → 200 QuotePageView | 200 { state: 'replaced', storeName } | 404
|
|
@@ -53,6 +54,16 @@ import { verifyQuoteLinkToken } from "./quote-link-token.js";
|
|
|
53
54
|
*
|
|
54
55
|
* The payment completes in `billing-webhook.ts`, which turns this same
|
|
55
56
|
* document into the order (`paidQuoteOrder`); nothing here marks it paid.
|
|
57
|
+
*
|
|
58
|
+
* AN INVOICE'S LINK IS THIS ONE (AGL-4099). A draft order the business sent
|
|
59
|
+
* an invoice for opens on the same page from the same kind of link (its
|
|
60
|
+
* version on `payLink.version`, `commerce-invoices.ts`), as "Invoice #N" with
|
|
61
|
+
* no proofs or approval, and Pay opens the same kind of fresh session from
|
|
62
|
+
* its stored lines, discount, tax and the shipping rates it was made with.
|
|
63
|
+
* Its webhook is the draft branch it always completed through, which flips
|
|
64
|
+
* the draft to paid without restamping its list fields. The proof and answer
|
|
65
|
+
* routes stay a quote's alone: `loadLinkedQuote` opens an invoice only for
|
|
66
|
+
* the routes that ask (`{ invoices: true }`).
|
|
56
67
|
*=========================================*/ /** Lookups and presses one address may make per site, per ten minutes. */ export const QUOTE_VIEW_LOOKUPS_PER_WINDOW = 60;
|
|
57
68
|
export const QUOTE_PAY_PRESSES_PER_WINDOW = 20;
|
|
58
69
|
export const QUOTE_WINDOW_MS = 10 * 60000;
|
|
@@ -95,6 +106,13 @@ const NOT_FOUND = {
|
|
|
95
106
|
error: 'We could not find that quote. Check the link you were sent.'
|
|
96
107
|
}
|
|
97
108
|
};
|
|
109
|
+
/** The not-found answer of a route an invoice's link reaches too: it names neither. */ const NOT_FOUND_ANY = {
|
|
110
|
+
status: 404,
|
|
111
|
+
body: {
|
|
112
|
+
error: 'We could not find that quote or invoice. Check the link you were sent.'
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
/** `quote` or `invoice`, as the customer reads it. */ const nounOf = (kind)=>kind === 'invoice' ? 'invoice' : 'quote';
|
|
98
116
|
function bodyOf(req) {
|
|
99
117
|
const raw = req.body;
|
|
100
118
|
if (typeof raw === 'string') {
|
|
@@ -137,11 +155,13 @@ function headerOf(req, name) {
|
|
|
137
155
|
/**
|
|
138
156
|
* The quote a link names, after its token: the quote, the replaced answer for
|
|
139
157
|
* a link to an older version, or the one not-found answer for everything else.
|
|
140
|
-
|
|
158
|
+
* With `invoices`, a draft order's invoice too (AGL-4099).
|
|
159
|
+
*/ export async function loadLinkedQuote(deps, hostId, quoteId, token, options = {}) {
|
|
141
160
|
var _hostSnapshot_data, _resolveHostToken;
|
|
161
|
+
const notFound = options.invoices ? NOT_FOUND_ANY : NOT_FOUND;
|
|
142
162
|
const version = verifyQuoteLinkToken(hostId, quoteId, token);
|
|
143
163
|
if (version === null) return {
|
|
144
|
-
outcome:
|
|
164
|
+
outcome: notFound
|
|
145
165
|
};
|
|
146
166
|
const hostRef = deps.firestore().collection('hosts').doc(hostId);
|
|
147
167
|
const [hostSnapshot, store, orderSnapshot] = await Promise.all([
|
|
@@ -150,17 +170,18 @@ function headerOf(req, name) {
|
|
|
150
170
|
hostRef.collection('orders').doc(quoteId).get()
|
|
151
171
|
]);
|
|
152
172
|
if (!hostSnapshot.exists || !orderSnapshot.exists) return {
|
|
153
|
-
outcome:
|
|
173
|
+
outcome: notFound
|
|
154
174
|
};
|
|
155
175
|
const order = orderSnapshot.data();
|
|
156
|
-
|
|
157
|
-
|
|
176
|
+
const kind = CommerceModel.orderPayLinkKind(order);
|
|
177
|
+
if (kind === null || kind === 'invoice' && !options.invoices) return {
|
|
178
|
+
outcome: notFound
|
|
158
179
|
};
|
|
159
180
|
const host = (_hostSnapshot_data = hostSnapshot.data()) != null ? _hostSnapshot_data : {};
|
|
160
181
|
const storeName = String((_resolveHostToken = resolveHostToken('businessName', host)) != null ? _resolveHostToken : '');
|
|
161
|
-
const current = CommerceModel.
|
|
182
|
+
const current = CommerceModel.orderPayLinkVersion(order);
|
|
162
183
|
if (version > current) return {
|
|
163
|
-
outcome:
|
|
184
|
+
outcome: notFound
|
|
164
185
|
};
|
|
165
186
|
if (version < current) {
|
|
166
187
|
return {
|
|
@@ -168,9 +189,10 @@ function headerOf(req, name) {
|
|
|
168
189
|
status: 410,
|
|
169
190
|
body: {
|
|
170
191
|
state: 'replaced',
|
|
192
|
+
kind,
|
|
171
193
|
storeName,
|
|
172
194
|
replaced: true,
|
|
173
|
-
error: `This
|
|
195
|
+
error: `This ${nounOf(kind)} was updated, and ${storeName || 'the business'} sent you a newer link.`
|
|
174
196
|
}
|
|
175
197
|
}
|
|
176
198
|
};
|
|
@@ -181,7 +203,8 @@ function headerOf(req, name) {
|
|
|
181
203
|
host,
|
|
182
204
|
order,
|
|
183
205
|
storeName,
|
|
184
|
-
store
|
|
206
|
+
store,
|
|
207
|
+
kind
|
|
185
208
|
}
|
|
186
209
|
};
|
|
187
210
|
}
|
|
@@ -215,39 +238,51 @@ function headerOf(req, name) {
|
|
|
215
238
|
};
|
|
216
239
|
const refused = await limitedQuoteLink(deps, req, `commerce-quote-view:${hostId}`, QUOTE_VIEW_LOOKUPS_PER_WINDOW);
|
|
217
240
|
if (refused) return refused;
|
|
218
|
-
const linked = await loadLinkedQuote(deps, hostId, quoteId, token
|
|
241
|
+
const linked = await loadLinkedQuote(deps, hostId, quoteId, token, {
|
|
242
|
+
invoices: true
|
|
243
|
+
});
|
|
219
244
|
if ('outcome' in linked) {
|
|
220
245
|
// The replaced page is a page, not a failure: answered as data.
|
|
221
246
|
if (linked.outcome.status === 410) {
|
|
222
|
-
const { storeName } = linked.outcome.body;
|
|
247
|
+
const { storeName, kind } = linked.outcome.body;
|
|
223
248
|
return {
|
|
224
249
|
status: 200,
|
|
225
250
|
body: {
|
|
226
251
|
state: 'replaced',
|
|
252
|
+
kind,
|
|
227
253
|
storeName
|
|
228
254
|
}
|
|
229
255
|
};
|
|
230
256
|
}
|
|
231
257
|
return linked.outcome;
|
|
232
258
|
}
|
|
233
|
-
const { host, order, storeName, store } = linked.loaded;
|
|
234
|
-
const
|
|
259
|
+
const { host, order, storeName, store, kind } = linked.loaded;
|
|
260
|
+
const currency = String((_store_get = store.get('currency')) != null ? _store_get : 'USD');
|
|
261
|
+
const receiptUrl = order.status === 'pending' ? null : orderStatusUrl(host, hostId, quoteId);
|
|
262
|
+
const view = kind === 'quote' ? CommerceModel.buildQuotePageView({
|
|
235
263
|
order,
|
|
236
264
|
orderId: quoteId,
|
|
237
265
|
storeName,
|
|
238
|
-
currency
|
|
239
|
-
receiptUrl
|
|
266
|
+
currency,
|
|
267
|
+
receiptUrl
|
|
268
|
+
}) : CommerceModel.buildInvoicePageView({
|
|
269
|
+
order,
|
|
270
|
+
orderId: quoteId,
|
|
271
|
+
storeName,
|
|
272
|
+
currency,
|
|
273
|
+
receiptUrl
|
|
240
274
|
});
|
|
241
275
|
return {
|
|
242
276
|
status: 200,
|
|
243
277
|
body: view
|
|
244
278
|
};
|
|
245
279
|
}
|
|
246
|
-
/** The Checkout Session a quote's Pay opens, as form params. Pure over its inputs. */ export function buildQuoteSessionParams(input) {
|
|
247
|
-
var _ref;
|
|
280
|
+
/** The Checkout Session a quote's (or an invoice's) Pay opens, as form params. Pure over its inputs. */ export function buildQuoteSessionParams(input) {
|
|
281
|
+
var _order_quote, _charge_shippingOptions, _ref;
|
|
248
282
|
var _quote_customer;
|
|
249
283
|
const { charge, order } = input;
|
|
250
|
-
const
|
|
284
|
+
const invoice = CommerceModel.orderPayLinkKind(order) === 'invoice';
|
|
285
|
+
const quote = (_order_quote = order.quote) != null ? _order_quote : {};
|
|
251
286
|
const params = new URLSearchParams(_extends({
|
|
252
287
|
mode: 'payment'
|
|
253
288
|
}, checkoutSessionCardAuthenticationParams('payment'), charge.taxAtPayment ? {
|
|
@@ -267,7 +302,14 @@ function headerOf(req, name) {
|
|
|
267
302
|
// Shipping the business priced for the quote's ship-to, charged as the one
|
|
268
303
|
// option, with the address asked for where it ships (and where Stripe Tax
|
|
269
304
|
// reads the tax from). The quote's own address stands when it asks nothing.
|
|
270
|
-
|
|
305
|
+
// An invoiced draft offers the rates it was made with, and the buyer picks
|
|
306
|
+
// one, as on the draft's first link (AGL-4099).
|
|
307
|
+
const rates = (_charge_shippingOptions = charge.shippingOptions) != null ? _charge_shippingOptions : [];
|
|
308
|
+
if (rates.length > 0) {
|
|
309
|
+
var _charge_shippingCountries;
|
|
310
|
+
CommerceModel.appendShippingAddressCollectionParams(params, ((_charge_shippingCountries = charge.shippingCountries) == null ? void 0 : _charge_shippingCountries.length) ? charge.shippingCountries : CommerceModel.CHECKOUT_SHIPPING_COUNTRIES);
|
|
311
|
+
CommerceModel.appendCheckoutShippingParams(params, rates);
|
|
312
|
+
} else if (charge.shippingCents > 0) {
|
|
271
313
|
var _ref1;
|
|
272
314
|
var _quote_shipTo;
|
|
273
315
|
const country = String((_ref1 = (_quote_shipTo = quote.shipTo) == null ? void 0 : _quote_shipTo.country) != null ? _ref1 : '').toUpperCase();
|
|
@@ -282,7 +324,7 @@ function headerOf(req, name) {
|
|
|
282
324
|
}
|
|
283
325
|
]);
|
|
284
326
|
}
|
|
285
|
-
const shippingFloorCents = charge.shippingCents;
|
|
327
|
+
const shippingFloorCents = rates.length > 0 ? CommerceModel.shippingFloorCents(rates) : charge.shippingCents;
|
|
286
328
|
for (const [key, value] of Object.entries(CommerceModel.destinationChargeParams({
|
|
287
329
|
accountId: input.accountId,
|
|
288
330
|
feeCents: input.feeCents,
|
|
@@ -292,7 +334,7 @@ function headerOf(req, name) {
|
|
|
292
334
|
}))){
|
|
293
335
|
params.set(key, value);
|
|
294
336
|
}
|
|
295
|
-
const email = String((_ref = (_quote_customer = quote.customer) == null ? void 0 : _quote_customer.email) != null ? _ref : '').trim();
|
|
337
|
+
const email = String((_ref = invoice ? order.customerEmail : (_quote_customer = quote.customer) == null ? void 0 : _quote_customer.email) != null ? _ref : '').trim();
|
|
296
338
|
if (email) params.set('customer_email', email);
|
|
297
339
|
const separator = input.returnUrl.includes('?') ? '&' : '?';
|
|
298
340
|
params.set('success_url', `${input.returnUrl}${separator}session_id={CHECKOUT_SESSION_ID}`);
|
|
@@ -303,9 +345,18 @@ function headerOf(req, name) {
|
|
|
303
345
|
params.set('metadata[type]', 'commerce-draft');
|
|
304
346
|
params.set('metadata[hostId]', input.hostId);
|
|
305
347
|
params.set('metadata[orderId]', input.quoteId);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
348
|
+
if (invoice) {
|
|
349
|
+
// An invoice's link version, which the draft branch compares as a
|
|
350
|
+
// quote's (AGL-4099), and the promotion the draft was priced with, which
|
|
351
|
+
// it counts once the draft is paid (no hold: the draft's lapsed with its
|
|
352
|
+
// first session).
|
|
353
|
+
params.set('metadata[payLinkVersion]', String(CommerceModel.orderPayLinkVersion(order)));
|
|
354
|
+
if (order.discountId) params.set('metadata[discountId]', String(order.discountId));
|
|
355
|
+
} else {
|
|
356
|
+
params.set('metadata[quoteId]', input.quoteId);
|
|
357
|
+
params.set('metadata[quoteLinkVersion]', String(CommerceModel.quoteLinkVersion(quote)));
|
|
358
|
+
params.set('metadata[quoteUpdatedAtMs]', String(Number(quote.updatedAtMs) || 0));
|
|
359
|
+
}
|
|
309
360
|
params.set('metadata[feeCents]', String(input.feeCents));
|
|
310
361
|
// A tax charged as its own line is invisible to Stripe's tax total; this
|
|
311
362
|
// witness is what records it as the business's tax.
|
|
@@ -362,20 +413,24 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
362
413
|
};
|
|
363
414
|
const refused = await limitedQuoteLink(deps, req, `commerce-quote-pay:${hostId}`, QUOTE_PAY_PRESSES_PER_WINDOW);
|
|
364
415
|
if (refused) return refused;
|
|
365
|
-
const linked = await loadLinkedQuote(deps, hostId, quoteId, token
|
|
416
|
+
const linked = await loadLinkedQuote(deps, hostId, quoteId, token, {
|
|
417
|
+
invoices: true
|
|
418
|
+
});
|
|
366
419
|
if ('outcome' in linked) return linked.outcome;
|
|
367
|
-
const { hostRef, host, order, storeName, store } = linked.loaded;
|
|
420
|
+
const { hostRef, host, order, storeName, store, kind } = linked.loaded;
|
|
421
|
+
const noun = nounOf(kind);
|
|
368
422
|
// Every refusal the customer can be given is above the claim, so none of
|
|
369
423
|
// them spends the press. `v` is the edit of the quote the page showed: a
|
|
370
424
|
// quote changed since is shown again before it is paid, never charged unseen.
|
|
425
|
+
// An invoice is not edited, so it sends none.
|
|
371
426
|
const seen = Number(bodyOf(req)['v']);
|
|
372
|
-
if (Number.isFinite(seen) && seen > 0 && seen !== (Number((_order_quote = order.quote) == null ? void 0 : _order_quote.updatedAtMs) || 0)) {
|
|
427
|
+
if (kind === 'quote' && Number.isFinite(seen) && seen > 0 && seen !== (Number((_order_quote = order.quote) == null ? void 0 : _order_quote.updatedAtMs) || 0)) {
|
|
373
428
|
return refusal(409, 'updated', QUOTE_UPDATED_MESSAGE);
|
|
374
429
|
}
|
|
375
|
-
const blocked = CommerceModel.
|
|
376
|
-
if (blocked) return refusal(409, blocked, CommerceModel.
|
|
430
|
+
const blocked = CommerceModel.invoicePayRefusal(order, quoteId);
|
|
431
|
+
if (blocked) return refusal(409, blocked, CommerceModel.invoicePayRefusalMessage(blocked, storeName, kind));
|
|
377
432
|
const owner = await deps.orgForHost(hostId);
|
|
378
|
-
const unavailable = refusal(409, 'payments-unavailable', `${storeName || 'This business'} can’t take payments online right now. Contact them to pay this
|
|
433
|
+
const unavailable = refusal(409, 'payments-unavailable', `${storeName || 'This business'} can’t take payments online right now. Contact them to pay this ${noun}.`);
|
|
379
434
|
if (!checkEntitlement(owner == null ? void 0 : owner.org, 'commerce')) return unavailable;
|
|
380
435
|
const ownerUid = owner == null ? void 0 : (_owner_org = owner.org) == null ? void 0 : _owner_org.ownerUid;
|
|
381
436
|
const profile = ownerUid ? await deps.firestore().collection('profiles').doc(String(ownerUid)).get() : null;
|
|
@@ -385,17 +440,20 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
385
440
|
chargesEnabled: profile == null ? void 0 : profile.get('stripeChargesEnabled'),
|
|
386
441
|
accountLivemode: profile == null ? void 0 : profile.get('stripeAccountLivemode')
|
|
387
442
|
}, {
|
|
388
|
-
subject:
|
|
443
|
+
subject: `${noun} ${quoteId} on host ${hostId}`
|
|
389
444
|
});
|
|
390
445
|
if (!ready) return unavailable;
|
|
391
446
|
const taxSettings = (_store_get = store.get('tax')) != null ? _store_get : {};
|
|
392
447
|
if (CommerceModel.storefrontTaxDecision({
|
|
393
448
|
settings: taxSettings
|
|
394
449
|
}).kind === 'undecided') {
|
|
395
|
-
return refusal(409, 'tax-undecided', CommerceModel.
|
|
450
|
+
return refusal(409, 'tax-undecided', CommerceModel.invoicePayRefusalMessage('tax-undecided', storeName, kind));
|
|
396
451
|
}
|
|
397
|
-
const charge = CommerceModel.
|
|
398
|
-
|
|
452
|
+
const charge = CommerceModel.invoiceCheckoutCharge(order, quoteId);
|
|
453
|
+
// The fee is sized on the most the buyer can pay, as a draft's first link
|
|
454
|
+
// sized it on its dearest rate.
|
|
455
|
+
const dearestRateCents = charge.shippingOptions.reduce((most, rate)=>Math.max(most, rate.amountCents), 0);
|
|
456
|
+
const feeCents = resolveTransactionFeeCents(owner == null ? void 0 : owner.org, charge.feeProductType, charge.goodsCents, charge.totalCents + dearestRateCents);
|
|
399
457
|
const nativeMode = await deps.nativeMode(String((_ref = owner == null ? void 0 : owner.id) != null ? _ref : ''));
|
|
400
458
|
const site = host;
|
|
401
459
|
const origin = siteReturnOrigin({
|
|
@@ -407,8 +465,11 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
407
465
|
requestHost: headerOf(req, 'host')
|
|
408
466
|
});
|
|
409
467
|
const returnUrl = `${origin}${quotePageHref(quoteId, token)}`;
|
|
468
|
+
// A draft's first session (the one its paymentLinkUrl opens) is closed
|
|
469
|
+
// here too, so the draft has one way to pay from the first press on.
|
|
410
470
|
const openedFor = (_order_checkoutSessionId = order.checkoutSessionId) != null ? _order_checkoutSessionId : '';
|
|
411
471
|
const openedAtEdit = Number((_order_quote1 = order.quote) == null ? void 0 : _order_quote1.updatedAtMs) || 0;
|
|
472
|
+
const openedAtVersion = CommerceModel.orderPayLinkVersion(order);
|
|
412
473
|
const claimed = await claimAttempt(deps.firestore(), {
|
|
413
474
|
kind: 'commerce-quote-pay',
|
|
414
475
|
scopeId: `${hostId}:${quoteId}`,
|
|
@@ -438,7 +499,7 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
438
499
|
const previous = await deps.expireSession(openedFor);
|
|
439
500
|
if (previous === 'complete') {
|
|
440
501
|
await claim.release();
|
|
441
|
-
return refusal(409, 'paid',
|
|
502
|
+
return refusal(409, 'paid', `This ${noun} has just been paid. Your receipt is on its way.`, {
|
|
442
503
|
confirming: true
|
|
443
504
|
});
|
|
444
505
|
}
|
|
@@ -519,29 +580,30 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
519
580
|
var _current_quote;
|
|
520
581
|
const snapshot = await transaction.get(orderRef);
|
|
521
582
|
const current = snapshot.exists ? snapshot.data() : null;
|
|
522
|
-
if (!current ||
|
|
523
|
-
outcome:
|
|
583
|
+
if (!current || CommerceModel.orderPayLinkKind(current) !== kind) return {
|
|
584
|
+
outcome: NOT_FOUND_ANY,
|
|
524
585
|
displaced: ''
|
|
525
586
|
};
|
|
526
|
-
if (
|
|
587
|
+
if (current.status !== 'pending') {
|
|
588
|
+
const closed = current.status === 'cancelled' || current.status === 'refunded';
|
|
527
589
|
return {
|
|
528
|
-
outcome: refusal(409, 'paid', CommerceModel.
|
|
590
|
+
outcome: refusal(409, closed ? 'closed' : 'paid', CommerceModel.invoicePayRefusalMessage(closed ? 'closed' : 'paid', storeName, kind)),
|
|
529
591
|
displaced: ''
|
|
530
592
|
};
|
|
531
593
|
}
|
|
532
|
-
if (CommerceModel.
|
|
594
|
+
if (CommerceModel.orderPayLinkVersion(current) !== openedAtVersion) {
|
|
533
595
|
return {
|
|
534
596
|
outcome: {
|
|
535
597
|
status: 410,
|
|
536
598
|
body: {
|
|
537
599
|
replaced: true,
|
|
538
|
-
error: `This
|
|
600
|
+
error: `This ${noun} was updated, and ${storeName || 'the business'} sent you a newer link.`
|
|
539
601
|
}
|
|
540
602
|
},
|
|
541
603
|
displaced: ''
|
|
542
604
|
};
|
|
543
605
|
}
|
|
544
|
-
if ((Number((_current_quote = current.quote) == null ? void 0 : _current_quote.updatedAtMs) || 0) !== openedAtEdit) {
|
|
606
|
+
if (kind === 'quote' && (Number((_current_quote = current.quote) == null ? void 0 : _current_quote.updatedAtMs) || 0) !== openedAtEdit) {
|
|
545
607
|
return {
|
|
546
608
|
outcome: refusal(409, 'updated', QUOTE_UPDATED_MESSAGE),
|
|
547
609
|
displaced: ''
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/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 {\n checkEntitlement,\n claimAttempt,\n deriveStripeObjectKey,\n resolveTransactionFeeCents,\n type PluginApiHandler,\n type PluginApiRequest,\n} from '@aglyn/aglyn/server'\nimport { resolveHostToken, type HostTokenSource } from '@aglyn/aglyn/app-utils/host-tokens'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\nimport { type SiteReturnHost, siteReturnOrigin } from '@aglyn/aglyn/app-utils/site-return-url'\nimport { consumeRateLimit, firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { merchantAccountIsReady } from '@aglyn/tenant-data-admin/server/payment-provider'\nimport { checkoutSessionCardAuthenticationParams } from '@aglyn/tenant-data-admin/server/stripe-card-authentication'\nimport { quotePageHref } from '../constants/quote-page'\nimport * as CommerceModel from '../model'\nimport { expireCheckoutSession, type CheckoutSessionExpiry } from './checkout-session-expiry'\nimport { resolveManualTaxRateId } from './manual-tax-rate'\nimport {\n applyNativeCheckoutParams,\n nativeCheckoutStripeHeaders,\n readCheckoutSessionPayload,\n resolveNativeCheckoutMode,\n type NativeCheckoutMode,\n} from './native-checkout'\nimport { orderStatusUrl } from './order-status-token'\nimport { ensureCheckoutDomain } from './payment-method-domains'\nimport { verifyQuoteLinkToken } from './quote-link-token'\n\n/*==========================================\n * THE CUSTOMER'S QUOTE PAGE AND ITS PAY BUTTON (AGL-4094).\n *\n * GET /api/commerce/quote-view?hostId=&q=<quoteId>&t=<token>\n * → 200 QuotePageView | 200 { state: 'replaced', storeName } | 404\n * POST /api/commerce/quote-pay { hostId, q, t, v } + Idempotency-Key\n * → 200 { url } (Stripe's page) | 200 { clientSecret, publishableKey, sessionId, wallets? }\n * (the in-page form) | 409 { error, reason } | 410 { error, replaced: true } | 404\n *\n * The signed link (`quote-link-token.ts`) is the whole credential. A wrong\n * token answers exactly as a missing quote does, so neither route tells\n * anyone which quote ids exist; both are rate-limited per address per site\n * BEFORE the token check, so guessing costs what looking does. A link signed\n * for an older version of the quote opens the replaced page and nothing else.\n *\n * Pay opens a FRESH Checkout Session from the stored quote on every press:\n * the prices are the stored lines', never anything the page sends, and the\n * session the previous press opened is expired first, so two sessions for one\n * quote can never both be open. The quote keeps the open one in\n * `checkoutSessionId`. A Stripe session lapses after a day; the quote and its\n * link do not, because the next press opens another.\n *\n * The payment completes in `billing-webhook.ts`, which turns this same\n * document into the order (`paidQuoteOrder`); nothing here marks it paid.\n *=========================================*/\n\n/** Lookups and presses one address may make per site, per ten minutes. */\nexport const QUOTE_VIEW_LOOKUPS_PER_WINDOW = 60\nexport const QUOTE_PAY_PRESSES_PER_WINDOW = 20\nexport const QUOTE_WINDOW_MS = 10 * 60_000\n\nexport interface QuotePayDeps {\n firestore(): FirebaseFirestore.Firestore\n consumeRateLimit(key: string, options: { limit: number; windowMs: number }): Promise<{ allowed: boolean; resetMs: number }>\n orgForHost(hostId: string): Promise<{ id?: string; org?: any } | null>\n nativeMode(orgId: string): Promise<NativeCheckoutMode>\n /** The store's manual tax rate as a Stripe tax rate, or null when one cannot be had. */\n taxRateId(input: Parameters<typeof resolveManualTaxRateId>[0]): Promise<string | null>\n createSession(\n params: URLSearchParams,\n headers: Record<string, string>,\n ): Promise<{ ok: boolean; session: { id?: string; url?: string; client_secret?: string; error?: unknown } }>\n expireSession(sessionId: string): Promise<CheckoutSessionExpiry>\n /** Registers the page's domain for Apple Pay; never throws. */\n ensureDomain(input: Parameters<typeof ensureCheckoutDomain>[0]): Promise<unknown>\n now(): number\n}\n\nexport function defaultQuotePayDeps(): QuotePayDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n consumeRateLimit: (key, options) => consumeRateLimit(key, options),\n orgForHost: async (hostId) => {\n const found = await getOrgForHost(hostId)\n return found ? { id: found.orgId, org: found.org } : null\n },\n nativeMode: (orgId) => resolveNativeCheckoutMode(orgId),\n taxRateId: (input) => resolveManualTaxRateId(input),\n createSession: async (params, headers) => {\n const response = await fetch('https://api.stripe.com/v1/checkout/sessions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n ...headers,\n },\n body: params.toString(),\n })\n return { ok: response.ok, session: (await response.json().catch(() => ({}))) as never }\n },\n expireSession: (sessionId) => expireCheckoutSession(sessionId),\n ensureDomain: (input) => ensureCheckoutDomain(input).catch(() => null),\n now: () => Date.now(),\n }\n}\n\n/** A link route's answer: a status, a JSON body and any extra headers. */\nexport type QuoteLinkOutcome = { status: number; body: Record<string, unknown>; headers?: Record<string, string> }\n\nconst DOC_ID = /^(?!__.*__$)[^/]{1,200}$/\n\nconst NOT_FOUND: QuoteLinkOutcome = {\n status: 404,\n body: { error: 'We could not find that quote. Check the link you were sent.' },\n}\n\nfunction bodyOf(req: PluginApiRequest): Record<string, unknown> {\n const raw = req.body\n if (typeof raw === 'string') {\n try {\n const parsed = JSON.parse(raw)\n return parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n }\n return raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {}\n}\n\nfunction headerOf(req: PluginApiRequest, name: string): string {\n const headers = (req.headers ?? {}) as Record<string, unknown>\n const value = headers[name.toLowerCase()] ?? headers[name]\n return String(Array.isArray(value) ? (value[0] ?? '') : (value ?? '')).trim()\n}\n\n/** The rate limit a link route spends per address per site, BEFORE the token is checked. */\nexport async function limitedQuoteLink(\n deps: QuotePayDeps,\n req: PluginApiRequest,\n key: string,\n limit: number,\n): Promise<QuoteLinkOutcome | null> {\n const ip = readClientIp(req.headers as never, { remoteAddress: req.socket?.remoteAddress }) ?? 'no-address'\n const rate = await deps.consumeRateLimit(`${key}:${ip}`, { limit, windowMs: QUOTE_WINDOW_MS })\n if (rate.allowed) return null\n return {\n status: 429,\n body: { error: 'Too many tries. Try again in a few minutes.' },\n headers: { 'Retry-After': String(Math.max(1, Math.ceil((rate.resetMs - deps.now()) / 1000))) },\n }\n}\n\nexport interface LoadedQuote {\n hostRef: FirebaseFirestore.DocumentReference\n host: Record<string, unknown>\n order: CommerceModel.HostOrder\n storeName: string\n store: FirebaseFirestore.DocumentSnapshot\n}\n\n/**\n * The quote a link names, after its token: the quote, the replaced answer for\n * a link to an older version, or the one not-found answer for everything else.\n */\nexport async function loadLinkedQuote(\n deps: QuotePayDeps,\n hostId: string,\n quoteId: string,\n token: string,\n): Promise<{ loaded: LoadedQuote } | { outcome: QuoteLinkOutcome }> {\n const version = verifyQuoteLinkToken(hostId, quoteId, token)\n if (version === null) return { outcome: NOT_FOUND }\n const hostRef = deps.firestore().collection('hosts').doc(hostId)\n const [hostSnapshot, store, orderSnapshot] = await Promise.all([\n hostRef.get(),\n hostRef.collection('settings').doc('store').get(),\n hostRef.collection('orders').doc(quoteId).get(),\n ])\n if (!hostSnapshot.exists || !orderSnapshot.exists) return { outcome: NOT_FOUND }\n const order = orderSnapshot.data() as CommerceModel.HostOrder\n if (!CommerceModel.isQuote(order) || !order.quote) return { outcome: NOT_FOUND }\n const host = (hostSnapshot.data() ?? {}) as Record<string, unknown>\n const storeName = String(resolveHostToken('businessName', host as HostTokenSource) ?? '')\n const current = CommerceModel.quoteLinkVersion(order.quote)\n if (version > current) return { outcome: NOT_FOUND }\n if (version < current) {\n return {\n outcome: {\n status: 410,\n body: {\n state: 'replaced',\n storeName,\n replaced: true,\n error: `This quote was updated, and ${storeName || 'the business'} sent you a newer link.`,\n },\n },\n }\n }\n return { loaded: { hostRef, host, order, storeName, store } }\n}\n\n/** The link's site, quote and token from a query or a body, and whether all three are there. */\nexport function quoteLinkParams(source: Record<string, unknown>) {\n const hostId = String(source['hostId'] ?? '')\n const quoteId = String(source['q'] ?? '').slice(0, 200)\n const token = String(source['t'] ?? '').slice(0, 200)\n const complete = DOC_ID.test(hostId) && DOC_ID.test(quoteId) && Boolean(token)\n return { hostId, quoteId, token, complete }\n}\n\n/** `GET /api/commerce/quote-view`: the quote page's data. */\nexport async function handleQuoteView(deps: QuotePayDeps, req: PluginApiRequest): Promise<QuoteLinkOutcome> {\n if (req.method !== 'GET') return { status: 405, body: { error: 'Method not allowed' } }\n const { hostId, quoteId, token, complete } = quoteLinkParams((req.query ?? {}) as Record<string, unknown>)\n if (!complete) return { status: 400, body: { error: 'This link is incomplete.' } }\n const refused = await limitedQuoteLink(deps, req, `commerce-quote-view:${hostId}`, QUOTE_VIEW_LOOKUPS_PER_WINDOW)\n if (refused) return refused\n const linked = await loadLinkedQuote(deps, hostId, quoteId, token)\n if ('outcome' in linked) {\n // The replaced page is a page, not a failure: answered as data.\n if (linked.outcome.status === 410) {\n const { storeName } = linked.outcome.body as { storeName: string }\n return { status: 200, body: { state: 'replaced', storeName } }\n }\n return linked.outcome\n }\n const { host, order, storeName, store } = linked.loaded\n const view = CommerceModel.buildQuotePageView({\n order,\n orderId: quoteId,\n storeName,\n currency: String(store.get('currency') ?? 'USD'),\n receiptUrl: CommerceModel.isUnpaidQuote(order) ? null : orderStatusUrl(host, hostId, quoteId),\n })\n return { status: 200, body: view as unknown as Record<string, unknown> }\n}\n\n/** The Checkout Session a quote's Pay opens, as form params. Pure over its inputs. */\nexport function buildQuoteSessionParams(input: {\n hostId: string\n quoteId: string\n order: CommerceModel.HostOrder\n charge: CommerceModel.QuoteCheckoutCharge\n accountId: string\n feeCents: number\n taxRateId: string\n returnUrl: string\n paymentMethods: CommerceModel.StorefrontPaymentMethodControls\n}): URLSearchParams {\n const { charge, order } = input\n const quote = order.quote as CommerceModel.OrderQuote\n const params = new URLSearchParams({\n mode: 'payment',\n ...checkoutSessionCardAuthenticationParams('payment'),\n ...(charge.taxAtPayment ? { 'automatic_tax[enabled]': 'true' } : {}),\n })\n charge.lines.forEach((line, index) => {\n const at = `line_items[${index}]`\n params.set(`${at}[quantity]`, '1')\n params.set(`${at}[price_data][currency]`, 'usd')\n params.set(`${at}[price_data][unit_amount]`, String(line.amountCents))\n params.set(`${at}[price_data][product_data][name]`, line.name.slice(0, 120))\n if (line.description) params.set(`${at}[price_data][product_data][description]`, line.description)\n // Stripe Tax's nontaxable code, for tax-exempt products and a tax charged as its own line.\n if (!line.taxable) params.set(`${at}[price_data][product_data][tax_code]`, 'txcd_00000000')\n if (line.taxable && input.taxRateId) params.set(`${at}[tax_rates][0]`, input.taxRateId)\n })\n // Shipping the business priced for the quote's ship-to, charged as the one\n // option, with the address asked for where it ships (and where Stripe Tax\n // reads the tax from). The quote's own address stands when it asks nothing.\n if (charge.shippingCents > 0) {\n const country = String(quote.shipTo?.country ?? '').toUpperCase()\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n /^[A-Z]{2}$/.test(country) ? [country] : CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,\n )\n CommerceModel.appendCheckoutShippingParams(params, [\n { rateId: 'quote', name: 'Shipping', amountCents: charge.shippingCents },\n ])\n }\n const shippingFloorCents = charge.shippingCents\n for (const [key, value] of Object.entries(\n CommerceModel.destinationChargeParams({\n accountId: input.accountId,\n feeCents: input.feeCents,\n taxOwner: charge.taxAtPayment ? 'platform' : 'merchant',\n merchantGoodsCents: charge.goodsCents,\n shippingFloorCents,\n }),\n )) {\n params.set(key, value)\n }\n const email = String(quote.customer?.email ?? '').trim()\n if (email) params.set('customer_email', email)\n const separator = input.returnUrl.includes('?') ? '&' : '?'\n params.set('success_url', `${input.returnUrl}${separator}session_id={CHECKOUT_SESSION_ID}`)\n params.set('cancel_url', input.returnUrl)\n // What the `commerce-draft` webhook branch reads to turn this quote into\n // its order, and to tell a payment for the quote as it stands from one for\n // a replaced link or an earlier edit.\n params.set('metadata[type]', 'commerce-draft')\n params.set('metadata[hostId]', input.hostId)\n params.set('metadata[orderId]', input.quoteId)\n params.set('metadata[quoteId]', input.quoteId)\n params.set('metadata[quoteLinkVersion]', String(CommerceModel.quoteLinkVersion(quote)))\n params.set('metadata[quoteUpdatedAtMs]', String(Number(quote.updatedAtMs) || 0))\n params.set('metadata[feeCents]', String(input.feeCents))\n // A tax charged as its own line is invisible to Stripe's tax total; this\n // witness is what records it as the business's tax.\n if (charge.taxCents > 0 && charge.taxPct === null) {\n params.set('metadata[taxCents]', String(charge.taxCents))\n }\n if (charge.taxAtPayment) {\n params.set(\n 'metadata[transferCents]',\n String(\n CommerceModel.platformLiableTransferCents({\n feeCents: input.feeCents,\n merchantGoodsCents: charge.goodsCents,\n shippingFloorCents,\n }),\n ),\n )\n params.set('metadata[transferShippingCents]', String(shippingFloorCents))\n }\n CommerceModel.appendStorefrontPaymentMethodParams(params, input.paymentMethods)\n return params\n}\n\nconst QUOTE_UPDATED_MESSAGE = 'This quote was updated since you opened it. Reload the page to see it before you pay.'\n\nconst refusal = (status: number, reason: string, error: string, extra: Record<string, unknown> = {}): QuoteLinkOutcome => ({\n status,\n body: { error, reason, ...extra },\n})\n\n/** `POST /api/commerce/quote-pay`: a fresh Checkout Session for the quote. */\nexport async function handleQuotePay(deps: QuotePayDeps, req: PluginApiRequest): Promise<QuoteLinkOutcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n if (!process.env.STRIPE_SECRET_KEY) return { status: 501, body: { error: 'Payments are not configured.' } }\n const { hostId, quoteId, token, complete } = quoteLinkParams(bodyOf(req))\n if (!complete) return { status: 400, body: { error: 'This link is incomplete.' } }\n const idempotencyKey = headerOf(req, 'idempotency-key').slice(0, 200)\n if (!idempotencyKey) return { status: 400, body: { error: 'Paying needs an Idempotency-Key header.' } }\n const refused = await limitedQuoteLink(deps, req, `commerce-quote-pay:${hostId}`, QUOTE_PAY_PRESSES_PER_WINDOW)\n if (refused) return refused\n const linked = await loadLinkedQuote(deps, hostId, quoteId, token)\n if ('outcome' in linked) return linked.outcome\n const { hostRef, host, order, storeName, store } = linked.loaded\n\n // Every refusal the customer can be given is above the claim, so none of\n // them spends the press. `v` is the edit of the quote the page showed: a\n // quote changed since is shown again before it is paid, never charged unseen.\n const seen = Number(bodyOf(req)['v'])\n if (Number.isFinite(seen) && seen > 0 && seen !== (Number(order.quote?.updatedAtMs) || 0)) {\n return refusal(409, 'updated', QUOTE_UPDATED_MESSAGE)\n }\n const blocked = CommerceModel.quotePayRefusal(order, quoteId)\n if (blocked) return refusal(409, blocked, CommerceModel.quotePayRefusalMessage(blocked, storeName))\n const owner = await deps.orgForHost(hostId)\n const unavailable = refusal(\n 409,\n 'payments-unavailable',\n `${storeName || 'This business'} can’t take payments online right now. Contact them to pay this quote.`,\n )\n if (!checkEntitlement(owner?.org as never, 'commerce')) return unavailable\n const ownerUid = owner?.org?.ownerUid\n const profile = ownerUid ? await deps.firestore().collection('profiles').doc(String(ownerUid)).get() : null\n const accountId = profile?.get('stripeAccountId')\n const ready = merchantAccountIsReady(\n {\n accountId,\n chargesEnabled: profile?.get('stripeChargesEnabled'),\n accountLivemode: profile?.get('stripeAccountLivemode'),\n },\n { subject: `quote ${quoteId} on host ${hostId}` },\n )\n if (!ready) return unavailable\n const taxSettings = (store.get('tax') ?? {}) as CommerceModel.TaxSettings\n if (CommerceModel.storefrontTaxDecision({ settings: taxSettings }).kind === 'undecided') {\n return refusal(409, 'tax-undecided', CommerceModel.quotePayRefusalMessage('tax-undecided', storeName))\n }\n\n const charge = CommerceModel.quoteCheckoutCharge(order, quoteId)\n const feeCents = resolveTransactionFeeCents(owner?.org as never, charge.feeProductType, charge.goodsCents, charge.totalCents)\n const nativeMode = await deps.nativeMode(String(owner?.id ?? ''))\n const site = host as SiteReturnHost\n const origin = siteReturnOrigin({\n candidates: [headerOf(req, 'referer'), `https://${headerOf(req, 'host')}`],\n site,\n requestHost: headerOf(req, 'host'),\n })\n const returnUrl = `${origin}${quotePageHref(quoteId, token)}`\n const openedFor = order.checkoutSessionId ?? ''\n const openedAtEdit = Number(order.quote?.updatedAtMs) || 0\n\n const claimed = await claimAttempt(deps.firestore(), {\n kind: 'commerce-quote-pay',\n scopeId: `${hostId}:${quoteId}`,\n orgId: String(owner?.id ?? ''),\n key: idempotencyKey,\n busyMessage: 'Your payment is already being set up',\n })\n if ('replay' in claimed) {\n return { status: claimed.replay.status, body: claimed.replay.body as Record<string, unknown> }\n }\n const claim = claimed.claim\n const stripeKey = (object: string): Record<string, string> => {\n const derived = deriveStripeObjectKey(claim.stripeKey, object)\n return derived ? { 'Idempotency-Key': derived } : {}\n }\n let minted = ''\n try {\n // The session the last press opened is closed before another opens. One\n // that was paid a moment ago means the quote is paid; one that cannot be\n // closed means a second session would be a second way to pay.\n if (openedFor) {\n const previous = await deps.expireSession(openedFor)\n if (previous === 'complete') {\n await claim.release()\n return refusal(409, 'paid', 'This quote has just been paid. Your receipt is on its way.', { confirming: true })\n }\n if (previous === 'open') {\n await claim.release()\n return { status: 502, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n }\n\n let taxRateId = ''\n if (charge.taxPct !== null && charge.taxableCents > 0) {\n const basis = order.quote?.tax\n taxRateId =\n (await deps.taxRateId({\n hostRef,\n taxPct: charge.taxPct,\n taxLabel:\n basis?.kind === 'rate'\n ? basis.label || `Tax (${charge.taxPct}%)`\n : 'Sales tax',\n headers: stripeKey(`tax-rate-${Math.round(charge.taxPct * 10_000)}`),\n })) ?? ''\n if (!taxRateId) {\n // Never an untaxed session for a taxed quote.\n await claim.release()\n return { status: 502, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n }\n\n const paymentMethods = CommerceModel.resolveStorefrontPaymentMethodControls(\n CommerceModel.normalizeStorefrontPaymentMethodSettings(store.get('paymentMethods')),\n )\n const params = buildQuoteSessionParams({\n hostId,\n quoteId,\n order,\n charge,\n accountId: String(accountId),\n feeCents,\n taxRateId,\n returnUrl,\n paymentMethods,\n })\n if (nativeMode.native) {\n applyNativeCheckoutParams(params, params.get('success_url') ?? returnUrl)\n }\n const domain = nativeMode.native\n ? deps.ensureDomain({ pageUrl: returnUrl, site, hostId })\n : Promise.resolve(null)\n const created = await deps.createSession(params, {\n ...stripeKey('session'),\n ...nativeCheckoutStripeHeaders(nativeMode),\n })\n await domain\n const payload = created.ok\n ? readCheckoutSessionPayload(created.session, nativeMode, paymentMethods.wallets)\n : null\n if (!payload || !created.session.id) {\n console.error('Stripe quote checkout error', created.session?.error)\n await claim.release()\n return { status: 502, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n minted = String(created.session.id)\n\n // The quote keeps the session it now has open. Whatever another press\n // stored meanwhile is closed after, so the last press is the one open\n // session; a quote that was paid, replaced or edited meanwhile closes\n // this one instead.\n const orderRef = hostRef.collection('orders').doc(quoteId)\n const settled = await deps.firestore().runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n const current = snapshot.exists ? (snapshot.data() as CommerceModel.HostOrder) : null\n if (!current || !CommerceModel.isQuote(current)) return { outcome: NOT_FOUND, displaced: '' }\n if (!CommerceModel.isUnpaidQuote(current)) {\n return {\n outcome: refusal(409, 'paid', CommerceModel.quotePayRefusalMessage('paid', storeName)),\n displaced: '',\n }\n }\n if (CommerceModel.quoteLinkVersion(current.quote) !== CommerceModel.quoteLinkVersion(order.quote)) {\n return {\n outcome: {\n status: 410,\n body: { replaced: true, error: `This quote was updated, and ${storeName || 'the business'} sent you a newer link.` },\n },\n displaced: '',\n }\n }\n if ((Number(current.quote?.updatedAtMs) || 0) !== openedAtEdit) {\n return { outcome: refusal(409, 'updated', QUOTE_UPDATED_MESSAGE), displaced: '' }\n }\n transaction.set(orderRef, { checkoutSessionId: minted }, { merge: true })\n const displaced = current.checkoutSessionId && current.checkoutSessionId !== openedFor ? current.checkoutSessionId : ''\n return { outcome: null, displaced }\n })\n if (settled.outcome) {\n await deps.expireSession(minted)\n await claim.release()\n return settled.outcome\n }\n if (settled.displaced && settled.displaced !== minted) await deps.expireSession(settled.displaced)\n await claim.record(200, payload)\n return { status: 200, body: payload as unknown as Record<string, unknown> }\n } catch (error) {\n console.error('quote pay failed', error)\n if (minted) await deps.expireSession(minted).catch(() => 'open')\n await claim.release()\n return { status: 500, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n}\n\n/** Sends a link route's answer, never cached or indexed. */\nexport function sendQuoteLinkOutcome(res: Parameters<PluginApiHandler>[1], outcome: QuoteLinkOutcome) {\n res.setHeader('Cache-Control', 'private, no-store')\n res.setHeader('X-Robots-Tag', 'noindex, nofollow')\n for (const [name, value] of Object.entries(outcome.headers ?? {})) res.setHeader(name, value)\n return res.status(outcome.status).json(outcome.body)\n}\n\n/** `GET /api/commerce/quote-view`. */\nexport const quoteViewHandler: PluginApiHandler = async (req, res) => {\n try {\n return sendQuoteLinkOutcome(res, await handleQuoteView(defaultQuotePayDeps(), req))\n } catch (error) {\n console.error('quote view failed', error)\n return sendQuoteLinkOutcome(res, { status: 500, body: { error: 'This quote could not be loaded. Try again.' } })\n }\n}\n\n/** `POST /api/commerce/quote-pay`. */\nexport const quotePayHandler: PluginApiHandler = async (req, res) =>\n sendQuoteLinkOutcome(res, await handleQuotePay(defaultQuotePayDeps(), req))\n"],"names":["checkEntitlement","claimAttempt","deriveStripeObjectKey","resolveTransactionFeeCents","resolveHostToken","readClientIp","siteReturnOrigin","consumeRateLimit","firebaseAdmin","getOrgForHost","merchantAccountIsReady","checkoutSessionCardAuthenticationParams","quotePageHref","CommerceModel","expireCheckoutSession","resolveManualTaxRateId","applyNativeCheckoutParams","nativeCheckoutStripeHeaders","readCheckoutSessionPayload","resolveNativeCheckoutMode","orderStatusUrl","ensureCheckoutDomain","verifyQuoteLinkToken","QUOTE_VIEW_LOOKUPS_PER_WINDOW","QUOTE_PAY_PRESSES_PER_WINDOW","QUOTE_WINDOW_MS","defaultQuotePayDeps","firestore","app","key","options","orgForHost","hostId","found","id","orgId","org","nativeMode","taxRateId","input","createSession","params","headers","response","fetch","method","Authorization","process","env","STRIPE_SECRET_KEY","body","toString","ok","session","json","catch","expireSession","sessionId","ensureDomain","now","Date","DOC_ID","NOT_FOUND","status","error","bodyOf","req","raw","parsed","JSON","parse","headerOf","name","value","toLowerCase","String","Array","isArray","trim","limitedQuoteLink","deps","limit","ip","remoteAddress","socket","rate","windowMs","allowed","Math","max","ceil","resetMs","loadLinkedQuote","quoteId","token","hostSnapshot","version","outcome","hostRef","collection","doc","store","orderSnapshot","Promise","all","get","exists","order","data","isQuote","quote","host","storeName","current","quoteLinkVersion","state","replaced","loaded","quoteLinkParams","source","slice","complete","test","Boolean","handleQuoteView","query","refused","linked","view","buildQuotePageView","orderId","currency","receiptUrl","isUnpaidQuote","buildQuoteSessionParams","charge","URLSearchParams","mode","taxAtPayment","lines","forEach","line","index","at","set","amountCents","description","taxable","shippingCents","country","shipTo","toUpperCase","appendShippingAddressCollectionParams","CHECKOUT_SHIPPING_COUNTRIES","appendCheckoutShippingParams","rateId","shippingFloorCents","Object","entries","destinationChargeParams","accountId","feeCents","taxOwner","merchantGoodsCents","goodsCents","email","customer","separator","returnUrl","includes","Number","updatedAtMs","taxCents","taxPct","platformLiableTransferCents","appendStorefrontPaymentMethodParams","paymentMethods","QUOTE_UPDATED_MESSAGE","refusal","reason","extra","handleQuotePay","owner","idempotencyKey","seen","isFinite","blocked","quotePayRefusal","quotePayRefusalMessage","unavailable","ownerUid","profile","ready","chargesEnabled","accountLivemode","subject","taxSettings","storefrontTaxDecision","settings","kind","quoteCheckoutCharge","feeProductType","totalCents","site","origin","candidates","requestHost","openedFor","checkoutSessionId","openedAtEdit","claimed","scopeId","busyMessage","replay","claim","stripeKey","object","derived","minted","previous","release","confirming","taxableCents","basis","tax","taxLabel","label","round","resolveStorefrontPaymentMethodControls","normalizeStorefrontPaymentMethodSettings","native","domain","pageUrl","resolve","created","payload","wallets","console","orderRef","settled","runTransaction","transaction","snapshot","displaced","merge","record","sendQuoteLinkOutcome","res","setHeader","quoteViewHandler","quotePayHandler"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/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 {\n checkEntitlement,\n claimAttempt,\n deriveStripeObjectKey,\n resolveTransactionFeeCents,\n type PluginApiHandler,\n type PluginApiRequest,\n} from '@aglyn/aglyn/server'\nimport { resolveHostToken, type HostTokenSource } from '@aglyn/aglyn/app-utils/host-tokens'\nimport { readClientIp } from '@aglyn/aglyn/app-utils/request-ip'\nimport { type SiteReturnHost, siteReturnOrigin } from '@aglyn/aglyn/app-utils/site-return-url'\nimport { consumeRateLimit, firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { merchantAccountIsReady } from '@aglyn/tenant-data-admin/server/payment-provider'\nimport { checkoutSessionCardAuthenticationParams } from '@aglyn/tenant-data-admin/server/stripe-card-authentication'\nimport { quotePageHref } from '../constants/quote-page'\nimport * as CommerceModel from '../model'\nimport { expireCheckoutSession, type CheckoutSessionExpiry } from './checkout-session-expiry'\nimport { resolveManualTaxRateId } from './manual-tax-rate'\nimport {\n applyNativeCheckoutParams,\n nativeCheckoutStripeHeaders,\n readCheckoutSessionPayload,\n resolveNativeCheckoutMode,\n type NativeCheckoutMode,\n} from './native-checkout'\nimport { orderStatusUrl } from './order-status-token'\nimport { ensureCheckoutDomain } from './payment-method-domains'\nimport { verifyQuoteLinkToken } from './quote-link-token'\n\n/*==========================================\n * THE CUSTOMER'S QUOTE PAGE AND ITS PAY BUTTON (AGL-4094), AND AN\n * INVOICE'S (AGL-4099).\n *\n * GET /api/commerce/quote-view?hostId=&q=<quoteId>&t=<token>\n * → 200 QuotePageView | 200 { state: 'replaced', storeName } | 404\n * POST /api/commerce/quote-pay { hostId, q, t, v } + Idempotency-Key\n * → 200 { url } (Stripe's page) | 200 { clientSecret, publishableKey, sessionId, wallets? }\n * (the in-page form) | 409 { error, reason } | 410 { error, replaced: true } | 404\n *\n * The signed link (`quote-link-token.ts`) is the whole credential. A wrong\n * token answers exactly as a missing quote does, so neither route tells\n * anyone which quote ids exist; both are rate-limited per address per site\n * BEFORE the token check, so guessing costs what looking does. A link signed\n * for an older version of the quote opens the replaced page and nothing else.\n *\n * Pay opens a FRESH Checkout Session from the stored quote on every press:\n * the prices are the stored lines', never anything the page sends, and the\n * session the previous press opened is expired first, so two sessions for one\n * quote can never both be open. The quote keeps the open one in\n * `checkoutSessionId`. A Stripe session lapses after a day; the quote and its\n * link do not, because the next press opens another.\n *\n * The payment completes in `billing-webhook.ts`, which turns this same\n * document into the order (`paidQuoteOrder`); nothing here marks it paid.\n *\n * AN INVOICE'S LINK IS THIS ONE (AGL-4099). A draft order the business sent\n * an invoice for opens on the same page from the same kind of link (its\n * version on `payLink.version`, `commerce-invoices.ts`), as \"Invoice #N\" with\n * no proofs or approval, and Pay opens the same kind of fresh session from\n * its stored lines, discount, tax and the shipping rates it was made with.\n * Its webhook is the draft branch it always completed through, which flips\n * the draft to paid without restamping its list fields. The proof and answer\n * routes stay a quote's alone: `loadLinkedQuote` opens an invoice only for\n * the routes that ask (`{ invoices: true }`).\n *=========================================*/\n\n/** Lookups and presses one address may make per site, per ten minutes. */\nexport const QUOTE_VIEW_LOOKUPS_PER_WINDOW = 60\nexport const QUOTE_PAY_PRESSES_PER_WINDOW = 20\nexport const QUOTE_WINDOW_MS = 10 * 60_000\n\nexport interface QuotePayDeps {\n firestore(): FirebaseFirestore.Firestore\n consumeRateLimit(key: string, options: { limit: number; windowMs: number }): Promise<{ allowed: boolean; resetMs: number }>\n orgForHost(hostId: string): Promise<{ id?: string; org?: any } | null>\n nativeMode(orgId: string): Promise<NativeCheckoutMode>\n /** The store's manual tax rate as a Stripe tax rate, or null when one cannot be had. */\n taxRateId(input: Parameters<typeof resolveManualTaxRateId>[0]): Promise<string | null>\n createSession(\n params: URLSearchParams,\n headers: Record<string, string>,\n ): Promise<{ ok: boolean; session: { id?: string; url?: string; client_secret?: string; error?: unknown } }>\n expireSession(sessionId: string): Promise<CheckoutSessionExpiry>\n /** Registers the page's domain for Apple Pay; never throws. */\n ensureDomain(input: Parameters<typeof ensureCheckoutDomain>[0]): Promise<unknown>\n now(): number\n}\n\nexport function defaultQuotePayDeps(): QuotePayDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore(),\n consumeRateLimit: (key, options) => consumeRateLimit(key, options),\n orgForHost: async (hostId) => {\n const found = await getOrgForHost(hostId)\n return found ? { id: found.orgId, org: found.org } : null\n },\n nativeMode: (orgId) => resolveNativeCheckoutMode(orgId),\n taxRateId: (input) => resolveManualTaxRateId(input),\n createSession: async (params, headers) => {\n const response = await fetch('https://api.stripe.com/v1/checkout/sessions', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n ...headers,\n },\n body: params.toString(),\n })\n return { ok: response.ok, session: (await response.json().catch(() => ({}))) as never }\n },\n expireSession: (sessionId) => expireCheckoutSession(sessionId),\n ensureDomain: (input) => ensureCheckoutDomain(input).catch(() => null),\n now: () => Date.now(),\n }\n}\n\n/** A link route's answer: a status, a JSON body and any extra headers. */\nexport type QuoteLinkOutcome = { status: number; body: Record<string, unknown>; headers?: Record<string, string> }\n\nconst DOC_ID = /^(?!__.*__$)[^/]{1,200}$/\n\nconst NOT_FOUND: QuoteLinkOutcome = {\n status: 404,\n body: { error: 'We could not find that quote. Check the link you were sent.' },\n}\n\n/** The not-found answer of a route an invoice's link reaches too: it names neither. */\nconst NOT_FOUND_ANY: QuoteLinkOutcome = {\n status: 404,\n body: { error: 'We could not find that quote or invoice. Check the link you were sent.' },\n}\n\n/** `quote` or `invoice`, as the customer reads it. */\nconst nounOf = (kind: CommerceModel.OrderPayLinkKind) => (kind === 'invoice' ? 'invoice' : 'quote')\n\nfunction bodyOf(req: PluginApiRequest): Record<string, unknown> {\n const raw = req.body\n if (typeof raw === 'string') {\n try {\n const parsed = JSON.parse(raw)\n return parsed && typeof parsed === 'object' ? parsed : {}\n } catch {\n return {}\n }\n }\n return raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : {}\n}\n\nfunction headerOf(req: PluginApiRequest, name: string): string {\n const headers = (req.headers ?? {}) as Record<string, unknown>\n const value = headers[name.toLowerCase()] ?? headers[name]\n return String(Array.isArray(value) ? (value[0] ?? '') : (value ?? '')).trim()\n}\n\n/** The rate limit a link route spends per address per site, BEFORE the token is checked. */\nexport async function limitedQuoteLink(\n deps: QuotePayDeps,\n req: PluginApiRequest,\n key: string,\n limit: number,\n): Promise<QuoteLinkOutcome | null> {\n const ip = readClientIp(req.headers as never, { remoteAddress: req.socket?.remoteAddress }) ?? 'no-address'\n const rate = await deps.consumeRateLimit(`${key}:${ip}`, { limit, windowMs: QUOTE_WINDOW_MS })\n if (rate.allowed) return null\n return {\n status: 429,\n body: { error: 'Too many tries. Try again in a few minutes.' },\n headers: { 'Retry-After': String(Math.max(1, Math.ceil((rate.resetMs - deps.now()) / 1000))) },\n }\n}\n\nexport interface LoadedQuote {\n hostRef: FirebaseFirestore.DocumentReference\n host: Record<string, unknown>\n order: CommerceModel.HostOrder\n storeName: string\n store: FirebaseFirestore.DocumentSnapshot\n /** A quote, or an order's invoice (only for a route that opens invoices). */\n kind: CommerceModel.OrderPayLinkKind\n}\n\n/**\n * The quote a link names, after its token: the quote, the replaced answer for\n * a link to an older version, or the one not-found answer for everything else.\n * With `invoices`, a draft order's invoice too (AGL-4099).\n */\nexport async function loadLinkedQuote(\n deps: QuotePayDeps,\n hostId: string,\n quoteId: string,\n token: string,\n options: { invoices?: boolean } = {},\n): Promise<{ loaded: LoadedQuote } | { outcome: QuoteLinkOutcome }> {\n const notFound = options.invoices ? NOT_FOUND_ANY : NOT_FOUND\n const version = verifyQuoteLinkToken(hostId, quoteId, token)\n if (version === null) return { outcome: notFound }\n const hostRef = deps.firestore().collection('hosts').doc(hostId)\n const [hostSnapshot, store, orderSnapshot] = await Promise.all([\n hostRef.get(),\n hostRef.collection('settings').doc('store').get(),\n hostRef.collection('orders').doc(quoteId).get(),\n ])\n if (!hostSnapshot.exists || !orderSnapshot.exists) return { outcome: notFound }\n const order = orderSnapshot.data() as CommerceModel.HostOrder\n const kind = CommerceModel.orderPayLinkKind(order)\n if (kind === null || (kind === 'invoice' && !options.invoices)) return { outcome: notFound }\n const host = (hostSnapshot.data() ?? {}) as Record<string, unknown>\n const storeName = String(resolveHostToken('businessName', host as HostTokenSource) ?? '')\n const current = CommerceModel.orderPayLinkVersion(order)\n if (version > current) return { outcome: notFound }\n if (version < current) {\n return {\n outcome: {\n status: 410,\n body: {\n state: 'replaced',\n kind,\n storeName,\n replaced: true,\n error: `This ${nounOf(kind)} was updated, and ${storeName || 'the business'} sent you a newer link.`,\n },\n },\n }\n }\n return { loaded: { hostRef, host, order, storeName, store, kind } }\n}\n\n/** The link's site, quote and token from a query or a body, and whether all three are there. */\nexport function quoteLinkParams(source: Record<string, unknown>) {\n const hostId = String(source['hostId'] ?? '')\n const quoteId = String(source['q'] ?? '').slice(0, 200)\n const token = String(source['t'] ?? '').slice(0, 200)\n const complete = DOC_ID.test(hostId) && DOC_ID.test(quoteId) && Boolean(token)\n return { hostId, quoteId, token, complete }\n}\n\n/** `GET /api/commerce/quote-view`: the quote page's data. */\nexport async function handleQuoteView(deps: QuotePayDeps, req: PluginApiRequest): Promise<QuoteLinkOutcome> {\n if (req.method !== 'GET') return { status: 405, body: { error: 'Method not allowed' } }\n const { hostId, quoteId, token, complete } = quoteLinkParams((req.query ?? {}) as Record<string, unknown>)\n if (!complete) return { status: 400, body: { error: 'This link is incomplete.' } }\n const refused = await limitedQuoteLink(deps, req, `commerce-quote-view:${hostId}`, QUOTE_VIEW_LOOKUPS_PER_WINDOW)\n if (refused) return refused\n const linked = await loadLinkedQuote(deps, hostId, quoteId, token, { invoices: true })\n if ('outcome' in linked) {\n // The replaced page is a page, not a failure: answered as data.\n if (linked.outcome.status === 410) {\n const { storeName, kind } = linked.outcome.body as { storeName: string; kind: CommerceModel.OrderPayLinkKind }\n return { status: 200, body: { state: 'replaced', kind, storeName } }\n }\n return linked.outcome\n }\n const { host, order, storeName, store, kind } = linked.loaded\n const currency = String(store.get('currency') ?? 'USD')\n const receiptUrl = order.status === 'pending' ? null : orderStatusUrl(host, hostId, quoteId)\n const view =\n kind === 'quote'\n ? CommerceModel.buildQuotePageView({ order, orderId: quoteId, storeName, currency, receiptUrl })\n : CommerceModel.buildInvoicePageView({ order, orderId: quoteId, storeName, currency, receiptUrl })\n return { status: 200, body: view as unknown as Record<string, unknown> }\n}\n\n/** The Checkout Session a quote's (or an invoice's) Pay opens, as form params. Pure over its inputs. */\nexport function buildQuoteSessionParams(input: {\n hostId: string\n quoteId: string\n order: CommerceModel.HostOrder\n charge: CommerceModel.QuoteCheckoutCharge & Partial<Pick<CommerceModel.InvoiceCheckoutCharge, 'shippingOptions' | 'shippingCountries'>>\n accountId: string\n feeCents: number\n taxRateId: string\n returnUrl: string\n paymentMethods: CommerceModel.StorefrontPaymentMethodControls\n}): URLSearchParams {\n const { charge, order } = input\n const invoice = CommerceModel.orderPayLinkKind(order) === 'invoice'\n const quote = (order.quote ?? {}) as CommerceModel.OrderQuote\n const params = new URLSearchParams({\n mode: 'payment',\n ...checkoutSessionCardAuthenticationParams('payment'),\n ...(charge.taxAtPayment ? { 'automatic_tax[enabled]': 'true' } : {}),\n })\n charge.lines.forEach((line, index) => {\n const at = `line_items[${index}]`\n params.set(`${at}[quantity]`, '1')\n params.set(`${at}[price_data][currency]`, 'usd')\n params.set(`${at}[price_data][unit_amount]`, String(line.amountCents))\n params.set(`${at}[price_data][product_data][name]`, line.name.slice(0, 120))\n if (line.description) params.set(`${at}[price_data][product_data][description]`, line.description)\n // Stripe Tax's nontaxable code, for tax-exempt products and a tax charged as its own line.\n if (!line.taxable) params.set(`${at}[price_data][product_data][tax_code]`, 'txcd_00000000')\n if (line.taxable && input.taxRateId) params.set(`${at}[tax_rates][0]`, input.taxRateId)\n })\n // Shipping the business priced for the quote's ship-to, charged as the one\n // option, with the address asked for where it ships (and where Stripe Tax\n // reads the tax from). The quote's own address stands when it asks nothing.\n // An invoiced draft offers the rates it was made with, and the buyer picks\n // one, as on the draft's first link (AGL-4099).\n const rates = charge.shippingOptions ?? []\n if (rates.length > 0) {\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n charge.shippingCountries?.length ? charge.shippingCountries : CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,\n )\n CommerceModel.appendCheckoutShippingParams(params, rates)\n } else if (charge.shippingCents > 0) {\n const country = String(quote.shipTo?.country ?? '').toUpperCase()\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n /^[A-Z]{2}$/.test(country) ? [country] : CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,\n )\n CommerceModel.appendCheckoutShippingParams(params, [\n { rateId: 'quote', name: 'Shipping', amountCents: charge.shippingCents },\n ])\n }\n const shippingFloorCents = rates.length > 0 ? CommerceModel.shippingFloorCents(rates) : charge.shippingCents\n for (const [key, value] of Object.entries(\n CommerceModel.destinationChargeParams({\n accountId: input.accountId,\n feeCents: input.feeCents,\n taxOwner: charge.taxAtPayment ? 'platform' : 'merchant',\n merchantGoodsCents: charge.goodsCents,\n shippingFloorCents,\n }),\n )) {\n params.set(key, value)\n }\n const email = String((invoice ? order.customerEmail : quote.customer?.email) ?? '').trim()\n if (email) params.set('customer_email', email)\n const separator = input.returnUrl.includes('?') ? '&' : '?'\n params.set('success_url', `${input.returnUrl}${separator}session_id={CHECKOUT_SESSION_ID}`)\n params.set('cancel_url', input.returnUrl)\n // What the `commerce-draft` webhook branch reads to turn this quote into\n // its order, and to tell a payment for the quote as it stands from one for\n // a replaced link or an earlier edit.\n params.set('metadata[type]', 'commerce-draft')\n params.set('metadata[hostId]', input.hostId)\n params.set('metadata[orderId]', input.quoteId)\n if (invoice) {\n // An invoice's link version, which the draft branch compares as a\n // quote's (AGL-4099), and the promotion the draft was priced with, which\n // it counts once the draft is paid (no hold: the draft's lapsed with its\n // first session).\n params.set('metadata[payLinkVersion]', String(CommerceModel.orderPayLinkVersion(order)))\n if (order.discountId) params.set('metadata[discountId]', String(order.discountId))\n } else {\n params.set('metadata[quoteId]', input.quoteId)\n params.set('metadata[quoteLinkVersion]', String(CommerceModel.quoteLinkVersion(quote)))\n params.set('metadata[quoteUpdatedAtMs]', String(Number(quote.updatedAtMs) || 0))\n }\n params.set('metadata[feeCents]', String(input.feeCents))\n // A tax charged as its own line is invisible to Stripe's tax total; this\n // witness is what records it as the business's tax.\n if (charge.taxCents > 0 && charge.taxPct === null) {\n params.set('metadata[taxCents]', String(charge.taxCents))\n }\n if (charge.taxAtPayment) {\n params.set(\n 'metadata[transferCents]',\n String(\n CommerceModel.platformLiableTransferCents({\n feeCents: input.feeCents,\n merchantGoodsCents: charge.goodsCents,\n shippingFloorCents,\n }),\n ),\n )\n params.set('metadata[transferShippingCents]', String(shippingFloorCents))\n }\n CommerceModel.appendStorefrontPaymentMethodParams(params, input.paymentMethods)\n return params\n}\n\nconst QUOTE_UPDATED_MESSAGE = 'This quote was updated since you opened it. Reload the page to see it before you pay.'\n\nconst refusal = (status: number, reason: string, error: string, extra: Record<string, unknown> = {}): QuoteLinkOutcome => ({\n status,\n body: { error, reason, ...extra },\n})\n\n/** `POST /api/commerce/quote-pay`: a fresh Checkout Session for the quote. */\nexport async function handleQuotePay(deps: QuotePayDeps, req: PluginApiRequest): Promise<QuoteLinkOutcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n if (!process.env.STRIPE_SECRET_KEY) return { status: 501, body: { error: 'Payments are not configured.' } }\n const { hostId, quoteId, token, complete } = quoteLinkParams(bodyOf(req))\n if (!complete) return { status: 400, body: { error: 'This link is incomplete.' } }\n const idempotencyKey = headerOf(req, 'idempotency-key').slice(0, 200)\n if (!idempotencyKey) return { status: 400, body: { error: 'Paying needs an Idempotency-Key header.' } }\n const refused = await limitedQuoteLink(deps, req, `commerce-quote-pay:${hostId}`, QUOTE_PAY_PRESSES_PER_WINDOW)\n if (refused) return refused\n const linked = await loadLinkedQuote(deps, hostId, quoteId, token, { invoices: true })\n if ('outcome' in linked) return linked.outcome\n const { hostRef, host, order, storeName, store, kind } = linked.loaded\n const noun = nounOf(kind)\n\n // Every refusal the customer can be given is above the claim, so none of\n // them spends the press. `v` is the edit of the quote the page showed: a\n // quote changed since is shown again before it is paid, never charged unseen.\n // An invoice is not edited, so it sends none.\n const seen = Number(bodyOf(req)['v'])\n if (kind === 'quote' && Number.isFinite(seen) && seen > 0 && seen !== (Number(order.quote?.updatedAtMs) || 0)) {\n return refusal(409, 'updated', QUOTE_UPDATED_MESSAGE)\n }\n const blocked = CommerceModel.invoicePayRefusal(order, quoteId)\n if (blocked) return refusal(409, blocked, CommerceModel.invoicePayRefusalMessage(blocked, storeName, kind))\n const owner = await deps.orgForHost(hostId)\n const unavailable = refusal(\n 409,\n 'payments-unavailable',\n `${storeName || 'This business'} can’t take payments online right now. Contact them to pay this ${noun}.`,\n )\n if (!checkEntitlement(owner?.org as never, 'commerce')) return unavailable\n const ownerUid = owner?.org?.ownerUid\n const profile = ownerUid ? await deps.firestore().collection('profiles').doc(String(ownerUid)).get() : null\n const accountId = profile?.get('stripeAccountId')\n const ready = merchantAccountIsReady(\n {\n accountId,\n chargesEnabled: profile?.get('stripeChargesEnabled'),\n accountLivemode: profile?.get('stripeAccountLivemode'),\n },\n { subject: `${noun} ${quoteId} on host ${hostId}` },\n )\n if (!ready) return unavailable\n const taxSettings = (store.get('tax') ?? {}) as CommerceModel.TaxSettings\n if (CommerceModel.storefrontTaxDecision({ settings: taxSettings }).kind === 'undecided') {\n return refusal(409, 'tax-undecided', CommerceModel.invoicePayRefusalMessage('tax-undecided', storeName, kind))\n }\n\n const charge = CommerceModel.invoiceCheckoutCharge(order, quoteId)\n // The fee is sized on the most the buyer can pay, as a draft's first link\n // sized it on its dearest rate.\n const dearestRateCents = charge.shippingOptions.reduce((most, rate) => Math.max(most, rate.amountCents), 0)\n const feeCents = resolveTransactionFeeCents(\n owner?.org as never,\n charge.feeProductType,\n charge.goodsCents,\n charge.totalCents + dearestRateCents,\n )\n const nativeMode = await deps.nativeMode(String(owner?.id ?? ''))\n const site = host as SiteReturnHost\n const origin = siteReturnOrigin({\n candidates: [headerOf(req, 'referer'), `https://${headerOf(req, 'host')}`],\n site,\n requestHost: headerOf(req, 'host'),\n })\n const returnUrl = `${origin}${quotePageHref(quoteId, token)}`\n // A draft's first session (the one its paymentLinkUrl opens) is closed\n // here too, so the draft has one way to pay from the first press on.\n const openedFor = order.checkoutSessionId ?? ''\n const openedAtEdit = Number(order.quote?.updatedAtMs) || 0\n const openedAtVersion = CommerceModel.orderPayLinkVersion(order)\n\n const claimed = await claimAttempt(deps.firestore(), {\n kind: 'commerce-quote-pay',\n scopeId: `${hostId}:${quoteId}`,\n orgId: String(owner?.id ?? ''),\n key: idempotencyKey,\n busyMessage: 'Your payment is already being set up',\n })\n if ('replay' in claimed) {\n return { status: claimed.replay.status, body: claimed.replay.body as Record<string, unknown> }\n }\n const claim = claimed.claim\n const stripeKey = (object: string): Record<string, string> => {\n const derived = deriveStripeObjectKey(claim.stripeKey, object)\n return derived ? { 'Idempotency-Key': derived } : {}\n }\n let minted = ''\n try {\n // The session the last press opened is closed before another opens. One\n // that was paid a moment ago means the quote is paid; one that cannot be\n // closed means a second session would be a second way to pay.\n if (openedFor) {\n const previous = await deps.expireSession(openedFor)\n if (previous === 'complete') {\n await claim.release()\n return refusal(409, 'paid', `This ${noun} has just been paid. Your receipt is on its way.`, { confirming: true })\n }\n if (previous === 'open') {\n await claim.release()\n return { status: 502, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n }\n\n let taxRateId = ''\n if (charge.taxPct !== null && charge.taxableCents > 0) {\n const basis = order.quote?.tax\n taxRateId =\n (await deps.taxRateId({\n hostRef,\n taxPct: charge.taxPct,\n taxLabel:\n basis?.kind === 'rate'\n ? basis.label || `Tax (${charge.taxPct}%)`\n : 'Sales tax',\n headers: stripeKey(`tax-rate-${Math.round(charge.taxPct * 10_000)}`),\n })) ?? ''\n if (!taxRateId) {\n // Never an untaxed session for a taxed quote.\n await claim.release()\n return { status: 502, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n }\n\n const paymentMethods = CommerceModel.resolveStorefrontPaymentMethodControls(\n CommerceModel.normalizeStorefrontPaymentMethodSettings(store.get('paymentMethods')),\n )\n const params = buildQuoteSessionParams({\n hostId,\n quoteId,\n order,\n charge,\n accountId: String(accountId),\n feeCents,\n taxRateId,\n returnUrl,\n paymentMethods,\n })\n if (nativeMode.native) {\n applyNativeCheckoutParams(params, params.get('success_url') ?? returnUrl)\n }\n const domain = nativeMode.native\n ? deps.ensureDomain({ pageUrl: returnUrl, site, hostId })\n : Promise.resolve(null)\n const created = await deps.createSession(params, {\n ...stripeKey('session'),\n ...nativeCheckoutStripeHeaders(nativeMode),\n })\n await domain\n const payload = created.ok\n ? readCheckoutSessionPayload(created.session, nativeMode, paymentMethods.wallets)\n : null\n if (!payload || !created.session.id) {\n console.error('Stripe quote checkout error', created.session?.error)\n await claim.release()\n return { status: 502, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n minted = String(created.session.id)\n\n // The quote keeps the session it now has open. Whatever another press\n // stored meanwhile is closed after, so the last press is the one open\n // session; a quote that was paid, replaced or edited meanwhile closes\n // this one instead.\n const orderRef = hostRef.collection('orders').doc(quoteId)\n const settled = await deps.firestore().runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n const current = snapshot.exists ? (snapshot.data() as CommerceModel.HostOrder) : null\n if (!current || CommerceModel.orderPayLinkKind(current) !== kind) return { outcome: NOT_FOUND_ANY, displaced: '' }\n if (current.status !== 'pending') {\n const closed = current.status === 'cancelled' || current.status === 'refunded'\n return {\n outcome: refusal(\n 409,\n closed ? 'closed' : 'paid',\n CommerceModel.invoicePayRefusalMessage(closed ? 'closed' : 'paid', storeName, kind),\n ),\n displaced: '',\n }\n }\n if (CommerceModel.orderPayLinkVersion(current) !== openedAtVersion) {\n return {\n outcome: {\n status: 410,\n body: { replaced: true, error: `This ${noun} was updated, and ${storeName || 'the business'} sent you a newer link.` },\n },\n displaced: '',\n }\n }\n if (kind === 'quote' && (Number(current.quote?.updatedAtMs) || 0) !== openedAtEdit) {\n return { outcome: refusal(409, 'updated', QUOTE_UPDATED_MESSAGE), displaced: '' }\n }\n transaction.set(orderRef, { checkoutSessionId: minted }, { merge: true })\n const displaced = current.checkoutSessionId && current.checkoutSessionId !== openedFor ? current.checkoutSessionId : ''\n return { outcome: null, displaced }\n })\n if (settled.outcome) {\n await deps.expireSession(minted)\n await claim.release()\n return settled.outcome\n }\n if (settled.displaced && settled.displaced !== minted) await deps.expireSession(settled.displaced)\n await claim.record(200, payload)\n return { status: 200, body: payload as unknown as Record<string, unknown> }\n } catch (error) {\n console.error('quote pay failed', error)\n if (minted) await deps.expireSession(minted).catch(() => 'open')\n await claim.release()\n return { status: 500, body: { error: 'Your payment could not be set up. Try again.' } }\n }\n}\n\n/** Sends a link route's answer, never cached or indexed. */\nexport function sendQuoteLinkOutcome(res: Parameters<PluginApiHandler>[1], outcome: QuoteLinkOutcome) {\n res.setHeader('Cache-Control', 'private, no-store')\n res.setHeader('X-Robots-Tag', 'noindex, nofollow')\n for (const [name, value] of Object.entries(outcome.headers ?? {})) res.setHeader(name, value)\n return res.status(outcome.status).json(outcome.body)\n}\n\n/** `GET /api/commerce/quote-view`. */\nexport const quoteViewHandler: PluginApiHandler = async (req, res) => {\n try {\n return sendQuoteLinkOutcome(res, await handleQuoteView(defaultQuotePayDeps(), req))\n } catch (error) {\n console.error('quote view failed', error)\n return sendQuoteLinkOutcome(res, { status: 500, body: { error: 'This quote could not be loaded. Try again.' } })\n }\n}\n\n/** `POST /api/commerce/quote-pay`. */\nexport const quotePayHandler: PluginApiHandler = async (req, res) =>\n sendQuoteLinkOutcome(res, await handleQuotePay(defaultQuotePayDeps(), req))\n"],"names":["checkEntitlement","claimAttempt","deriveStripeObjectKey","resolveTransactionFeeCents","resolveHostToken","readClientIp","siteReturnOrigin","consumeRateLimit","firebaseAdmin","getOrgForHost","merchantAccountIsReady","checkoutSessionCardAuthenticationParams","quotePageHref","CommerceModel","expireCheckoutSession","resolveManualTaxRateId","applyNativeCheckoutParams","nativeCheckoutStripeHeaders","readCheckoutSessionPayload","resolveNativeCheckoutMode","orderStatusUrl","ensureCheckoutDomain","verifyQuoteLinkToken","QUOTE_VIEW_LOOKUPS_PER_WINDOW","QUOTE_PAY_PRESSES_PER_WINDOW","QUOTE_WINDOW_MS","defaultQuotePayDeps","firestore","app","key","options","orgForHost","hostId","found","id","orgId","org","nativeMode","taxRateId","input","createSession","params","headers","response","fetch","method","Authorization","process","env","STRIPE_SECRET_KEY","body","toString","ok","session","json","catch","expireSession","sessionId","ensureDomain","now","Date","DOC_ID","NOT_FOUND","status","error","NOT_FOUND_ANY","nounOf","kind","bodyOf","req","raw","parsed","JSON","parse","headerOf","name","value","toLowerCase","String","Array","isArray","trim","limitedQuoteLink","deps","limit","ip","remoteAddress","socket","rate","windowMs","allowed","Math","max","ceil","resetMs","loadLinkedQuote","quoteId","token","hostSnapshot","notFound","invoices","version","outcome","hostRef","collection","doc","store","orderSnapshot","Promise","all","get","exists","order","data","orderPayLinkKind","host","storeName","current","orderPayLinkVersion","state","replaced","loaded","quoteLinkParams","source","slice","complete","test","Boolean","handleQuoteView","query","refused","linked","currency","receiptUrl","view","buildQuotePageView","orderId","buildInvoicePageView","buildQuoteSessionParams","charge","invoice","quote","URLSearchParams","mode","taxAtPayment","lines","forEach","line","index","at","set","amountCents","description","taxable","rates","shippingOptions","length","appendShippingAddressCollectionParams","shippingCountries","CHECKOUT_SHIPPING_COUNTRIES","appendCheckoutShippingParams","shippingCents","country","shipTo","toUpperCase","rateId","shippingFloorCents","Object","entries","destinationChargeParams","accountId","feeCents","taxOwner","merchantGoodsCents","goodsCents","email","customerEmail","customer","separator","returnUrl","includes","discountId","quoteLinkVersion","Number","updatedAtMs","taxCents","taxPct","platformLiableTransferCents","appendStorefrontPaymentMethodParams","paymentMethods","QUOTE_UPDATED_MESSAGE","refusal","reason","extra","handleQuotePay","owner","idempotencyKey","noun","seen","isFinite","blocked","invoicePayRefusal","invoicePayRefusalMessage","unavailable","ownerUid","profile","ready","chargesEnabled","accountLivemode","subject","taxSettings","storefrontTaxDecision","settings","invoiceCheckoutCharge","dearestRateCents","reduce","most","feeProductType","totalCents","site","origin","candidates","requestHost","openedFor","checkoutSessionId","openedAtEdit","openedAtVersion","claimed","scopeId","busyMessage","replay","claim","stripeKey","object","derived","minted","previous","release","confirming","taxableCents","basis","tax","taxLabel","label","round","resolveStorefrontPaymentMethodControls","normalizeStorefrontPaymentMethodSettings","native","domain","pageUrl","resolve","created","payload","wallets","console","orderRef","settled","runTransaction","transaction","snapshot","displaced","closed","merge","record","sendQuoteLinkOutcome","res","setHeader","quoteViewHandler","quotePayHandler"],"mappings":"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"}
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type PluginApiHandler, type PluginApiRequest } from '@aglyn/aglyn/server';
|
|
18
18
|
import { type PluginPersonRecord, type PluginPersonSearchRequest } from '@aglyn/aglyn/plugin-manager/plugin-person-records';
|
|
19
|
+
import { type PluginRecordFactsRead, type PluginRecordFactsRequest } from '@aglyn/aglyn/plugin-manager/plugin-record-facts';
|
|
19
20
|
import { type SaleTaxEngineRequest, type SaleTaxEngineResult } from './tax-engine-quote';
|
|
20
21
|
import { type CheckoutSessionExpiry } from './checkout-session-expiry';
|
|
21
22
|
import type { quotePdfFile } from './commerce-pdf';
|
|
22
23
|
import { type CommercePdfFile } from './pdf-response';
|
|
24
|
+
import type { QuoteMailer } from './quote-email';
|
|
23
25
|
import type { QuoteUploadCalls } from './quote-uploads';
|
|
24
26
|
export interface QuoteDeps {
|
|
25
27
|
verifyIdToken(token: string): Promise<{
|
|
@@ -39,10 +41,19 @@ export interface QuoteDeps {
|
|
|
39
41
|
expireSession(sessionId: string): Promise<CheckoutSessionExpiry>;
|
|
40
42
|
/** The quote's pay link at a link version, or null when the site has no address or no signing secret. */
|
|
41
43
|
payLinkUrl(host: Record<string, unknown>, hostId: string, quoteId: string, version: number): string | null;
|
|
44
|
+
/** One of the current revision's proofs behind that link, for the email's pictures. */
|
|
45
|
+
proofLinkUrl(host: Record<string, unknown>, hostId: string, quoteId: string, version: number, proofId: string): string | null;
|
|
42
46
|
/** The quote's PDF; loads the PDF writer and sharp only when asked. */
|
|
43
47
|
quotePdf(input: Parameters<typeof quotePdfFile>[0]): Promise<CommercePdfFile>;
|
|
44
48
|
/** The upload pipeline, loaded only by the proof actions. */
|
|
45
49
|
uploads(): Promise<QuoteUploadCalls>;
|
|
50
|
+
/** The quote email (AGL-4097): the renderer, the PDF and sharp, loaded only by the send. */
|
|
51
|
+
mailer(): Promise<QuoteMailer>;
|
|
52
|
+
/**
|
|
53
|
+
* A request's facts (AGL-4098), from the plugin that keeps form submissions,
|
|
54
|
+
* through the core's seam; null when no plugin reads them in this process.
|
|
55
|
+
*/
|
|
56
|
+
requestFacts(request: PluginRecordFactsRequest): Promise<PluginRecordFactsRead | null>;
|
|
46
57
|
now(): number;
|
|
47
58
|
}
|
|
48
59
|
export declare function defaultQuoteDeps(): QuoteDeps;
|