@aglyn/plugins-commerce 1.0.0-beta.289 → 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/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.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/host-quotes-card.component.js +20 -4
- package/src/lib/components/console/host-quotes-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 +15 -4
- package/src/lib/components/console/order-invoice.component.js +93 -12
- 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 +154 -5
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/quote-proofs.component.d.ts +48 -0
- package/src/lib/components/console/quote-proofs.component.js +694 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -0
- 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 +67 -20
- package/src/lib/components/quote-page-body.js.map +1 -1
- 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/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 +76 -6
- package/src/lib/model/commerce-quote-pay.js +105 -39
- package/src/lib/model/commerce-quote-pay.js.map +1 -1
- 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-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 +140 -3
- package/src/lib/model/commerce-quotes.js.map +1 -1
- package/src/lib/model/index.d.ts +4 -0
- package/src/lib/model/index.js +4 -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 +104 -0
- package/src/lib/server/commerce-pdf.js +310 -0
- package/src/lib/server/commerce-pdf.js.map +1 -0
- 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-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/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/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 +3 -2
- package/src/lib/server/person-eraser.js +25 -6
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-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/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 +38 -6
- package/src/lib/server/quote-pay.js +115 -53
- package/src/lib/server/quote-pay.js.map +1 -1
- 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 +25 -0
- package/src/lib/server/quote.js +638 -8
- package/src/lib/server/quote.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.js +2 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +24 -1
- 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 +82 -0
- package/src/lib/utils/commerce-document-pdf.js +613 -0
- package/src/lib/utils/commerce-document-pdf.js.map +1 -0
- 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 +52 -3
- package/src/lib/utils/order-invoice.js +124 -64
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/utils/commerce-document-pdf.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 { buildEmailPalette, emailAccent, resolveEmailColor, type EmailPalette } from '@aglyn/shared-util-email'\nimport { relativeLuminanceOfRgb, rgbChannelsOfHex } from '@aglyn/shared-util-tools/contrast'\nimport type { PdfJpegImage } from '@aglyn/shared-util-tools/pdf/pdf-file'\nimport { PdfWriter, pdfRgbFromHex, type PdfRgb, type PdfTableCell } from '@aglyn/shared-util-tools/pdf/pdf-writer'\nimport type { QuotePageAddress, QuotePageView } from '../model/commerce-quote-pay'\nimport { buildOrderInvoiceData, invoiceAddressLines, invoiceMoney, type InvoiceOrder, type OrderInvoiceStore } from './order-invoice'\n\n/*==========================================\n * A QUOTE AND AN INVOICE AS BRANDED PDFs (AGL-4095).\n *\n * renderQuotePdf({ brand, view, revision, payUrl }) → Uint8Array\n * renderOrderInvoicePdf({ brand, order, orderId, store }) → Uint8Array\n *\n * Both are one layout: the business's logo, name and contact at the top left\n * under a band of the site's primary color, the document's name, number,\n * date and standing at the top right, who it is billed and shipped to, the\n * lines in a table whose header wears the primary color, the totals, then\n * notes, terms and (for a quote still to be paid) the pay link as a button\n * and as text, both clickable. Every page ends with the document's name and\n * its page number.\n *\n * Pure over its input: the server reads the site, its theme and its logo\n * (`server/commerce-pdf-brand.ts`) and the quote or order, and these turn\n * them into bytes, so the console's download, the customer's download and\n * the emails that attach a quote or an invoice all make the same file.\n *\n * A quote is drawn from `buildQuotePageView`, the customer-safe projection\n * the quote page shows, so the PDF never says more than the page does. An\n * invoice is drawn from `buildOrderInvoiceData`, the content the printed\n * invoice shows.\n *=========================================*/\n\n/** The colors a document is drawn in, as `#rrggbb`. */\nexport interface CommercePdfColors {\n /** The band, the table's header and the pay button: the site's primary color. */\n accent: string\n /** Text set on the accent. */\n onAccent: string\n /** The accent as text on white: the document's name, links. */\n accentText: string\n /** A wash of the accent, behind the total. */\n tint: string\n ink: string\n muted: string\n rule: string\n}\n\n/** Who the document is from. */\nexport interface CommercePdfBrand {\n businessName: string\n /** Address lines, email, phone and web address, in that order. */\n contactLines: readonly string[]\n /** The site's logo as a JPEG; the business name stands alone without one. */\n logo?: PdfJpegImage | null\n /** The site theme's colors; any left out are the neutral ones. */\n colors?: Partial<CommercePdfColors> | null\n /** The site's time zone, which dates are given in. */\n timeZone?: string | null\n}\n\nconst NEUTRAL_PALETTE = buildEmailPalette({ base: 'tenant' })\nconst neutral = (path: string) => resolveEmailColor(path, NEUTRAL_PALETTE) ?? ''\n\n/** The colors a document wears where its site's theme gives none: the default site theme's greys. */\nexport const COMMERCE_PDF_NEUTRAL_COLORS: Readonly<CommercePdfColors> = Object.freeze({\n accent: neutral('grey.800'),\n onAccent: neutral('background.paper'),\n accentText: neutral('grey.800'),\n tint: neutral('grey.100'),\n ink: neutral('text.primary'),\n muted: neutral('text.secondary'),\n rule: neutral('divider'),\n})\n\n/** WCAG contrast between two `#rrggbb` colors; 1 when either cannot be read. */\nfunction contrast(one: string, other: string): number {\n const a = rgbChannelsOfHex(one)\n const b = rgbChannelsOfHex(other)\n if (!a || !b) return 1\n const la = relativeLuminanceOfRgb(...a)\n const lb = relativeLuminanceOfRgb(...b)\n return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05)\n}\n\n/**\n * A document's colors from a site's resolved palette: the primary color for\n * the accent, the theme's own text and divider colors, and text on the\n * accent that reads at 4.5:1 (the theme's contrast text when it does, else\n * the theme's dark ink or white, whichever reads better).\n */\nexport function commercePdfColorsFromPalette(palette: EmailPalette): CommercePdfColors {\n const accent = emailAccent(palette)\n const read = (path: string, fallback: string) => resolveEmailColor(path, palette) ?? fallback\n const ink = read('text.primary', COMMERCE_PDF_NEUTRAL_COLORS.ink)\n const paper = read('background.paper', COMMERCE_PDF_NEUTRAL_COLORS.onAccent)\n const onAccent =\n contrast(accent.label, accent.fill) >= 4.5\n ? accent.label\n : contrast(ink, accent.fill) >= contrast(paper, accent.fill)\n ? ink\n : paper\n return {\n accent: accent.fill,\n onAccent,\n accentText: contrast(accent.link, paper) >= 4.5 ? accent.link : ink,\n tint: read('tint.primary', COMMERCE_PDF_NEUTRAL_COLORS.tint),\n ink,\n muted: read('text.secondary', COMMERCE_PDF_NEUTRAL_COLORS.muted),\n rule: read('divider', COMMERCE_PDF_NEUTRAL_COLORS.rule),\n }\n}\n\n/*==========================================\n * WHAT A DOCUMENT SAYS\n *=========================================*/\n\ninterface DocumentLine {\n name: string\n details: string[]\n quantity: number\n unit: string\n amount: string\n}\n\ninterface SummaryRow {\n label: string\n value: string\n emphasis?: boolean\n /** A note in place of a figure, such as \"Tax is added at payment\". */\n note?: boolean\n}\n\ninterface CommerceDocument {\n /** QUOTE or INVOICE, across the top. */\n kind: string\n /** `Quote #1042`. */\n title: string\n facts: Array<[string, string]>\n /** A short standing, set in a pill under the facts: `Paid October 30, 2026`. */\n stamp: string | null\n parties: Array<{ label: string; lines: string[] }>\n lines: DocumentLine[]\n summary: SummaryRow[]\n sections: Array<{ heading: string; text: string; link?: string }>\n pay: { url: string; label: string; intro: string } | null\n}\n\n/** A date the American way, in the site's time zone: `October 28, 2026`. */\nexport function formatPdfDate(ms: number | null | undefined, timeZone?: string | null): string {\n if (!ms || !Number.isFinite(ms)) return ''\n const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }\n try {\n return new Intl.DateTimeFormat('en-US', { ...options, ...(timeZone ? { timeZone } : {}) }).format(new Date(ms))\n } catch {\n return new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'UTC' }).format(new Date(ms))\n }\n}\n\nconst minus = (text: string) => `-${text}`\n\n/*==========================================\n * THE QUOTE\n *=========================================*/\n\nexport interface QuotePdfInput {\n brand: CommercePdfBrand\n /** The quote as its customer may see it: `buildQuotePageView`. */\n view: QuotePageView\n /** The quote's revision, printed beside its date. */\n revision?: number | null\n /** The pay link, printed on a quote still to be paid; left off when the reader may not have it. */\n payUrl?: string | null\n}\n\nfunction quoteAddressLines(address: QuotePageAddress | null): string[] {\n return invoiceAddressLines(address as never, { phone: false })\n}\n\nfunction quoteDocument(input: QuotePdfInput): CommerceDocument {\n const { view, brand } = input\n const money = (cents: number) => invoiceMoney(cents, view.currency)\n const date = (ms: number | null) => formatPdfDate(ms, brand.timeZone)\n const customer = [view.customerName ?? '', view.company ?? ''].filter(Boolean)\n const billTo = quoteAddressLines(view.billTo)\n const parties: CommerceDocument['parties'] = []\n if (billTo.length) {\n // The bill-to's own name leads; the customer's name and company are added when it does not carry them.\n const named = customer.filter((part) => !billTo.includes(part))\n parties.push({ label: 'Bill to', lines: [...named, ...billTo] })\n } else if (customer.length) {\n parties.push({ label: 'Prepared for', lines: customer })\n }\n const shipTo = quoteAddressLines(view.shipTo)\n if (shipTo.length) parties.push({ label: 'Ship to', lines: shipTo })\n\n const summary: SummaryRow[] = [{ label: 'Subtotal', value: money(view.totals.itemsCents) }]\n if (view.totals.discountCents > 0) {\n summary.push({\n label: view.discountLabel ? `Discount (${view.discountLabel})` : 'Discount',\n value: minus(money(view.totals.discountCents)),\n })\n }\n if (view.totals.shippingCents > 0) summary.push({ label: 'Shipping', value: money(view.totals.shippingCents) })\n if (view.taxAtPayment) summary.push({ label: 'Tax is added at payment', value: '', note: true })\n else if (view.totals.taxCents > 0) summary.push({ label: view.taxLabel || 'Tax', value: money(view.totals.taxCents) })\n else if (view.taxLabel && view.taxLabel !== 'Tax') summary.push({ label: view.taxLabel, value: '', note: true })\n summary.push({ label: view.taxAtPayment ? 'Total before tax' : 'Total', value: money(view.totals.totalCents), emphasis: true })\n\n const stamp =\n view.status === 'paid'\n ? `Paid${view.paidAtMs ? ` ${date(view.paidAtMs)}` : ''}`\n : view.status === 'canceled'\n ? 'Canceled'\n : view.status === 'refunded'\n ? 'Refunded'\n : null\n const facts: Array<[string, string]> = []\n if (view.dateMs) facts.push(['Date', date(view.dateMs)])\n if (input.revision && input.revision > 0) facts.push(['Revision', String(input.revision)])\n\n const sections: CommerceDocument['sections'] = []\n if (view.notes) sections.push({ heading: 'Notes', text: view.notes })\n if (view.terms) sections.push({ heading: 'Terms', text: view.terms })\n\n const payUrl = view.status === 'unpaid' && input.payUrl ? input.payUrl : null\n return {\n kind: 'QUOTE',\n title: view.number,\n facts,\n stamp,\n parties,\n lines: view.lines.map((line) => ({\n name: line.name,\n details: [line.variantLabel ?? '', line.description ?? '', line.sku ? `SKU ${line.sku}` : ''].filter(Boolean),\n quantity: line.quantity,\n unit: line.included ? '' : money(line.unitAmountCents),\n amount: line.included ? 'Included' : money(line.amountCents),\n })),\n summary,\n sections,\n pay: payUrl\n ? {\n url: payUrl,\n label: 'Pay this quote',\n intro: view.approvalRequired\n ? `${view.storeName || 'We'} will ask you to approve the proof first, at this link:`\n : 'Open the quote on our site to pay it securely by card:',\n }\n : null,\n }\n}\n\n/** The quote as a PDF. */\nexport function renderQuotePdf(input: QuotePdfInput): Uint8Array {\n return renderCommerceDocument(input.brand, quoteDocument(input))\n}\n\n/*==========================================\n * THE INVOICE\n *=========================================*/\n\nexport interface OrderInvoicePdfInput {\n brand: CommercePdfBrand\n order: InvoiceOrder\n orderId: string\n /** The store's currency, receipt footer and terms address (`invoiceStoreFrom`). */\n store: OrderInvoiceStore\n /**\n * The order's pay link (AGL-4099), printed on an invoice still to be paid\n * as Pay online with a button and the address; left off when absent.\n */\n payUrl?: string | null\n}\n\nfunction invoiceDocument(input: OrderInvoicePdfInput): CommerceDocument {\n const data = buildOrderInvoiceData(input)\n const money = (cents: number) => invoiceMoney(cents, data.currency)\n const parties: CommerceDocument['parties'] = []\n if (data.billTo.length) parties.push({ label: 'Bill to', lines: data.billTo })\n if (data.shipTo.length) parties.push({ label: 'Ship to', lines: data.shipTo })\n const facts: Array<[string, string]> = []\n if (data.dateMs) facts.push(['Order date', formatPdfDate(data.dateMs, input.brand.timeZone)])\n const sections: CommerceDocument['sections'] = []\n if (data.footer) sections.push({ heading: '', text: data.footer })\n if (data.termsUrl) sections.push({ heading: 'Terms', text: data.termsUrl, link: data.termsUrl })\n const payUrl = input.order.status === 'pending' && input.payUrl ? input.payUrl : null\n return {\n kind: 'INVOICE',\n title: `Invoice ${data.number}`,\n facts,\n stamp: data.paymentStatus,\n parties,\n lines: data.lines.map((line) => ({\n name: line.name,\n details: [line.detail, line.description].filter(Boolean),\n quantity: line.quantity,\n unit: line.included ? '' : money(line.unitCents),\n amount: line.included ? 'Included' : money(line.amountCents),\n })),\n summary: data.summary.map((row) => ({\n label: row.label,\n value: row.minus ? minus(money(row.cents)) : money(row.cents),\n emphasis: row.emphasis,\n })),\n sections,\n pay: payUrl\n ? { url: payUrl, label: 'Pay this invoice', intro: 'Open the invoice on our site to pay it securely by card:' }\n : null,\n }\n}\n\n/** Any order's invoice as a PDF, a paid quote's included. */\nexport function renderOrderInvoicePdf(input: OrderInvoicePdfInput): Uint8Array {\n return renderCommerceDocument(input.brand, invoiceDocument(input))\n}\n\n/*==========================================\n * THE LAYOUT\n *=========================================*/\n\nconst PAGE_MARGIN = { top: 48, right: 48, bottom: 60, left: 48 }\nconst BAND_HEIGHT = 10\nconst LOGO_BOX = { width: 180, height: 50 }\n\nfunction renderCommerceDocument(brand: CommercePdfBrand, document: CommerceDocument): Uint8Array {\n const palette = { ...COMMERCE_PDF_NEUTRAL_COLORS, ...(brand.colors ?? {}) }\n const color = (hex: string, fallback: string): PdfRgb =>\n pdfRgbFromHex(hex) ?? pdfRgbFromHex(fallback) ?? [0, 0, 0]\n const colors = {\n accent: color(palette.accent, COMMERCE_PDF_NEUTRAL_COLORS.accent),\n onAccent: color(palette.onAccent, COMMERCE_PDF_NEUTRAL_COLORS.onAccent),\n accentText: color(palette.accentText, COMMERCE_PDF_NEUTRAL_COLORS.accentText),\n tint: color(palette.tint, COMMERCE_PDF_NEUTRAL_COLORS.tint),\n ink: color(palette.ink, COMMERCE_PDF_NEUTRAL_COLORS.ink),\n muted: color(palette.muted, COMMERCE_PDF_NEUTRAL_COLORS.muted),\n rule: color(palette.rule, COMMERCE_PDF_NEUTRAL_COLORS.rule),\n }\n const businessName = brand.businessName.trim()\n const documentTitle = businessName ? `${document.title} - ${businessName}` : document.title\n\n const writer: PdfWriter = new PdfWriter({\n title: documentTitle,\n margin: PAGE_MARGIN,\n onNewPage: (page) => {\n page.fillRect(0, page.pageHeight - BAND_HEIGHT / 2, page.pageWidth, BAND_HEIGHT / 2, colors.accent)\n const baseline = page.top + 14\n if (businessName) page.textIn(page.left, page.contentWidth, baseline, businessName, { size: 8.5, font: 'bold', color: colors.muted })\n page.textIn(page.left, page.contentWidth, baseline, `${document.title} (continued)`, { size: 8.5, color: colors.muted }, 'right')\n },\n })\n const { left, right } = writer\n\n // The band across the top of the first page.\n writer.fillRect(0, writer.pageHeight - BAND_HEIGHT, writer.pageWidth, BAND_HEIGHT, colors.accent)\n\n // Left: logo, business name, contact.\n const leftWidth = writer.contentWidth * 0.55\n let leftY = writer.top\n if (brand.logo) {\n const drawn = writer.image(brand.logo, { x: left, y: leftY - LOGO_BOX.height, width: LOGO_BOX.width, height: LOGO_BOX.height })\n leftY = drawn.y - 12\n }\n writer.y = leftY\n if (businessName) writer.paragraph(businessName, { width: leftWidth, style: { font: 'bold', size: 14, color: colors.ink }, leading: 1.25, after: 2 })\n for (const line of brand.contactLines) {\n writer.paragraph(line, { width: leftWidth, style: { size: 9, color: colors.muted }, leading: 1.3 })\n }\n leftY = writer.y\n\n // Right: the document's name, number, facts and standing.\n const factsWidth = 210\n const factsX = right - factsWidth\n let rightY = writer.top - 22\n writer.textIn(factsX, factsWidth, rightY, document.kind, { font: 'bold', size: 24, color: colors.accentText }, 'right')\n rightY -= 20\n writer.textIn(factsX, factsWidth, rightY, document.title, { font: 'bold', size: 11, color: colors.ink }, 'right')\n for (const [label, value] of document.facts) {\n rightY -= 14\n const valueWidth = writer.measure(value, { size: 9.5 })\n writer.textIn(factsX, factsWidth, rightY, value, { size: 9.5, color: colors.ink }, 'right')\n writer.textIn(factsX, factsWidth - valueWidth - 8, rightY, label, { size: 9.5, color: colors.muted }, 'right')\n }\n if (document.stamp) {\n rightY -= 24\n const style = { font: 'bold' as const, size: 8.5, color: colors.onAccent }\n const width = writer.measure(document.stamp, style) + 16\n writer.fillRect(right - width, rightY - 5, width, 17, colors.accent)\n writer.textIn(right - width, width, rightY, document.stamp, style, 'center')\n rightY -= 5\n }\n\n writer.y = Math.min(leftY, rightY) - 22\n\n // Who it is for.\n if (document.parties.length) {\n const columnWidth = Math.min(220, writer.contentWidth / document.parties.length)\n const top = writer.y\n let lowest = top\n document.parties.forEach((party, index) => {\n const x = left + index * (columnWidth + 16)\n writer.y = top\n writer.paragraph(party.label.toUpperCase(), { x, width: columnWidth, style: { font: 'bold', size: 8, color: colors.muted }, after: 2 })\n party.lines.forEach((line, lineIndex) => {\n writer.paragraph(line, {\n x,\n width: columnWidth,\n style: { size: 9.5, color: colors.ink, font: lineIndex === 0 ? 'bold' : 'regular' },\n })\n })\n lowest = Math.min(lowest, writer.y)\n })\n writer.y = lowest - 18\n }\n\n // The lines.\n const rows: PdfTableCell[][] = document.lines.map((line) => [\n [\n { text: line.name, style: { font: 'bold', size: 9.5, color: colors.ink } },\n ...line.details.map((detail) => ({ text: detail, style: { size: 8.5, color: colors.muted } })),\n ],\n String(line.quantity),\n line.unit,\n line.amount,\n ])\n writer.table({\n columns: [\n { header: 'Item', width: 0 },\n { header: 'Qty', width: 48, align: 'right' },\n { header: 'Unit price', width: 86, align: 'right' },\n { header: 'Amount', width: 90, align: 'right' },\n ],\n rows,\n headerFill: colors.accent,\n headerStyle: { font: 'bold', size: 8.5, color: colors.onAccent },\n bodyStyle: { size: 9.5, color: colors.ink },\n padding: { x: 8, y: 6 },\n ruleColor: colors.rule,\n })\n\n // The totals on the right; beside them on the left, paying it.\n const summaryWidth = 250\n const summaryX = right - summaryWidth\n const payWidth = writer.contentWidth - summaryWidth - 28\n const rowHeight = 17\n const summaryHeight = document.summary.reduce((sum, row) => sum + (row.emphasis ? rowHeight + 8 : rowHeight), 0)\n const pay = document.pay\n const buttonStyle = { font: 'bold' as const, size: 10.5, color: colors.onAccent }\n const buttonHeight = 26\n const payParts = pay\n ? {\n intro: writer.wrap(pay.intro, payWidth, { size: 9 }),\n url: writer.wrap(pay.url, payWidth, { size: 8 }),\n }\n : null\n const payHeight = payParts ? 18 + payParts.intro.length * 12 + 6 + buttonHeight + 8 + payParts.url.length * 11 : 0\n writer.down(12)\n writer.ensure(Math.max(summaryHeight, payHeight))\n const blockTop = writer.y\n for (const row of document.summary) {\n const height = row.emphasis ? rowHeight + 8 : rowHeight\n if (row.emphasis) writer.fillRect(summaryX, writer.y - height, summaryWidth, height, colors.tint)\n const size = row.emphasis ? 11.5 : 9.5\n const baseline = writer.y - height / 2 - size * 0.35\n const style = row.emphasis\n ? { font: 'bold' as const, size, color: colors.ink }\n : { size, color: row.note ? colors.muted : colors.ink, ...(row.note ? { font: 'italic' as const } : {}) }\n writer.textIn(summaryX + 8, summaryWidth - 16, baseline, row.label, style, 'left')\n if (row.value) writer.textIn(summaryX + 8, summaryWidth - 16, baseline, row.value, style, 'right')\n writer.down(height)\n }\n const summaryBottom = writer.y\n if (pay && payParts) {\n let y = blockTop - 12\n writer.text(left, y, 'Pay online', { font: 'bold', size: 11, color: colors.ink })\n y -= 6\n for (const line of payParts.intro) {\n y -= 12\n writer.text(left, y + 2, line, { size: 9, color: colors.muted })\n }\n y -= 6\n const buttonWidth = writer.measure(pay.label, buttonStyle) + 36\n writer.fillRect(left, y - buttonHeight, buttonWidth, buttonHeight, colors.accent)\n writer.textIn(left, buttonWidth, y - buttonHeight / 2 - 10.5 * 0.35, pay.label, buttonStyle, 'center')\n writer.link(left, y - buttonHeight, buttonWidth, buttonHeight, pay.url)\n y -= buttonHeight + 8\n for (const line of payParts.url) {\n y -= 11\n writer.text(left, y + 2, line, { size: 8, color: colors.accentText })\n writer.link(left, y, writer.measure(line, { size: 8 }), 11, pay.url)\n }\n writer.y = Math.min(summaryBottom, y)\n }\n writer.down(22)\n\n // Notes, terms, the store's footer.\n for (const section of document.sections) {\n if (section.heading) {\n writer.ensure(36)\n writer.paragraph(section.heading, { style: { font: 'bold', size: 10, color: colors.ink }, after: 1 })\n }\n writer.paragraph(section.text, {\n style: { size: 9, color: section.link ? colors.accentText : colors.muted },\n ...(section.link ? { link: section.link } : {}),\n after: 12,\n })\n }\n\n return writer.finish({\n footer: (page, pages) => ({\n left: businessName ? `${businessName} · ${document.title}` : document.title,\n right: `Page ${page} of ${pages}`,\n }),\n footerStyle: { size: 8, color: colors.muted },\n footerRule: colors.rule,\n })\n}\n"],"names":["buildEmailPalette","emailAccent","resolveEmailColor","relativeLuminanceOfRgb","rgbChannelsOfHex","PdfWriter","pdfRgbFromHex","buildOrderInvoiceData","invoiceAddressLines","invoiceMoney","NEUTRAL_PALETTE","base","neutral","path","COMMERCE_PDF_NEUTRAL_COLORS","Object","freeze","accent","onAccent","accentText","tint","ink","muted","rule","contrast","one","other","a","b","la","lb","Math","max","min","commercePdfColorsFromPalette","palette","read","fallback","paper","label","fill","link","formatPdfDate","ms","timeZone","Number","isFinite","options","year","month","day","Intl","DateTimeFormat","format","Date","minus","text","quoteAddressLines","address","phone","quoteDocument","input","view","brand","money","cents","currency","date","customer","customerName","company","filter","Boolean","billTo","parties","length","named","part","includes","push","lines","shipTo","summary","value","totals","itemsCents","discountCents","discountLabel","shippingCents","taxAtPayment","note","taxCents","taxLabel","totalCents","emphasis","stamp","status","paidAtMs","facts","dateMs","revision","String","sections","notes","heading","terms","payUrl","kind","title","number","map","line","name","details","variantLabel","description","sku","quantity","unit","included","unitAmountCents","amount","amountCents","pay","url","intro","approvalRequired","storeName","renderQuotePdf","renderCommerceDocument","invoiceDocument","data","footer","termsUrl","order","paymentStatus","detail","unitCents","row","renderOrderInvoicePdf","PAGE_MARGIN","top","right","bottom","left","BAND_HEIGHT","LOGO_BOX","width","height","document","colors","color","hex","businessName","trim","documentTitle","writer","margin","onNewPage","page","fillRect","pageHeight","pageWidth","baseline","textIn","contentWidth","size","font","leftWidth","leftY","logo","drawn","image","x","y","paragraph","style","leading","after","contactLines","factsWidth","factsX","rightY","valueWidth","measure","columnWidth","lowest","forEach","party","index","toUpperCase","lineIndex","rows","table","columns","header","align","headerFill","headerStyle","bodyStyle","padding","ruleColor","summaryWidth","summaryX","payWidth","rowHeight","summaryHeight","reduce","sum","buttonStyle","buttonHeight","payParts","wrap","payHeight","down","ensure","blockTop","summaryBottom","buttonWidth","section","finish","pages","footerStyle","footerRule"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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 } from '../model';
|
|
18
|
+
/** The catalog key the invoice email is sent and designed under. */
|
|
19
|
+
export declare const INVOICE_EMAIL_KEY = "order-invoice";
|
|
20
|
+
export interface InvoiceEmailContentInput {
|
|
21
|
+
order: HostOrder;
|
|
22
|
+
orderId: string;
|
|
23
|
+
businessName: string;
|
|
24
|
+
currency: string;
|
|
25
|
+
/** The subject the email goes under. */
|
|
26
|
+
subject: string;
|
|
27
|
+
/** The business's message, as typed; may be empty. */
|
|
28
|
+
message: string;
|
|
29
|
+
/** The order's pay link, for an invoice still to be paid; null for a paid one. */
|
|
30
|
+
payUrl: string | null;
|
|
31
|
+
/** The site's time zone, for the dates. */
|
|
32
|
+
timeZone?: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface InvoiceEmailContent {
|
|
35
|
+
/** The catalog email's merge tokens. */
|
|
36
|
+
tokens: Record<string, string>;
|
|
37
|
+
/** The same email as plain text, for a send whose design cannot be rendered. */
|
|
38
|
+
text: string;
|
|
39
|
+
/** The invoice says it is paid: no button. */
|
|
40
|
+
paid: boolean;
|
|
41
|
+
/** The total it shows. */
|
|
42
|
+
totalCents: number;
|
|
43
|
+
}
|
|
44
|
+
/** The invoice email's tokens and its plain-text twin. */
|
|
45
|
+
export declare function invoiceEmailContent(input: InvoiceEmailContentInput): InvoiceEmailContent;
|
|
@@ -0,0 +1,126 @@
|
|
|
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 { formatOrderMoney, formatOrderNumber, invoiceCheckoutCharge, invoiceIsPaid, orderCreatedAtMs, orderPaidAtMs } from "../model/index.js";
|
|
18
|
+
/*==========================================
|
|
19
|
+
* WHAT THE INVOICE EMAIL SAYS (AGL-4099).
|
|
20
|
+
*
|
|
21
|
+
* The `order-invoice` catalog email's tokens, composed per send, curated for
|
|
22
|
+
* the two things an invoice can be:
|
|
23
|
+
*
|
|
24
|
+
* - UNPAID: "Invoice #1042 from Acme Signs", what it is for, the business's
|
|
25
|
+
* own message, the number, date, total and what is due, how to pay, and
|
|
26
|
+
* one button, "Pay $1,250.00", to the order's lasting pay link on the
|
|
27
|
+
* business's site.
|
|
28
|
+
* - PAID: the same heading and details with the date it was paid, "This
|
|
29
|
+
* invoice is paid; keep it for your records", and no button.
|
|
30
|
+
*
|
|
31
|
+
* The invoice itself is attached as a PDF by the sender. The figures are the
|
|
32
|
+
* ones the pay link charges (`invoiceCheckoutCharge`) while it is unpaid, and
|
|
33
|
+
* the order's own once paid, so the email, the page and the PDF agree.
|
|
34
|
+
*
|
|
35
|
+
* Pure: the send route and the sample renders call the same function.
|
|
36
|
+
*=========================================*/ /** The catalog key the invoice email is sent and designed under. */ export const INVOICE_EMAIL_KEY = 'order-invoice';
|
|
37
|
+
/** A date the American way, in the site's time zone: `October 28, 2026`. */ function longDate(ms, timeZone) {
|
|
38
|
+
if (!ms || !Number.isFinite(ms)) return '';
|
|
39
|
+
const options = {
|
|
40
|
+
year: 'numeric',
|
|
41
|
+
month: 'long',
|
|
42
|
+
day: 'numeric'
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
return new Intl.DateTimeFormat('en-US', _extends({}, options, timeZone ? {
|
|
46
|
+
timeZone
|
|
47
|
+
} : {})).format(new Date(ms));
|
|
48
|
+
} catch (unused) {
|
|
49
|
+
return new Intl.DateTimeFormat('en-US', _extends({}, options, {
|
|
50
|
+
timeZone: 'UTC'
|
|
51
|
+
})).format(new Date(ms));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function capitalize(value) {
|
|
55
|
+
return value ? value[0].toUpperCase() + value.slice(1) : value;
|
|
56
|
+
}
|
|
57
|
+
/** `before tax`, `before shipping`, `before shipping and tax`, or nothing. */ function laterParts(shipping, tax) {
|
|
58
|
+
if (shipping && tax) return 'shipping and tax';
|
|
59
|
+
if (shipping) return 'shipping';
|
|
60
|
+
if (tax) return 'tax';
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
/** The invoice email's tokens and its plain-text twin. */ export function invoiceEmailContent(input) {
|
|
64
|
+
var _input_payUrl, _ref, _ref1, _order_refundedCents;
|
|
65
|
+
var _order_totals;
|
|
66
|
+
const { order, orderId, timeZone } = input;
|
|
67
|
+
const name = input.businessName.trim();
|
|
68
|
+
const business = name || 'the business';
|
|
69
|
+
const number = formatOrderNumber(order, orderId);
|
|
70
|
+
const paid = invoiceIsPaid(order);
|
|
71
|
+
const payUrl = paid ? '' : String((_input_payUrl = input.payUrl) != null ? _input_payUrl : '').trim();
|
|
72
|
+
const forWhat = order.quote ? 'your quote' : 'your order';
|
|
73
|
+
const quoteSection = order.quote;
|
|
74
|
+
const dateMs = orderCreatedAtMs(order) || Number((_ref = quoteSection == null ? void 0 : quoteSection.updatedAtMs) != null ? _ref : quoteSection == null ? void 0 : quoteSection.createdAtMs) || 0;
|
|
75
|
+
const date = longDate(dateMs, timeZone);
|
|
76
|
+
// What it shows: what the link charges while it is unpaid, the order's own figures once paid.
|
|
77
|
+
const charge = paid ? null : invoiceCheckoutCharge(order, orderId);
|
|
78
|
+
const totalCents = charge ? charge.totalCents : Math.max(0, Math.round(Number((_ref1 = (_order_totals = order.totals) == null ? void 0 : _order_totals.totalCents) != null ? _ref1 : 0)));
|
|
79
|
+
const money = formatOrderMoney(totalCents, input.currency);
|
|
80
|
+
const later = charge ? laterParts(charge.shippingOptions.length > 0, charge.taxAtPayment) : '';
|
|
81
|
+
const total = later ? `${money} before ${later}` : money;
|
|
82
|
+
const refundedCents = Math.max(0, Math.round(Number((_order_refundedCents = order.refundedCents) != null ? _order_refundedCents : 0)));
|
|
83
|
+
const paidOn = longDate(orderPaidAtMs(order), timeZone);
|
|
84
|
+
const status = paid ? `${paidOn ? `Paid on ${paidOn}` : 'Paid'}` + (refundedCents > 0 ? `, ${formatOrderMoney(refundedCents, input.currency)} refunded` : '') : `Unpaid, ${money}${later ? ` plus ${later}` : ''} due`;
|
|
85
|
+
const heading = name ? `Invoice ${number} from ${name}` : `Invoice ${number}`;
|
|
86
|
+
const intro = paid ? `Here is the invoice for ${forWhat} with ${business}` + (paidOn ? `, paid on ${paidOn}` : ', which is paid') + '. It is attached as a PDF for your records.' : `${capitalize(business)} sent you an invoice for ${forWhat}. The details are below, and the invoice is attached as a PDF.`;
|
|
87
|
+
const nextStep = paid ? 'This invoice is paid; keep it for your records.' : payUrl ? 'Use the button below to pay it securely by card.' + (later ? ` ${capitalize(later)} ${later === 'shipping and tax' ? 'are' : 'is'} added when you pay.` : '') + ' The button keeps working until the invoice is paid.' : `Reply to this email to arrange payment with ${business}.`;
|
|
88
|
+
const buttonLabel = payUrl ? `Pay ${money}${later ? ` plus ${later}` : ''}` : '';
|
|
89
|
+
const message = input.message.trim();
|
|
90
|
+
const details = [
|
|
91
|
+
`Invoice: ${number}`,
|
|
92
|
+
date ? `Date: ${date}` : '',
|
|
93
|
+
`Total: ${total}`,
|
|
94
|
+
`Status: ${status}`
|
|
95
|
+
].filter(Boolean).join('\n');
|
|
96
|
+
const tokens = {
|
|
97
|
+
'invoice.subject': input.subject,
|
|
98
|
+
'invoice.number': number,
|
|
99
|
+
'invoice.date': date,
|
|
100
|
+
'invoice.total': total,
|
|
101
|
+
'invoice.status': status,
|
|
102
|
+
'invoice.heading': heading,
|
|
103
|
+
'invoice.intro': intro,
|
|
104
|
+
'invoice.message': message,
|
|
105
|
+
'invoice.nextStep': nextStep,
|
|
106
|
+
'invoice.buttonLabel': buttonLabel,
|
|
107
|
+
'invoice.url': payUrl
|
|
108
|
+
};
|
|
109
|
+
const text = [
|
|
110
|
+
heading,
|
|
111
|
+
intro,
|
|
112
|
+
message,
|
|
113
|
+
details,
|
|
114
|
+
nextStep,
|
|
115
|
+
buttonLabel ? `${buttonLabel}: ${payUrl}` : '',
|
|
116
|
+
`Reply to this email to reach ${business}.`
|
|
117
|
+
].filter(Boolean).join('\n\n');
|
|
118
|
+
return {
|
|
119
|
+
tokens,
|
|
120
|
+
text,
|
|
121
|
+
paid,
|
|
122
|
+
totalCents
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=invoice-email-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/utils/invoice-email-content.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n formatOrderMoney,\n formatOrderNumber,\n invoiceCheckoutCharge,\n invoiceIsPaid,\n orderCreatedAtMs,\n orderPaidAtMs,\n type HostOrder,\n} from '../model'\n\n/*==========================================\n * WHAT THE INVOICE EMAIL SAYS (AGL-4099).\n *\n * The `order-invoice` catalog email's tokens, composed per send, curated for\n * the two things an invoice can be:\n *\n * - UNPAID: \"Invoice #1042 from Acme Signs\", what it is for, the business's\n * own message, the number, date, total and what is due, how to pay, and\n * one button, \"Pay $1,250.00\", to the order's lasting pay link on the\n * business's site.\n * - PAID: the same heading and details with the date it was paid, \"This\n * invoice is paid; keep it for your records\", and no button.\n *\n * The invoice itself is attached as a PDF by the sender. The figures are the\n * ones the pay link charges (`invoiceCheckoutCharge`) while it is unpaid, and\n * the order's own once paid, so the email, the page and the PDF agree.\n *\n * Pure: the send route and the sample renders call the same function.\n *=========================================*/\n\n/** The catalog key the invoice email is sent and designed under. */\nexport const INVOICE_EMAIL_KEY = 'order-invoice'\n\nexport interface InvoiceEmailContentInput {\n order: HostOrder\n orderId: string\n businessName: string\n currency: string\n /** The subject the email goes under. */\n subject: string\n /** The business's message, as typed; may be empty. */\n message: string\n /** The order's pay link, for an invoice still to be paid; null for a paid one. */\n payUrl: string | null\n /** The site's time zone, for the dates. */\n timeZone?: string | null\n}\n\nexport interface InvoiceEmailContent {\n /** The catalog email's merge tokens. */\n tokens: Record<string, string>\n /** The same email as plain text, for a send whose design cannot be rendered. */\n text: string\n /** The invoice says it is paid: no button. */\n paid: boolean\n /** The total it shows. */\n totalCents: number\n}\n\n/** A date the American way, in the site's time zone: `October 28, 2026`. */\nfunction longDate(ms: number | null | undefined, timeZone?: string | null): string {\n if (!ms || !Number.isFinite(ms)) return ''\n const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }\n try {\n return new Intl.DateTimeFormat('en-US', { ...options, ...(timeZone ? { timeZone } : {}) }).format(new Date(ms))\n } catch {\n return new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'UTC' }).format(new Date(ms))\n }\n}\n\nfunction capitalize(value: string): string {\n return value ? value[0].toUpperCase() + value.slice(1) : value\n}\n\n/** `before tax`, `before shipping`, `before shipping and tax`, or nothing. */\nfunction laterParts(shipping: boolean, tax: boolean): string {\n if (shipping && tax) return 'shipping and tax'\n if (shipping) return 'shipping'\n if (tax) return 'tax'\n return ''\n}\n\n/** The invoice email's tokens and its plain-text twin. */\nexport function invoiceEmailContent(input: InvoiceEmailContentInput): InvoiceEmailContent {\n const { order, orderId, timeZone } = input\n const name = input.businessName.trim()\n const business = name || 'the business'\n const number = formatOrderNumber(order, orderId)\n const paid = invoiceIsPaid(order)\n const payUrl = paid ? '' : String(input.payUrl ?? '').trim()\n const forWhat = order.quote ? 'your quote' : 'your order'\n const quoteSection = order.quote\n const dateMs =\n orderCreatedAtMs(order as never) || Number(quoteSection?.updatedAtMs ?? quoteSection?.createdAtMs) || 0\n const date = longDate(dateMs, timeZone)\n\n // What it shows: what the link charges while it is unpaid, the order's own figures once paid.\n const charge = paid ? null : invoiceCheckoutCharge(order, orderId)\n const totalCents = charge ? charge.totalCents : Math.max(0, Math.round(Number(order.totals?.totalCents ?? 0)))\n const money = formatOrderMoney(totalCents, input.currency)\n const later = charge ? laterParts(charge.shippingOptions.length > 0, charge.taxAtPayment) : ''\n const total = later ? `${money} before ${later}` : money\n const refundedCents = Math.max(0, Math.round(Number(order.refundedCents ?? 0)))\n const paidOn = longDate(orderPaidAtMs(order), timeZone)\n const status = paid\n ? `${paidOn ? `Paid on ${paidOn}` : 'Paid'}` +\n (refundedCents > 0 ? `, ${formatOrderMoney(refundedCents, input.currency)} refunded` : '')\n : `Unpaid, ${money}${later ? ` plus ${later}` : ''} due`\n\n const heading = name ? `Invoice ${number} from ${name}` : `Invoice ${number}`\n const intro = paid\n ? `Here is the invoice for ${forWhat} with ${business}` +\n (paidOn ? `, paid on ${paidOn}` : ', which is paid') +\n '. It is attached as a PDF for your records.'\n : `${capitalize(business)} sent you an invoice for ${forWhat}. The details are below, and the invoice is attached as a PDF.`\n const nextStep = paid\n ? 'This invoice is paid; keep it for your records.'\n : payUrl\n ? 'Use the button below to pay it securely by card.' +\n (later ? ` ${capitalize(later)} ${later === 'shipping and tax' ? 'are' : 'is'} added when you pay.` : '') +\n ' The button keeps working until the invoice is paid.'\n : `Reply to this email to arrange payment with ${business}.`\n const buttonLabel = payUrl ? `Pay ${money}${later ? ` plus ${later}` : ''}` : ''\n const message = input.message.trim()\n const details = [`Invoice: ${number}`, date ? `Date: ${date}` : '', `Total: ${total}`, `Status: ${status}`]\n .filter(Boolean)\n .join('\\n')\n\n const tokens: Record<string, string> = {\n 'invoice.subject': input.subject,\n 'invoice.number': number,\n 'invoice.date': date,\n 'invoice.total': total,\n 'invoice.status': status,\n 'invoice.heading': heading,\n 'invoice.intro': intro,\n 'invoice.message': message,\n 'invoice.nextStep': nextStep,\n 'invoice.buttonLabel': buttonLabel,\n 'invoice.url': payUrl,\n }\n\n const text = [\n heading,\n intro,\n message,\n details,\n nextStep,\n buttonLabel ? `${buttonLabel}: ${payUrl}` : '',\n `Reply to this email to reach ${business}.`,\n ]\n .filter(Boolean)\n .join('\\n\\n')\n\n return { tokens, text, paid, totalCents }\n}\n"],"names":["formatOrderMoney","formatOrderNumber","invoiceCheckoutCharge","invoiceIsPaid","orderCreatedAtMs","orderPaidAtMs","INVOICE_EMAIL_KEY","longDate","ms","timeZone","Number","isFinite","options","year","month","day","Intl","DateTimeFormat","format","Date","capitalize","value","toUpperCase","slice","laterParts","shipping","tax","invoiceEmailContent","input","order","orderId","name","businessName","trim","business","number","paid","payUrl","String","forWhat","quote","quoteSection","dateMs","updatedAtMs","createdAtMs","date","charge","totalCents","Math","max","round","totals","money","currency","later","shippingOptions","length","taxAtPayment","total","refundedCents","paidOn","status","heading","intro","nextStep","buttonLabel","message","details","filter","Boolean","join","tokens","subject","text"],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type HostOrder } from '../model/commerce-orders';
|
|
17
|
+
import { type HostOrder, type OrderAddress } from '../model/commerce-orders';
|
|
18
18
|
/**
|
|
19
19
|
* The printable invoice (AGL-3611), beside the packing slip: the order as a
|
|
20
20
|
* bill — who sold it, to whom, each line with its unit price, the totals,
|
|
@@ -42,19 +42,68 @@ export interface OrderInvoiceStore {
|
|
|
42
42
|
* the order dialog's Invoice button and `commerce/order-print` read them.
|
|
43
43
|
*/
|
|
44
44
|
export declare function invoiceStoreFrom(host: Record<string, unknown> | undefined | null, store: Record<string, unknown> | undefined | null): OrderInvoiceStore;
|
|
45
|
-
type InvoiceOrder = Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'totals' | 'refundedCents' | 'customerName' | 'customerEmail' | 'shippingAddress' | 'billingAddress'> & {
|
|
45
|
+
export type InvoiceOrder = Pick<HostOrder, 'number' | 'status' | 'lineItems' | 'totals' | 'refundedCents' | 'customerName' | 'customerEmail' | 'shippingAddress' | 'billingAddress'> & Partial<Pick<HostOrder, 'quote'>> & {
|
|
46
46
|
createdAtMs?: number;
|
|
47
47
|
currency?: string;
|
|
48
48
|
amountCents?: number;
|
|
49
49
|
};
|
|
50
50
|
/** Money in the order's currency, integer cents in. */
|
|
51
51
|
export declare function invoiceMoney(cents: number, currency: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* An address as lines, the American way: name, street, `City, ST 12345`,
|
|
54
|
+
* the country unless it is the United States, then the phone when the
|
|
55
|
+
* address carries one and it is wanted.
|
|
56
|
+
*/
|
|
57
|
+
export declare function invoiceAddressLines(address: Partial<OrderAddress> | undefined | null, options?: {
|
|
58
|
+
phone?: boolean;
|
|
59
|
+
}): string[];
|
|
52
60
|
/** What the payment line says. */
|
|
53
61
|
export declare function invoicePaymentStatus(order: Pick<InvoiceOrder, 'status' | 'refundedCents' | 'totals' | 'amountCents'>): string;
|
|
62
|
+
/** One line of an invoice, as both the printed page and the PDF set it. */
|
|
63
|
+
export interface InvoiceLine {
|
|
64
|
+
name: string;
|
|
65
|
+
/** The option chosen and the SKU, as one line under the name. */
|
|
66
|
+
detail: string;
|
|
67
|
+
/** A typed line's own description. */
|
|
68
|
+
description: string;
|
|
69
|
+
quantity: number;
|
|
70
|
+
unitCents: number;
|
|
71
|
+
amountCents: number;
|
|
72
|
+
/** Included at no charge. */
|
|
73
|
+
included: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** One row of an invoice's summary: a figure, or a deduction printed with a minus. */
|
|
76
|
+
export interface InvoiceSummaryRow {
|
|
77
|
+
label: string;
|
|
78
|
+
cents: number;
|
|
79
|
+
minus?: boolean;
|
|
80
|
+
/** The row the summary leads to: the total, or what was paid after refunds. */
|
|
81
|
+
emphasis?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/** An invoice's content, worked out once for the printed page and the PDF. */
|
|
84
|
+
export interface OrderInvoiceData {
|
|
85
|
+
/** `#1042`. */
|
|
86
|
+
number: string;
|
|
87
|
+
/** When the order was placed; 0 when it does not say. */
|
|
88
|
+
dateMs: number;
|
|
89
|
+
currency: string;
|
|
90
|
+
paymentStatus: string;
|
|
91
|
+
billTo: string[];
|
|
92
|
+
shipTo: string[];
|
|
93
|
+
lines: InvoiceLine[];
|
|
94
|
+
summary: InvoiceSummaryRow[];
|
|
95
|
+
footer: string;
|
|
96
|
+
termsUrl: string;
|
|
97
|
+
}
|
|
98
|
+
/** What an invoice says, from the order and its store. */
|
|
99
|
+
export declare function buildOrderInvoiceData(input: {
|
|
100
|
+
order: InvoiceOrder;
|
|
101
|
+
orderId: string;
|
|
102
|
+
store: OrderInvoiceStore;
|
|
103
|
+
}): OrderInvoiceData;
|
|
54
104
|
/** The invoice's markup: a full document body, every value escaped. */
|
|
55
105
|
export declare function buildOrderInvoiceHtml(input: {
|
|
56
106
|
order: InvoiceOrder;
|
|
57
107
|
orderId: string;
|
|
58
108
|
store: OrderInvoiceStore;
|
|
59
109
|
}): string;
|
|
60
|
-
export {};
|
|
@@ -40,21 +40,31 @@ import { escapeHtml } from "./escape-html.js";
|
|
|
40
40
|
return `${amount.toFixed(2)} ${String(currency || '').toUpperCase()}`.trim();
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
/** The United States, however an address names it: left off, as a US envelope does. */ const US_COUNTRY = /^(us|usa|u\.s\.a?\.?|united states( of america)?)$/i;
|
|
44
|
+
/**
|
|
45
|
+
* An address as lines, the American way: name, street, `City, ST 12345`,
|
|
46
|
+
* the country unless it is the United States, then the phone when the
|
|
47
|
+
* address carries one and it is wanted.
|
|
48
|
+
*/ export function invoiceAddressLines(address, options = {}) {
|
|
44
49
|
if (!address) return [];
|
|
50
|
+
const clean = (part)=>String(part != null ? part : '').trim();
|
|
51
|
+
const region = [
|
|
52
|
+
clean(address.state),
|
|
53
|
+
clean(address.postalCode)
|
|
54
|
+
].filter(Boolean).join(' ');
|
|
45
55
|
return [
|
|
46
|
-
address.name,
|
|
47
|
-
address.line1,
|
|
48
|
-
address.line2,
|
|
56
|
+
clean(address.name),
|
|
57
|
+
clean(address.line1),
|
|
58
|
+
clean(address.line2),
|
|
49
59
|
[
|
|
50
|
-
address.city,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
address.
|
|
55
|
-
|
|
56
|
-
].map((part)=>String(part != null ? part : '').trim()).filter(Boolean);
|
|
60
|
+
clean(address.city),
|
|
61
|
+
region
|
|
62
|
+
].filter(Boolean).join(', '),
|
|
63
|
+
US_COUNTRY.test(clean(address.country)) ? '' : clean(address.country),
|
|
64
|
+
options.phone === false ? '' : clean(address.phone)
|
|
65
|
+
].filter(Boolean);
|
|
57
66
|
}
|
|
67
|
+
const addressLines = (address)=>invoiceAddressLines(address);
|
|
58
68
|
/** What the payment line says. */ export function invoicePaymentStatus(order) {
|
|
59
69
|
var _ref, _ref1, _order_refundedCents, _order_status;
|
|
60
70
|
var _order_totals;
|
|
@@ -67,75 +77,125 @@ function addressLines(address) {
|
|
|
67
77
|
if (refunded > 0) return 'Paid, partially refunded';
|
|
68
78
|
return 'Paid';
|
|
69
79
|
}
|
|
70
|
-
/**
|
|
71
|
-
var _ref, _order_lineItems, _ref1, _ref2, _order_refundedCents, _order_customerName, _order_customerEmail,
|
|
80
|
+
/** What an invoice says, from the order and its store. */ export function buildOrderInvoiceData(input) {
|
|
81
|
+
var _ref, _order_lineItems, _ref1, _ref2, _order_refundedCents, _order_lineItems1, _ref3, _ref4, _ref5, _ref6, _ref7, _order_billingAddress, _ref8, _ref9, _order_customerName, _ref10, _order_customerEmail, _ref11, _ref12, _order_shippingAddress, _store_footer, _store_termsUrl;
|
|
82
|
+
var _quote_customer, _quote_customer1, _quote_customer2;
|
|
72
83
|
const { order, orderId, store } = input;
|
|
73
84
|
const currency = String(order.currency || store.currency || 'USD').toUpperCase();
|
|
74
|
-
const money = (cents)=>escapeHtml(invoiceMoney(cents, currency));
|
|
75
|
-
const number = formatOrderNumber(order, orderId);
|
|
76
|
-
const createdAt = orderCreatedAtMs(order);
|
|
77
85
|
const totals = order.totals;
|
|
78
86
|
const itemsCents = (_ref = totals == null ? void 0 : totals.itemsCents) != null ? _ref : ((_order_lineItems = order.lineItems) != null ? _order_lineItems : []).reduce((sum, line)=>sum + Number(line.unitAmountCents || 0) * Number(line.quantity || 0), 0);
|
|
79
87
|
const totalCents = Number((_ref1 = (_ref2 = totals == null ? void 0 : totals.totalCents) != null ? _ref2 : order.amountCents) != null ? _ref1 : itemsCents);
|
|
80
88
|
const refundedCents = Math.max(0, Number((_order_refundedCents = order.refundedCents) != null ? _order_refundedCents : 0));
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
(_order_customerName = order.customerName) != null ? _order_customerName : ''
|
|
84
|
-
].filter(Boolean),
|
|
85
|
-
(_order_customerEmail = order.customerEmail) != null ? _order_customerEmail : ''
|
|
86
|
-
].filter(Boolean);
|
|
87
|
-
const shipTo = addressLines(order.shippingAddress);
|
|
88
|
-
const rows = ((_order_lineItems1 = order.lineItems) != null ? _order_lineItems1 : []).map((line)=>{
|
|
89
|
+
const lines = ((_order_lineItems1 = order.lineItems) != null ? _order_lineItems1 : []).map((line)=>{
|
|
90
|
+
var _line_name, _line_description;
|
|
89
91
|
const quantity = Number(line.quantity) || 0;
|
|
90
92
|
const unit = Number(line.unitAmountCents) || 0;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
return {
|
|
94
|
+
name: String((_line_name = line.name) != null ? _line_name : ''),
|
|
95
|
+
detail: [
|
|
96
|
+
line.variantLabel ? String(line.variantLabel) : '',
|
|
97
|
+
line.sku ? `SKU ${line.sku}` : ''
|
|
98
|
+
].filter(Boolean).join(' · '),
|
|
99
|
+
description: String((_line_description = line.description) != null ? _line_description : '').trim(),
|
|
100
|
+
quantity,
|
|
101
|
+
unitCents: unit,
|
|
102
|
+
amountCents: unit * quantity,
|
|
103
|
+
included: line.included === true
|
|
104
|
+
};
|
|
105
|
+
});
|
|
97
106
|
const summary = [
|
|
98
|
-
|
|
99
|
-
'Subtotal',
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
{
|
|
108
|
+
label: 'Subtotal',
|
|
109
|
+
cents: itemsCents
|
|
110
|
+
}
|
|
102
111
|
];
|
|
103
|
-
if (Number((_ref3 = totals == null ? void 0 : totals.discountCents) != null ? _ref3 : 0) > 0) summary.push(
|
|
104
|
-
'Discount',
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
if (Number((_ref3 = totals == null ? void 0 : totals.discountCents) != null ? _ref3 : 0) > 0) summary.push({
|
|
113
|
+
label: 'Discount',
|
|
114
|
+
cents: Number(totals == null ? void 0 : totals.discountCents),
|
|
115
|
+
minus: true
|
|
116
|
+
});
|
|
117
|
+
if (Number((_ref4 = totals == null ? void 0 : totals.shippingCents) != null ? _ref4 : 0) > 0) summary.push({
|
|
118
|
+
label: 'Shipping',
|
|
119
|
+
cents: Number(totals == null ? void 0 : totals.shippingCents)
|
|
120
|
+
});
|
|
121
|
+
if (Number((_ref5 = totals == null ? void 0 : totals.taxCents) != null ? _ref5 : 0) > 0) summary.push({
|
|
122
|
+
label: 'Tax',
|
|
123
|
+
cents: Number(totals == null ? void 0 : totals.taxCents)
|
|
124
|
+
});
|
|
125
|
+
summary.push({
|
|
126
|
+
label: 'Total',
|
|
127
|
+
cents: totalCents,
|
|
128
|
+
emphasis: true
|
|
129
|
+
});
|
|
130
|
+
if (Number((_ref6 = totals == null ? void 0 : totals.tipCents) != null ? _ref6 : 0) > 0) summary.push({
|
|
131
|
+
label: 'Tip',
|
|
132
|
+
cents: Number(totals == null ? void 0 : totals.tipCents)
|
|
133
|
+
});
|
|
123
134
|
if (refundedCents > 0) {
|
|
124
|
-
summary.push(
|
|
125
|
-
'Refunded',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
summary.push({
|
|
136
|
+
label: 'Refunded',
|
|
137
|
+
cents: refundedCents,
|
|
138
|
+
minus: true
|
|
139
|
+
});
|
|
140
|
+
summary.push({
|
|
141
|
+
label: 'Net paid',
|
|
142
|
+
cents: Math.max(0, totalCents - refundedCents),
|
|
143
|
+
emphasis: true
|
|
144
|
+
});
|
|
132
145
|
}
|
|
146
|
+
// An unpaid quote keeps its customer, its addresses and its date in its
|
|
147
|
+
// quote section until it is paid (AGL-4093); its invoice (AGL-4099) reads
|
|
148
|
+
// them there.
|
|
149
|
+
const quote = order.quote;
|
|
150
|
+
const billingAddress = (_ref7 = (_order_billingAddress = order.billingAddress) != null ? _order_billingAddress : quote == null ? void 0 : quote.billTo) != null ? _ref7 : null;
|
|
151
|
+
const customerName = (_ref8 = (_ref9 = (_order_customerName = order.customerName) != null ? _order_customerName : quote == null ? void 0 : (_quote_customer = quote.customer) == null ? void 0 : _quote_customer.name) != null ? _ref9 : quote == null ? void 0 : (_quote_customer1 = quote.customer) == null ? void 0 : _quote_customer1.company) != null ? _ref8 : '';
|
|
152
|
+
const customerEmail = (_ref10 = (_order_customerEmail = order.customerEmail) != null ? _order_customerEmail : quote == null ? void 0 : (_quote_customer2 = quote.customer) == null ? void 0 : _quote_customer2.email) != null ? _ref10 : '';
|
|
153
|
+
return {
|
|
154
|
+
number: formatOrderNumber(order, orderId),
|
|
155
|
+
dateMs: orderCreatedAtMs(order) || Number((_ref11 = quote == null ? void 0 : quote.updatedAtMs) != null ? _ref11 : quote == null ? void 0 : quote.createdAtMs) || 0,
|
|
156
|
+
currency,
|
|
157
|
+
paymentStatus: invoicePaymentStatus(order),
|
|
158
|
+
billTo: [
|
|
159
|
+
...billingAddress ? addressLines(billingAddress) : [
|
|
160
|
+
customerName
|
|
161
|
+
].filter(Boolean),
|
|
162
|
+
customerEmail
|
|
163
|
+
].filter(Boolean),
|
|
164
|
+
shipTo: addressLines((_ref12 = (_order_shippingAddress = order.shippingAddress) != null ? _order_shippingAddress : quote == null ? void 0 : quote.shipTo) != null ? _ref12 : null),
|
|
165
|
+
lines,
|
|
166
|
+
summary,
|
|
167
|
+
footer: String((_store_footer = store.footer) != null ? _store_footer : '').trim(),
|
|
168
|
+
termsUrl: String((_store_termsUrl = store.termsUrl) != null ? _store_termsUrl : '').trim()
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** The invoice's markup: a full document body, every value escaped. */ export function buildOrderInvoiceHtml(input) {
|
|
172
|
+
const data = buildOrderInvoiceData(input);
|
|
173
|
+
const { store } = input;
|
|
174
|
+
const money = (cents)=>escapeHtml(invoiceMoney(cents, data.currency));
|
|
175
|
+
const number = data.number;
|
|
176
|
+
const rows = data.lines.map((line)=>{
|
|
177
|
+
const detail = [
|
|
178
|
+
line.detail,
|
|
179
|
+
line.description
|
|
180
|
+
].filter(Boolean).map((part)=>escapeHtml(part)).join('<br/>');
|
|
181
|
+
return `<tr><td>${escapeHtml(line.name)}${detail ? `<br/><small>${detail}</small>` : ''}</td>` + `<td align="right">${escapeHtml(line.quantity)}</td>` + `<td align="right">${line.included ? '' : money(line.unitCents)}</td>` + `<td align="right">${line.included ? 'Included' : money(line.amountCents)}</td></tr>`;
|
|
182
|
+
}).join('');
|
|
183
|
+
const summary = data.summary.map(({ label, cents, minus, emphasis })=>{
|
|
184
|
+
const figure = `${minus ? '−' : ''}${money(cents)}`;
|
|
185
|
+
return emphasis ? [
|
|
186
|
+
`<b>${label}</b>`,
|
|
187
|
+
`<b>${figure}</b>`
|
|
188
|
+
] : [
|
|
189
|
+
label,
|
|
190
|
+
figure
|
|
191
|
+
];
|
|
192
|
+
});
|
|
133
193
|
const block = (title, lines)=>lines.length ? `<td valign="top"><b>${title}</b><br/>${lines.map((line)=>escapeHtml(line)).join('<br/>')}</td>` : '';
|
|
134
|
-
return `<title>${escapeHtml(`Invoice ${number}`)}</title>` + `<h2>${escapeHtml(store.name || 'Invoice')}</h2>` + `<h3>${escapeHtml(`Invoice ${number}`)}</h3>` + `<p>${
|
|
194
|
+
return `<title>${escapeHtml(`Invoice ${number}`)}</title>` + `<h2>${escapeHtml(store.name || 'Invoice')}</h2>` + `<h3>${escapeHtml(`Invoice ${number}`)}</h3>` + `<p>${data.dateMs ? `Date: ${escapeHtml(new Date(data.dateMs).toLocaleDateString('en-US', {
|
|
135
195
|
year: 'numeric',
|
|
136
196
|
month: 'long',
|
|
137
197
|
day: 'numeric'
|
|
138
|
-
}))}<br/>` : ''}` + `Payment: ${escapeHtml(
|
|
198
|
+
}))}<br/>` : ''}` + `Payment: ${escapeHtml(data.paymentStatus)}<br/>Currency: ${escapeHtml(data.currency)}</p>` + `<table width="100%" cellpadding="6"><tr>${block('Bill to', data.billTo)}${block('Ship to', data.shipTo)}</tr></table>` + `<table width="100%" cellpadding="6" border="1" cellspacing="0">` + `<tr><th align="left">Item</th><th align="right">Qty</th><th align="right">Unit price</th><th align="right">Amount</th></tr>` + rows + `</table>` + `<table width="100%" cellpadding="4">${summary.map(([label, value])=>`<tr><td align="right" width="80%">${label}</td><td align="right">${value}</td></tr>`).join('')}</table>` + (store.footer ? `<p>${escapeHtml(store.footer)}</p>` : '') + (store.termsUrl ? `<p><small>Terms: ${escapeHtml(store.termsUrl)}</small></p>` : '');
|
|
139
199
|
}
|
|
140
200
|
|
|
141
201
|
//# sourceMappingURL=order-invoice.js.map
|