@aglyn/plugins-commerce 1.0.0-beta.288 → 1.0.0-beta.290
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 +2 -2
- package/package.json +13 -13
- package/src/lib/components/commerce-console-page.js +29 -2
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
- package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
- package/src/lib/components/console/commerce-pdf-download.js +53 -0
- package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.js +5 -1
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
- package/src/lib/components/console/host-quotes-card.component.js +263 -0
- package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
- package/src/lib/components/console/order-invoice.component.d.ts +6 -4
- package/src/lib/components/console/order-invoice.component.js +67 -11
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
- package/src/lib/components/console/quote-editor-dialog.component.js +1317 -0
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
- package/src/lib/components/console/quote-pay-link.component.js +213 -0
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
- package/src/lib/components/console/quote-proofs.component.d.ts +37 -0
- package/src/lib/components/console/quote-proofs.component.js +626 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -0
- package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
- package/src/lib/components/console/quote-settings-card.component.js +121 -0
- package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.js +4 -1
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
- package/src/lib/components/quote-page-body.d.ts +33 -0
- package/src/lib/components/quote-page-body.js +724 -0
- package/src/lib/components/quote-page-body.js.map +1 -0
- package/src/lib/components/quote-page.d.ts +29 -0
- package/src/lib/components/quote-page.js +96 -0
- package/src/lib/components/quote-page.js.map +1 -0
- package/src/lib/components/quote-proof-panel.d.ts +42 -0
- package/src/lib/components/quote-proof-panel.js +436 -0
- package/src/lib/components/quote-proof-panel.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +7 -1
- package/src/lib/components/storefront-payment-element.js +8 -3
- package/src/lib/components/storefront-payment-element.js.map +1 -1
- package/src/lib/constants/orders-list-query.js +6 -1
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/quote-page.d.ts +30 -0
- package/src/lib/constants/quote-page.js +35 -0
- package/src/lib/constants/quote-page.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +25 -2
- package/src/lib/model/commerce-orders.js +10 -1
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-quote-pay.d.ts +256 -0
- package/src/lib/model/commerce-quote-pay.js +426 -0
- package/src/lib/model/commerce-quote-pay.js.map +1 -0
- package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
- package/src/lib/model/commerce-quote-proofs.js +348 -0
- package/src/lib/model/commerce-quote-proofs.js.map +1 -0
- package/src/lib/model/commerce-quotes.d.ts +335 -0
- package/src/lib/model/commerce-quotes.js +522 -0
- package/src/lib/model/commerce-quotes.js.map +1 -0
- package/src/lib/model/index.d.ts +3 -0
- package/src/lib/model/index.js +3 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +20 -0
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/server/api-v1/orders-and-products.js +6 -2
- package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
- package/src/lib/server/billing-webhook.js +127 -52
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/checkout-session-expiry.d.ts +36 -0
- package/src/lib/server/checkout-session-expiry.js +63 -0
- package/src/lib/server/checkout-session-expiry.js.map +1 -0
- package/src/lib/server/commerce-pdf.d.ts +100 -0
- package/src/lib/server/commerce-pdf.js +283 -0
- package/src/lib/server/commerce-pdf.js.map +1 -0
- package/src/lib/server/draft-order.js +16 -8
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/order-print.d.ts +6 -0
- package/src/lib/server/order-print.js +22 -5
- package/src/lib/server/order-print.js.map +1 -1
- package/src/lib/server/pdf-response.d.ts +31 -0
- package/src/lib/server/pdf-response.js +30 -0
- package/src/lib/server/pdf-response.js.map +1 -0
- package/src/lib/server/person-eraser.d.ts +7 -0
- package/src/lib/server/person-eraser.js +24 -2
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-reader-splash.d.ts +2 -18
- package/src/lib/server/pos-reader-splash.js +8 -93
- package/src/lib/server/pos-reader-splash.js.map +1 -1
- package/src/lib/server/pos-return.js +3 -1
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/quote-link-token.d.ts +39 -0
- package/src/lib/server/quote-link-token.js +97 -0
- package/src/lib/server/quote-link-token.js.map +1 -0
- package/src/lib/server/quote-pay.d.ts +110 -0
- package/src/lib/server/quote-pay.js +606 -0
- package/src/lib/server/quote-pay.js.map +1 -0
- package/src/lib/server/quote-proofs.d.ts +51 -0
- package/src/lib/server/quote-proofs.js +500 -0
- package/src/lib/server/quote-proofs.js.map +1 -0
- package/src/lib/server/quote-uploads.d.ts +33 -0
- package/src/lib/server/quote-uploads.js +29 -0
- package/src/lib/server/quote-uploads.js.map +1 -0
- package/src/lib/server/quote.d.ts +63 -0
- package/src/lib/server/quote.js +846 -0
- package/src/lib/server/quote.js.map +1 -0
- package/src/lib/server/related.js +2 -0
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/site-image-source.d.ts +38 -0
- package/src/lib/server/site-image-source.js +130 -0
- package/src/lib/server/site-image-source.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +2 -0
- package/src/lib/server/site-page-resolver.js +75 -0
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +33 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/site.js +6 -0
- package/src/lib/site.js.map +1 -1
- package/src/lib/transfer/tracking.server.js +6 -0
- package/src/lib/transfer/tracking.server.js.map +1 -1
- package/src/lib/utils/commerce-document-pdf.d.ts +77 -0
- package/src/lib/utils/commerce-document-pdf.js +608 -0
- package/src/lib/utils/commerce-document-pdf.js.map +1 -0
- package/src/lib/utils/order-invoice.d.ts +52 -3
- package/src/lib/utils/order-invoice.js +116 -64
- package/src/lib/utils/order-invoice.js.map +1 -1
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
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 HostOrder, type OrderAddress, type OrderLineItem, type OrderTotals } from './commerce-orders';
|
|
18
|
+
import { type OrderQuote, type QuoteApproval, type QuoteCustomer, type QuoteCustomerResponse, type QuoteDiscount, type QuoteProof, type QuoteResponseFile, type QuoteShipping, type QuoteStage, type QuoteTaxBasis } from './commerce-quotes';
|
|
19
|
+
import type { StorefrontTaxMode } from './commerce-tax-decision';
|
|
20
|
+
/** The most proofs one revision holds. */
|
|
21
|
+
export declare const QUOTE_MAX_PROOFS = 10;
|
|
22
|
+
/** The most answers the customer gives one revision; plenty for a change of mind. */
|
|
23
|
+
export declare const QUOTE_MAX_RESPONSES = 20;
|
|
24
|
+
/** The longest comment a customer may leave. */
|
|
25
|
+
export declare const QUOTE_MAX_COMMENT = 5000;
|
|
26
|
+
/** The longest name an approval is signed with. */
|
|
27
|
+
export declare const QUOTE_MAX_APPROVER_NAME = 120;
|
|
28
|
+
/** What the visitor uploads are called for a quote: its proofs, and the customer's artwork. */
|
|
29
|
+
export declare const QUOTE_PROOF_PURPOSE = "quote-proof";
|
|
30
|
+
export declare const QUOTE_ARTWORK_PURPOSE = "quote-artwork";
|
|
31
|
+
/** A revision as it stood when the next one started. */
|
|
32
|
+
export interface QuoteRevisionSnapshot {
|
|
33
|
+
revision: number;
|
|
34
|
+
/** Where it stood with the business when it closed. */
|
|
35
|
+
stage: QuoteStage;
|
|
36
|
+
lineItems: OrderLineItem[];
|
|
37
|
+
totals: OrderTotals | null;
|
|
38
|
+
taxMode: StorefrontTaxMode | null;
|
|
39
|
+
tax: QuoteTaxBasis | null;
|
|
40
|
+
discount: QuoteDiscount | null;
|
|
41
|
+
shipping: QuoteShipping | null;
|
|
42
|
+
notes: string;
|
|
43
|
+
terms: string;
|
|
44
|
+
customer: QuoteCustomer | null;
|
|
45
|
+
billTo: OrderAddress | null;
|
|
46
|
+
shipTo: OrderAddress | null;
|
|
47
|
+
proofs: QuoteProof[];
|
|
48
|
+
responses: QuoteCustomerResponse[];
|
|
49
|
+
approval: QuoteApproval | null;
|
|
50
|
+
/** When the revision was last written by the business. */
|
|
51
|
+
updatedAtMs: number;
|
|
52
|
+
/** When the next revision started, and who started it. */
|
|
53
|
+
closedAtMs: number;
|
|
54
|
+
closedBy: string;
|
|
55
|
+
}
|
|
56
|
+
/** The current revision's proofs. */
|
|
57
|
+
export declare function currentQuoteProofs(quote: Pick<OrderQuote, 'proofs' | 'revision'> | null | undefined): QuoteProof[];
|
|
58
|
+
/** The customer's latest answer to the current revision, if they gave one. */
|
|
59
|
+
export declare function latestQuoteResponse(quote: Pick<OrderQuote, 'responses' | 'revision'> | null | undefined): QuoteCustomerResponse | null;
|
|
60
|
+
/** Whether the customer has answered the current revision. */
|
|
61
|
+
export declare function quoteRevisionAnswered(quote: Pick<OrderQuote, 'responses' | 'revision'> | null | undefined): boolean;
|
|
62
|
+
export type QuoteProofRefusal = 'not-a-quote' | 'paid' | 'answered' | 'full' | 'unknown-proof';
|
|
63
|
+
/** Why the current revision's proofs cannot be changed now, if they cannot. */
|
|
64
|
+
export declare function quoteProofChangeRefusal(order: Pick<HostOrder, 'channel' | 'status' | 'quote'> | null | undefined, change: 'add' | 'remove'): QuoteProofRefusal | null;
|
|
65
|
+
export declare function quoteProofRefusalMessage(reason: QuoteProofRefusal): string;
|
|
66
|
+
/** The quote with a proof added to its current revision, edited by `by` at `atMs`. */
|
|
67
|
+
export declare function withQuoteProofAdded(order: HostOrder, proof: Omit<QuoteProof, 'revision' | 'addedAtMs' | 'addedBy'>, options: {
|
|
68
|
+
atMs: number;
|
|
69
|
+
by: string;
|
|
70
|
+
}): {
|
|
71
|
+
ok: true;
|
|
72
|
+
order: HostOrder;
|
|
73
|
+
proof: QuoteProof;
|
|
74
|
+
} | {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: QuoteProofRefusal;
|
|
77
|
+
};
|
|
78
|
+
/** The quote with one of the current revision's proofs taken off. */
|
|
79
|
+
export declare function withQuoteProofRemoved(order: HostOrder, proofId: string, options: {
|
|
80
|
+
atMs: number;
|
|
81
|
+
by: string;
|
|
82
|
+
}): {
|
|
83
|
+
ok: true;
|
|
84
|
+
order: HostOrder;
|
|
85
|
+
proof: QuoteProof;
|
|
86
|
+
} | {
|
|
87
|
+
ok: false;
|
|
88
|
+
reason: QuoteProofRefusal;
|
|
89
|
+
};
|
|
90
|
+
export type QuoteRevisionRefusal = 'not-a-quote' | 'paid';
|
|
91
|
+
/**
|
|
92
|
+
* Starts the quote's next revision. The current one is SNAPSHOTTED as it
|
|
93
|
+
* stands (its lines, totals, notes, terms, proofs and the customer's
|
|
94
|
+
* answers); the quote moves to revision N+1 with no proofs and no answer,
|
|
95
|
+
* back at Draft, edited by `by` at `atMs`. A paid quote starts none.
|
|
96
|
+
*
|
|
97
|
+
* The caller writes `snapshot` to `hosts/{hostId}/orders/{id}/revisions/{revision}`
|
|
98
|
+
* (`String(snapshot.revision)`, created, never overwritten) and `order` over
|
|
99
|
+
* the quote, in one transaction. The pay link's version is not touched: the
|
|
100
|
+
* send that hands the customer the new revision decides that.
|
|
101
|
+
*/
|
|
102
|
+
export declare function startQuoteRevision(order: HostOrder, options: {
|
|
103
|
+
atMs: number;
|
|
104
|
+
by: string;
|
|
105
|
+
}): {
|
|
106
|
+
ok: true;
|
|
107
|
+
snapshot: QuoteRevisionSnapshot;
|
|
108
|
+
order: HostOrder;
|
|
109
|
+
revision: number;
|
|
110
|
+
} | {
|
|
111
|
+
ok: false;
|
|
112
|
+
reason: QuoteRevisionRefusal;
|
|
113
|
+
error: string;
|
|
114
|
+
};
|
|
115
|
+
export type QuoteRespondRefusal = 'not-a-quote' | 'paid' | 'no-proof' | 'too-many';
|
|
116
|
+
/** Why the customer cannot answer the current revision now, if they cannot. */
|
|
117
|
+
export declare function quoteRespondRefusal(order: Pick<HostOrder, 'channel' | 'status' | 'quote'> | null | undefined): QuoteRespondRefusal | null;
|
|
118
|
+
export declare function quoteRespondRefusalMessage(reason: QuoteRespondRefusal, storeName: string): string;
|
|
119
|
+
/** What the customer sends: approve under a name, or changes with a comment and any files. */
|
|
120
|
+
export type QuoteResponseInput = {
|
|
121
|
+
action: 'approve';
|
|
122
|
+
name: string;
|
|
123
|
+
} | {
|
|
124
|
+
action: 'changes';
|
|
125
|
+
comment: string;
|
|
126
|
+
fileIds: string[];
|
|
127
|
+
};
|
|
128
|
+
/** The customer's answer as the route received it, checked and trimmed, or what to fix. */
|
|
129
|
+
export declare function parseQuoteResponseInput(raw: unknown): {
|
|
130
|
+
ok: true;
|
|
131
|
+
input: QuoteResponseInput;
|
|
132
|
+
} | {
|
|
133
|
+
ok: false;
|
|
134
|
+
error: string;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* The quote with the customer's answer recorded on its current revision.
|
|
138
|
+
* Approving names the revision approved and moves the quote to Approved;
|
|
139
|
+
* asking for changes moves it to Changes requested and withdraws an approval
|
|
140
|
+
* of the same revision. The latest answer is the one that counts. The
|
|
141
|
+
* business's edit stamp (`updatedAtMs`) is not touched: the quote's content
|
|
142
|
+
* did not change, and the page that sent the answer can still pay it.
|
|
143
|
+
*/
|
|
144
|
+
export declare function applyQuoteResponse(order: HostOrder, input: QuoteResponseInput & {
|
|
145
|
+
files?: QuoteResponseFile[];
|
|
146
|
+
}, context: {
|
|
147
|
+
atMs: number;
|
|
148
|
+
ip: string | null;
|
|
149
|
+
userAgent: string | null;
|
|
150
|
+
}): {
|
|
151
|
+
ok: true;
|
|
152
|
+
order: HostOrder;
|
|
153
|
+
response: QuoteCustomerResponse;
|
|
154
|
+
} | {
|
|
155
|
+
ok: false;
|
|
156
|
+
reason: QuoteRespondRefusal;
|
|
157
|
+
};
|
|
158
|
+
/** One line on what the customer last said: `Approved by Jordan Lee`, `Changes requested`. */
|
|
159
|
+
export declare function describeQuoteResponse(response: Pick<QuoteCustomerResponse, 'action' | 'name'>): string;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { appendOrderEvent } from "./commerce-orders.js";
|
|
18
|
+
import { formatQuoteNumber, isQuote, isUnpaidQuote } from "./commerce-quotes.js";
|
|
19
|
+
/*==========================================
|
|
20
|
+
* PROOFS, REVISIONS AND THE CUSTOMER'S ANSWER (AGL-4096).
|
|
21
|
+
*
|
|
22
|
+
* A quote's REVISION is the version the customer is looking at. The business
|
|
23
|
+
* attaches proofs to the current revision; the customer approves them under
|
|
24
|
+
* a typed name or asks for changes; the business then starts the next
|
|
25
|
+
* revision, which keeps the one before exactly as it stood in
|
|
26
|
+
* `hosts/{hostId}/orders/{id}/revisions/{n}` and starts the new one with no
|
|
27
|
+
* proofs and no answer. Every revision is kept.
|
|
28
|
+
*
|
|
29
|
+
* Whether paying waits on an approval is the business's control
|
|
30
|
+
* (`requireProofApproval`, AGL-4093) and `quotePaymentBlockedByApproval`
|
|
31
|
+
* answers it; nothing here makes paying wait when it is off.
|
|
32
|
+
*
|
|
33
|
+
* Pure: the console route, the customer's route and (slice 5) the send that
|
|
34
|
+
* starts a revision all call these, and write what they answer.
|
|
35
|
+
*=========================================*/ /** The most proofs one revision holds. */ export const QUOTE_MAX_PROOFS = 10;
|
|
36
|
+
/** The most answers the customer gives one revision; plenty for a change of mind. */ export const QUOTE_MAX_RESPONSES = 20;
|
|
37
|
+
/** The longest comment a customer may leave. */ export const QUOTE_MAX_COMMENT = 5000;
|
|
38
|
+
/** The longest name an approval is signed with. */ export const QUOTE_MAX_APPROVER_NAME = 120;
|
|
39
|
+
/** What the visitor uploads are called for a quote: its proofs, and the customer's artwork. */ export const QUOTE_PROOF_PURPOSE = 'quote-proof';
|
|
40
|
+
export const QUOTE_ARTWORK_PURPOSE = 'quote-artwork';
|
|
41
|
+
/** The current revision's proofs. */ export function currentQuoteProofs(quote) {
|
|
42
|
+
var _ref, _ref1;
|
|
43
|
+
const revision = Number((_ref = quote == null ? void 0 : quote.revision) != null ? _ref : 1);
|
|
44
|
+
return ((_ref1 = quote == null ? void 0 : quote.proofs) != null ? _ref1 : []).filter((proof)=>Number(proof == null ? void 0 : proof.revision) === revision);
|
|
45
|
+
}
|
|
46
|
+
/** The customer's latest answer to the current revision, if they gave one. */ export function latestQuoteResponse(quote) {
|
|
47
|
+
var _ref, _ref1;
|
|
48
|
+
const revision = Number((_ref = quote == null ? void 0 : quote.revision) != null ? _ref : 1);
|
|
49
|
+
const answers = ((_ref1 = quote == null ? void 0 : quote.responses) != null ? _ref1 : []).filter((response)=>Number(response == null ? void 0 : response.revision) === revision);
|
|
50
|
+
return answers.length ? answers[answers.length - 1] : null;
|
|
51
|
+
}
|
|
52
|
+
/** Whether the customer has answered the current revision. */ export function quoteRevisionAnswered(quote) {
|
|
53
|
+
return latestQuoteResponse(quote) !== null;
|
|
54
|
+
}
|
|
55
|
+
/** Why the current revision's proofs cannot be changed now, if they cannot. */ export function quoteProofChangeRefusal(order, change) {
|
|
56
|
+
if (!order || !isQuote(order) || !order.quote) return 'not-a-quote';
|
|
57
|
+
if (!isUnpaidQuote(order)) return 'paid';
|
|
58
|
+
if (quoteRevisionAnswered(order.quote)) return 'answered';
|
|
59
|
+
if (change === 'add' && currentQuoteProofs(order.quote).length >= QUOTE_MAX_PROOFS) return 'full';
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
export function quoteProofRefusalMessage(reason) {
|
|
63
|
+
switch(reason){
|
|
64
|
+
case 'not-a-quote':
|
|
65
|
+
return 'Unknown quote';
|
|
66
|
+
case 'paid':
|
|
67
|
+
return 'This quote has been paid, so its proofs can no longer be changed.';
|
|
68
|
+
case 'answered':
|
|
69
|
+
return 'The customer has answered this revision. Start a new revision to send different proofs.';
|
|
70
|
+
case 'full':
|
|
71
|
+
return `A revision holds up to ${QUOTE_MAX_PROOFS} proofs. Remove one first.`;
|
|
72
|
+
case 'unknown-proof':
|
|
73
|
+
return 'That proof is no longer on this quote.';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** The quote with a proof added to its current revision, edited by `by` at `atMs`. */ export function withQuoteProofAdded(order, proof, options) {
|
|
77
|
+
var _quote_revision;
|
|
78
|
+
const refused = quoteProofChangeRefusal(order, 'add');
|
|
79
|
+
if (refused) return {
|
|
80
|
+
ok: false,
|
|
81
|
+
reason: refused
|
|
82
|
+
};
|
|
83
|
+
const quote = order.quote;
|
|
84
|
+
const added = {
|
|
85
|
+
id: proof.id,
|
|
86
|
+
revision: Number((_quote_revision = quote.revision) != null ? _quote_revision : 1),
|
|
87
|
+
name: proof.name,
|
|
88
|
+
contentType: proof.contentType,
|
|
89
|
+
size: proof.size,
|
|
90
|
+
path: proof.path,
|
|
91
|
+
addedAtMs: options.atMs,
|
|
92
|
+
addedBy: options.by
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
proof: added,
|
|
97
|
+
order: _extends({}, order, {
|
|
98
|
+
timeline: appendOrderEvent(order, 'quote-proof-added', `Proof ${added.name} added to revision ${added.revision} by ${options.by}`, options.atMs),
|
|
99
|
+
quote: _extends({}, quote, {
|
|
100
|
+
proofs: [
|
|
101
|
+
...currentQuoteProofs(quote).filter((entry)=>entry.id !== added.id),
|
|
102
|
+
added
|
|
103
|
+
],
|
|
104
|
+
updatedAtMs: options.atMs,
|
|
105
|
+
updatedBy: options.by
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** The quote with one of the current revision's proofs taken off. */ export function withQuoteProofRemoved(order, proofId, options) {
|
|
111
|
+
const refused = quoteProofChangeRefusal(order, 'remove');
|
|
112
|
+
if (refused) return {
|
|
113
|
+
ok: false,
|
|
114
|
+
reason: refused
|
|
115
|
+
};
|
|
116
|
+
const quote = order.quote;
|
|
117
|
+
const proofs = currentQuoteProofs(quote);
|
|
118
|
+
const proof = proofs.find((entry)=>entry.id === proofId);
|
|
119
|
+
if (!proof) return {
|
|
120
|
+
ok: false,
|
|
121
|
+
reason: 'unknown-proof'
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
proof,
|
|
126
|
+
order: _extends({}, order, {
|
|
127
|
+
timeline: appendOrderEvent(order, 'quote-proof-removed', `Proof ${proof.name} removed from revision ${proof.revision} by ${options.by}`, options.atMs),
|
|
128
|
+
quote: _extends({}, quote, {
|
|
129
|
+
proofs: proofs.filter((entry)=>entry.id !== proofId),
|
|
130
|
+
updatedAtMs: options.atMs,
|
|
131
|
+
updatedBy: options.by
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Starts the quote's next revision. The current one is SNAPSHOTTED as it
|
|
138
|
+
* stands (its lines, totals, notes, terms, proofs and the customer's
|
|
139
|
+
* answers); the quote moves to revision N+1 with no proofs and no answer,
|
|
140
|
+
* back at Draft, edited by `by` at `atMs`. A paid quote starts none.
|
|
141
|
+
*
|
|
142
|
+
* The caller writes `snapshot` to `hosts/{hostId}/orders/{id}/revisions/{revision}`
|
|
143
|
+
* (`String(snapshot.revision)`, created, never overwritten) and `order` over
|
|
144
|
+
* the quote, in one transaction. The pay link's version is not touched: the
|
|
145
|
+
* send that hands the customer the new revision decides that.
|
|
146
|
+
*/ export function startQuoteRevision(order, options) {
|
|
147
|
+
var _quote_revision, _quote_responses, _quote_stage, _order_lineItems, _order_totals, _order_taxMode, _quote_tax, _quote_discount, _quote_shipping, _quote_notes, _quote_terms, _quote_customer, _quote_billTo, _quote_shipTo;
|
|
148
|
+
if (!isQuote(order) || !order.quote) return {
|
|
149
|
+
ok: false,
|
|
150
|
+
reason: 'not-a-quote',
|
|
151
|
+
error: 'Unknown quote'
|
|
152
|
+
};
|
|
153
|
+
if (!isUnpaidQuote(order)) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
reason: 'paid',
|
|
157
|
+
error: 'This quote has been paid, so it takes no new revision.'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const quote = order.quote;
|
|
161
|
+
const current = Math.max(1, Math.floor(Number((_quote_revision = quote.revision) != null ? _quote_revision : 1)) || 1);
|
|
162
|
+
const responses = ((_quote_responses = quote.responses) != null ? _quote_responses : []).filter((response)=>Number(response == null ? void 0 : response.revision) === current);
|
|
163
|
+
const approval = quote.approval && Number(quote.approval.revision) === current ? quote.approval : null;
|
|
164
|
+
const snapshot = {
|
|
165
|
+
revision: current,
|
|
166
|
+
stage: (_quote_stage = quote.stage) != null ? _quote_stage : 'draft',
|
|
167
|
+
lineItems: (_order_lineItems = order.lineItems) != null ? _order_lineItems : [],
|
|
168
|
+
totals: (_order_totals = order.totals) != null ? _order_totals : null,
|
|
169
|
+
taxMode: (_order_taxMode = order.taxMode) != null ? _order_taxMode : null,
|
|
170
|
+
tax: (_quote_tax = quote.tax) != null ? _quote_tax : null,
|
|
171
|
+
discount: (_quote_discount = quote.discount) != null ? _quote_discount : null,
|
|
172
|
+
shipping: (_quote_shipping = quote.shipping) != null ? _quote_shipping : null,
|
|
173
|
+
notes: (_quote_notes = quote.notes) != null ? _quote_notes : '',
|
|
174
|
+
terms: (_quote_terms = quote.terms) != null ? _quote_terms : '',
|
|
175
|
+
customer: (_quote_customer = quote.customer) != null ? _quote_customer : null,
|
|
176
|
+
billTo: (_quote_billTo = quote.billTo) != null ? _quote_billTo : null,
|
|
177
|
+
shipTo: (_quote_shipTo = quote.shipTo) != null ? _quote_shipTo : null,
|
|
178
|
+
proofs: currentQuoteProofs(quote),
|
|
179
|
+
responses,
|
|
180
|
+
approval,
|
|
181
|
+
updatedAtMs: Number(quote.updatedAtMs) || 0,
|
|
182
|
+
closedAtMs: options.atMs,
|
|
183
|
+
closedBy: options.by
|
|
184
|
+
};
|
|
185
|
+
const next = current + 1;
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
revision: current,
|
|
189
|
+
snapshot,
|
|
190
|
+
order: _extends({}, order, {
|
|
191
|
+
timeline: appendOrderEvent(order, 'quote-revision-started', `Revision ${next} started by ${options.by}; revision ${current} is kept with its proofs and answers`, options.atMs),
|
|
192
|
+
quote: _extends({}, quote, {
|
|
193
|
+
revision: next,
|
|
194
|
+
stage: 'draft',
|
|
195
|
+
proofs: [],
|
|
196
|
+
responses: [],
|
|
197
|
+
approval: null,
|
|
198
|
+
updatedAtMs: options.atMs,
|
|
199
|
+
updatedBy: options.by
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Why the customer cannot answer the current revision now, if they cannot. */ export function quoteRespondRefusal(order) {
|
|
205
|
+
var _order_quote_revision, _order_quote_responses;
|
|
206
|
+
if (!order || !isQuote(order) || !order.quote) return 'not-a-quote';
|
|
207
|
+
if (!isUnpaidQuote(order)) return 'paid';
|
|
208
|
+
if (currentQuoteProofs(order.quote).length === 0) return 'no-proof';
|
|
209
|
+
const revision = Number((_order_quote_revision = order.quote.revision) != null ? _order_quote_revision : 1);
|
|
210
|
+
const given = ((_order_quote_responses = order.quote.responses) != null ? _order_quote_responses : []).filter((response)=>Number(response == null ? void 0 : response.revision) === revision);
|
|
211
|
+
if (given.length >= QUOTE_MAX_RESPONSES) return 'too-many';
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
export function quoteRespondRefusalMessage(reason, storeName) {
|
|
215
|
+
const business = storeName || 'the business';
|
|
216
|
+
switch(reason){
|
|
217
|
+
case 'not-a-quote':
|
|
218
|
+
return 'We could not find that quote. Check the link you were sent.';
|
|
219
|
+
case 'paid':
|
|
220
|
+
return 'This quote has been paid, so it takes no more answers.';
|
|
221
|
+
case 'no-proof':
|
|
222
|
+
return `There is no proof on this quote to answer yet. ${storeName || 'The business'} will send one.`;
|
|
223
|
+
case 'too-many':
|
|
224
|
+
return `This proof has had a lot of answers already. Contact ${business} directly.`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** The customer's answer as the route received it, checked and trimmed, or what to fix. */ export function parseQuoteResponseInput(raw) {
|
|
228
|
+
const source = raw && typeof raw === 'object' ? raw : {};
|
|
229
|
+
if (source['action'] === 'approve') {
|
|
230
|
+
const name = typeof source['name'] === 'string' ? source['name'].replace(/\s+/g, ' ').trim() : '';
|
|
231
|
+
if (name.length < 2) return {
|
|
232
|
+
ok: false,
|
|
233
|
+
error: 'Type your name to approve the proof.'
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
ok: true,
|
|
237
|
+
input: {
|
|
238
|
+
action: 'approve',
|
|
239
|
+
name: name.slice(0, QUOTE_MAX_APPROVER_NAME)
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (source['action'] === 'changes') {
|
|
244
|
+
const comment = typeof source['comment'] === 'string' ? source['comment'].trim() : '';
|
|
245
|
+
if (!comment) return {
|
|
246
|
+
ok: false,
|
|
247
|
+
error: 'Tell us what to change.'
|
|
248
|
+
};
|
|
249
|
+
const raws = Array.isArray(source['fileIds']) ? source['fileIds'] : [];
|
|
250
|
+
const named = raws.map((value)=>String(value != null ? value : '')).filter((value)=>/^[A-Za-z0-9_-]{1,128}$/.test(value));
|
|
251
|
+
if (named.length !== raws.length) return {
|
|
252
|
+
ok: false,
|
|
253
|
+
error: 'One of the files could not be attached. Send it again.'
|
|
254
|
+
};
|
|
255
|
+
const fileIds = [
|
|
256
|
+
...new Set(named)
|
|
257
|
+
];
|
|
258
|
+
if (fileIds.length > QUOTE_MAX_PROOFS) return {
|
|
259
|
+
ok: false,
|
|
260
|
+
error: `Send up to ${QUOTE_MAX_PROOFS} files at a time.`
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
ok: true,
|
|
264
|
+
input: {
|
|
265
|
+
action: 'changes',
|
|
266
|
+
comment: comment.slice(0, QUOTE_MAX_COMMENT),
|
|
267
|
+
fileIds
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
ok: false,
|
|
273
|
+
error: 'Approve the proof or ask for changes.'
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The quote with the customer's answer recorded on its current revision.
|
|
278
|
+
* Approving names the revision approved and moves the quote to Approved;
|
|
279
|
+
* asking for changes moves it to Changes requested and withdraws an approval
|
|
280
|
+
* of the same revision. The latest answer is the one that counts. The
|
|
281
|
+
* business's edit stamp (`updatedAtMs`) is not touched: the quote's content
|
|
282
|
+
* did not change, and the page that sent the answer can still pay it.
|
|
283
|
+
*/ export function applyQuoteResponse(order, input, context) {
|
|
284
|
+
var _quote_revision, _quote_responses, _response_name, _quote_approvedRevision;
|
|
285
|
+
var _input_files, _response_files;
|
|
286
|
+
const refused = quoteRespondRefusal(order);
|
|
287
|
+
if (refused) return {
|
|
288
|
+
ok: false,
|
|
289
|
+
reason: refused
|
|
290
|
+
};
|
|
291
|
+
const quote = order.quote;
|
|
292
|
+
const revision = Number((_quote_revision = quote.revision) != null ? _quote_revision : 1);
|
|
293
|
+
const ip = context.ip ? context.ip.slice(0, 64) : null;
|
|
294
|
+
const userAgent = context.userAgent ? context.userAgent.slice(0, 400) : null;
|
|
295
|
+
const response = input.action === 'approve' ? {
|
|
296
|
+
action: 'approve',
|
|
297
|
+
revision,
|
|
298
|
+
atMs: context.atMs,
|
|
299
|
+
name: input.name,
|
|
300
|
+
ip,
|
|
301
|
+
userAgent
|
|
302
|
+
} : _extends({
|
|
303
|
+
action: 'changes',
|
|
304
|
+
revision,
|
|
305
|
+
atMs: context.atMs,
|
|
306
|
+
comment: input.comment
|
|
307
|
+
}, ((_input_files = input.files) == null ? void 0 : _input_files.length) ? {
|
|
308
|
+
files: input.files
|
|
309
|
+
} : {}, {
|
|
310
|
+
ip,
|
|
311
|
+
userAgent
|
|
312
|
+
});
|
|
313
|
+
const responses = [
|
|
314
|
+
...((_quote_responses = quote.responses) != null ? _quote_responses : []).filter((entry)=>Number(entry == null ? void 0 : entry.revision) === revision),
|
|
315
|
+
response
|
|
316
|
+
];
|
|
317
|
+
const number = formatQuoteNumber(order);
|
|
318
|
+
const next = response.action === 'approve' ? _extends({}, quote, {
|
|
319
|
+
responses,
|
|
320
|
+
stage: 'approved',
|
|
321
|
+
approvedRevision: revision,
|
|
322
|
+
approval: {
|
|
323
|
+
revision,
|
|
324
|
+
name: (_response_name = response.name) != null ? _response_name : '',
|
|
325
|
+
atMs: context.atMs,
|
|
326
|
+
ip,
|
|
327
|
+
userAgent
|
|
328
|
+
}
|
|
329
|
+
}) : _extends({}, quote, {
|
|
330
|
+
responses,
|
|
331
|
+
stage: 'changes_requested',
|
|
332
|
+
approvedRevision: Number(quote.approvedRevision) === revision ? null : (_quote_approvedRevision = quote.approvedRevision) != null ? _quote_approvedRevision : null,
|
|
333
|
+
approval: null
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
ok: true,
|
|
337
|
+
response,
|
|
338
|
+
order: _extends({}, order, {
|
|
339
|
+
timeline: appendOrderEvent(order, response.action === 'approve' ? 'quote-approved' : 'quote-changes-requested', response.action === 'approve' ? `${number} revision ${revision} approved by the customer as ${response.name}` : `The customer asked for changes to ${number} revision ${revision}` + (((_response_files = response.files) == null ? void 0 : _response_files.length) ? ` and sent ${response.files.length} file${response.files.length === 1 ? '' : 's'}` : ''), context.atMs),
|
|
340
|
+
quote: next
|
|
341
|
+
})
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/** One line on what the customer last said: `Approved by Jordan Lee`, `Changes requested`. */ export function describeQuoteResponse(response) {
|
|
345
|
+
return response.action === 'approve' ? `Approved by ${response.name || 'the customer'}` : 'Changes requested';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//# sourceMappingURL=commerce-quote-proofs.js.map
|