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