@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.
Files changed (129) hide show
  1. package/README.md +2 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +29 -2
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
  6. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
  7. package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
  8. package/src/lib/components/console/commerce-pdf-download.js +53 -0
  9. package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
  10. package/src/lib/components/console/host-orders-card.component.js +5 -1
  11. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  12. package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
  13. package/src/lib/components/console/host-quotes-card.component.js +263 -0
  14. package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
  15. package/src/lib/components/console/order-invoice.component.d.ts +6 -4
  16. package/src/lib/components/console/order-invoice.component.js +67 -11
  17. package/src/lib/components/console/order-invoice.component.js.map +1 -1
  18. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
  19. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  20. package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
  21. package/src/lib/components/console/quote-editor-dialog.component.js +1317 -0
  22. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
  23. package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
  24. package/src/lib/components/console/quote-pay-link.component.js +213 -0
  25. package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
  26. package/src/lib/components/console/quote-proofs.component.d.ts +37 -0
  27. package/src/lib/components/console/quote-proofs.component.js +626 -0
  28. package/src/lib/components/console/quote-proofs.component.js.map +1 -0
  29. package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
  30. package/src/lib/components/console/quote-settings-card.component.js +121 -0
  31. package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
  32. package/src/lib/components/console/tax-settings-card.component.js +4 -1
  33. package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
  34. package/src/lib/components/quote-page-body.d.ts +33 -0
  35. package/src/lib/components/quote-page-body.js +724 -0
  36. package/src/lib/components/quote-page-body.js.map +1 -0
  37. package/src/lib/components/quote-page.d.ts +29 -0
  38. package/src/lib/components/quote-page.js +96 -0
  39. package/src/lib/components/quote-page.js.map +1 -0
  40. package/src/lib/components/quote-proof-panel.d.ts +42 -0
  41. package/src/lib/components/quote-proof-panel.js +436 -0
  42. package/src/lib/components/quote-proof-panel.js.map +1 -0
  43. package/src/lib/components/storefront-payment-element.d.ts +7 -1
  44. package/src/lib/components/storefront-payment-element.js +8 -3
  45. package/src/lib/components/storefront-payment-element.js.map +1 -1
  46. package/src/lib/constants/orders-list-query.js +6 -1
  47. package/src/lib/constants/orders-list-query.js.map +1 -1
  48. package/src/lib/constants/quote-page.d.ts +30 -0
  49. package/src/lib/constants/quote-page.js +35 -0
  50. package/src/lib/constants/quote-page.js.map +1 -0
  51. package/src/lib/model/commerce-orders.d.ts +25 -2
  52. package/src/lib/model/commerce-orders.js +10 -1
  53. package/src/lib/model/commerce-orders.js.map +1 -1
  54. package/src/lib/model/commerce-quote-pay.d.ts +256 -0
  55. package/src/lib/model/commerce-quote-pay.js +426 -0
  56. package/src/lib/model/commerce-quote-pay.js.map +1 -0
  57. package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
  58. package/src/lib/model/commerce-quote-proofs.js +348 -0
  59. package/src/lib/model/commerce-quote-proofs.js.map +1 -0
  60. package/src/lib/model/commerce-quotes.d.ts +335 -0
  61. package/src/lib/model/commerce-quotes.js +522 -0
  62. package/src/lib/model/commerce-quotes.js.map +1 -0
  63. package/src/lib/model/index.d.ts +3 -0
  64. package/src/lib/model/index.js +3 -0
  65. package/src/lib/model/index.js.map +1 -1
  66. package/src/lib/model/plugin-permissions.d.ts +1 -1
  67. package/src/lib/model/plugin-permissions.js +20 -0
  68. package/src/lib/model/plugin-permissions.js.map +1 -1
  69. package/src/lib/server/api-v1/orders-and-products.js +6 -2
  70. package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
  71. package/src/lib/server/billing-webhook.js +127 -52
  72. package/src/lib/server/billing-webhook.js.map +1 -1
  73. package/src/lib/server/checkout-session-expiry.d.ts +36 -0
  74. package/src/lib/server/checkout-session-expiry.js +63 -0
  75. package/src/lib/server/checkout-session-expiry.js.map +1 -0
  76. package/src/lib/server/commerce-pdf.d.ts +100 -0
  77. package/src/lib/server/commerce-pdf.js +283 -0
  78. package/src/lib/server/commerce-pdf.js.map +1 -0
  79. package/src/lib/server/draft-order.js +16 -8
  80. package/src/lib/server/draft-order.js.map +1 -1
  81. package/src/lib/server/order-print.d.ts +6 -0
  82. package/src/lib/server/order-print.js +22 -5
  83. package/src/lib/server/order-print.js.map +1 -1
  84. package/src/lib/server/pdf-response.d.ts +31 -0
  85. package/src/lib/server/pdf-response.js +30 -0
  86. package/src/lib/server/pdf-response.js.map +1 -0
  87. package/src/lib/server/person-eraser.d.ts +7 -0
  88. package/src/lib/server/person-eraser.js +24 -2
  89. package/src/lib/server/person-eraser.js.map +1 -1
  90. package/src/lib/server/pos-reader-splash.d.ts +2 -18
  91. package/src/lib/server/pos-reader-splash.js +8 -93
  92. package/src/lib/server/pos-reader-splash.js.map +1 -1
  93. package/src/lib/server/pos-return.js +3 -1
  94. package/src/lib/server/pos-return.js.map +1 -1
  95. package/src/lib/server/quote-link-token.d.ts +39 -0
  96. package/src/lib/server/quote-link-token.js +97 -0
  97. package/src/lib/server/quote-link-token.js.map +1 -0
  98. package/src/lib/server/quote-pay.d.ts +110 -0
  99. package/src/lib/server/quote-pay.js +606 -0
  100. package/src/lib/server/quote-pay.js.map +1 -0
  101. package/src/lib/server/quote-proofs.d.ts +51 -0
  102. package/src/lib/server/quote-proofs.js +500 -0
  103. package/src/lib/server/quote-proofs.js.map +1 -0
  104. package/src/lib/server/quote-uploads.d.ts +33 -0
  105. package/src/lib/server/quote-uploads.js +29 -0
  106. package/src/lib/server/quote-uploads.js.map +1 -0
  107. package/src/lib/server/quote.d.ts +63 -0
  108. package/src/lib/server/quote.js +846 -0
  109. package/src/lib/server/quote.js.map +1 -0
  110. package/src/lib/server/related.js +2 -0
  111. package/src/lib/server/related.js.map +1 -1
  112. package/src/lib/server/site-image-source.d.ts +38 -0
  113. package/src/lib/server/site-image-source.js +130 -0
  114. package/src/lib/server/site-image-source.js.map +1 -0
  115. package/src/lib/server/site-page-resolver.d.ts +2 -0
  116. package/src/lib/server/site-page-resolver.js +75 -0
  117. package/src/lib/server/site-page-resolver.js.map +1 -1
  118. package/src/lib/server.js +33 -1
  119. package/src/lib/server.js.map +1 -1
  120. package/src/lib/site.js +6 -0
  121. package/src/lib/site.js.map +1 -1
  122. package/src/lib/transfer/tracking.server.js +6 -0
  123. package/src/lib/transfer/tracking.server.js.map +1 -1
  124. package/src/lib/utils/commerce-document-pdf.d.ts +77 -0
  125. package/src/lib/utils/commerce-document-pdf.js +608 -0
  126. package/src/lib/utils/commerce-document-pdf.js.map +1 -0
  127. package/src/lib/utils/order-invoice.d.ts +52 -3
  128. package/src/lib/utils/order-invoice.js +116 -64
  129. package/src/lib/utils/order-invoice.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/storefront-payment-element.tsx"],"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\n'use client'\n\n// The `/checkout` entry, not the main one: react-stripe-js v6 moved the\n// Checkout Sessions provider, its hook and its own `PaymentElement` there, and\n// the main entry's `PaymentElement` is the Elements one, which does not mount\n// under a Checkout provider.\nimport {\n CheckoutElementsProvider,\n PaymentElement,\n ShippingAddressElement,\n useCheckoutElements,\n} from '@stripe/react-stripe-js/checkout'\n// `/pure` deliberately, not the bare entry (AGL-2486). The bare entry ends\n// with a module-scope `Promise.resolve().then(() => getStripePromise())`,\n// so merely EVALUATING it injects the js.stripe.com/v3 script tag —\n// 249 KB gzipped — on any page that reaches this module, whether or not\n// `loadStripe` is ever called. `/pure` is the same API without that\n// statement, so the script is fetched when we ask for it and not before.\nimport { loadStripe } from '@stripe/stripe-js/pure'\n// The TYPE only, and from the bare entry because `/pure` does not re-export\n// it. `import type` is erased entirely at compile time, so this adds no\n// runtime import and cannot re-introduce the script injection above.\nimport type {\n Stripe,\n StripeAddressElementChangeEvent,\n StripeCheckoutElementsSdkOptions,\n} from '@stripe/stripe-js'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport CircularProgress from '@mui/material/CircularProgress'\nimport Divider from '@mui/material/Divider'\nimport FormControlLabel from '@mui/material/FormControlLabel'\nimport Radio from '@mui/material/Radio'\nimport RadioGroup from '@mui/material/RadioGroup'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { useTheme } from '@mui/material/styles'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n storefrontPaymentAppearance,\n storefrontPaymentFonts,\n} from './storefront-payment-appearance'\nimport type { StorefrontCheckoutWallets } from '../model/commerce-payment-methods'\n\n/**\n * The storefront Payment Element (AGL-1944) — the shopper pays on the\n * merchant's own domain instead of being sent to `checkout.stripe.com`.\n *\n * ## THIS COMPONENT DOES NOT FULFIL ANYTHING\n *\n * It has no success callback, and that absence is the design rather than an\n * omission. `libs/plugins/commerce/src/lib/server/billing-webhook.ts` creates\n * the order on `checkout.session.completed` and is the only thing that ever\n * does. An in-page flow makes trusting the browser far more tempting than a\n * redirect does — the form is right there, `confirm()` resolves in hand, and\n * posting \"done, please fulfil\" is one line away — and the browser is the one\n * participant in a payment that can lie about having paid.\n *\n * It is also the participant that can VANISH. A shopper who confirms and\n * closes the tab never runs a callback, so a flow that fulfils here loses the\n * order outright while keeping the money; a shopper who refreshes the return\n * page runs it twice. Neither is reachable from a design with no callback in\n * it. What happens after a successful confirm is a redirect to `return_url`,\n * carrying the `session_id` the server put there — so the page can SHOW the\n * shopper a result by looking the order up, without being TOLD the outcome by\n * this code.\n *\n * ## Declines and 3DS\n *\n * Both were handled for us by the redirect and now have to be handled here.\n *\n * A decline resolves as `{ type: 'error' }` and is rendered inline WITHOUT\n * unmounting the form: the shopper's basket, address and chosen method are all\n * still on screen and they can try another card. That is the whole difference\n * between a recoverable decline and a dead end, and it is why the error state\n * below sits beside the element rather than replacing it.\n *\n * A 3DS challenge is Stripe's own: `confirm()` presents the challenge and, for\n * redirect-based methods, sends the browser to `return_url` and back. Nothing\n * here has to model it — but the button must stay disabled across the whole\n * await, because a second `confirm()` during a challenge is exactly the\n * double-submit the server's idempotency claim exists to survive and there is\n * no reason to make it lean on that.\n *\n * ## Everything the hosted page collected, collected here (AGL-3606)\n *\n * The hosted page asked for an email, a shipping address and a shipping\n * method, and showed a running total. A `ui_mode: 'elements'` session asks\n * for none of that on its own — the Payment Element is only the payment\n * method — so each is mounted here, read off the SESSION rather than off\n * anything the storefront believes:\n *\n * - **Email.** A session created with `customer_email` (the cart's email\n * field) already has one and Stripe refuses `updateEmail` on it, so it is\n * shown read-only. Otherwise a required field, sent with `updateEmail`\n * before `confirm()` — the receipt and the order's customer both come from\n * it, and a sale without one is an order nobody can be told about.\n * - **Shipping.** Only when the session carries shipping options, which the\n * server attaches only when the store ships to the destination. The\n * Shipping Address Element collects the address (and, under Stripe Tax,\n * is where the tax is computed from); the picker lists the session's own\n * options and selects through `updateShippingOption`.\n * - **Tax address without shipping.** Left to the Payment Element, which\n * collects the country and postal code Stripe Tax needs when the session\n * has `automatic_tax` and no shipping address. A Billing Address Element\n * there would make `updateBillingAddress` conflict with it.\n * - **The total.** Rendered from `session.total`, which Stripe recomputes on\n * every address and option change — tax and shipping included — so the\n * number on the button is the number charged.\n * - **The look.** `elementsOptions.appearance` and `fonts` come from the site\n * theme (`storefront-payment-appearance.ts`), so Stripe's frames wear the\n * merchant's palette, radius and typeface.\n *\n * ## Expired and redirect-returned sessions\n *\n * A session that expired, or that cannot be loaded, offers a fresh one through\n * `onRestart` (a NEW attempt key, so the server does not replay the dead\n * session). A redirect-based method that fails comes back to `return_url`\n * with the session still open; `use-checkout-return-notice.ts` reads that on\n * the return page and says nothing was charged.\n */\n\nexport interface StorefrontPaymentElementProps {\n /** Stripe's instruction to the browser. Not a claim that money moved. */\n clientSecret: string\n publishableKey: string\n /** Shown on the pay button, so the merchant's own total reads through. */\n payLabel?: string\n /**\n * An email the storefront already knows but did not put on the session\n * (it prefills the field; the shopper can still change it). A session the\n * server created WITH an email shows that one read-only instead.\n */\n defaultEmail?: string\n /** Abandon the in-page form and return to the store. */\n onCancel?: () => void\n /**\n * Open a fresh session after this one expired or could not be loaded. The\n * caller must mint a NEW attempt key first, or the server replays the dead\n * session for the old one.\n */\n onRestart?: () => void\n /**\n * The wallets the merchant hid (AGL-3629). Stripe cannot exclude Apple Pay,\n * Google Pay or Link on the session, so the Payment Element is told instead.\n * Absent means every wallet the device supports shows.\n */\n wallets?: StorefrontCheckoutWallets\n}\n\n/**\n * `loadStripe` injects a script tag, so it is memoized per key rather than\n * called per render. Keyed because a page could in principle mount this for\n * two different merchants; in practice it is one, and the map costs nothing.\n */\nconst stripePromises = new Map<string, Promise<Stripe | null>>()\nfunction getStripe(publishableKey: string): Promise<Stripe | null> | null {\n if (!publishableKey) return null\n let promise = stripePromises.get(publishableKey)\n if (!promise) {\n promise = loadStripe(publishableKey)\n stripePromises.set(publishableKey, promise)\n }\n return promise\n}\n\n/** Test seam: `loadStripe` caches per key across a file's tests otherwise. */\nexport function __resetStripePromises(): void {\n stripePromises.clear()\n}\n\nconst GENERIC_FAILURE = 'That payment could not be completed. Try another card.'\n\n/** How long the address must sit still before the session re-prices on it. */\nconst ADDRESS_SETTLE_MS = 400\n\nfunction SummaryRow({\n label,\n value,\n strong,\n}: {\n label: string\n value: string\n strong?: boolean\n}) {\n return (\n <Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 2 }}>\n <Typography\n variant={strong ? 'subtitle1' : 'body2'}\n color={strong ? 'text.primary' : 'text.secondary'}\n >\n {label}\n </Typography>\n <Typography variant={strong ? 'subtitle1' : 'body2'}>{value}</Typography>\n </Box>\n )\n}\n\nfunction SessionNotice({\n message,\n onCancel,\n onRestart,\n}: {\n message: string\n onCancel?: () => void\n onRestart?: () => void\n}) {\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <Alert severity=\"error\" role=\"alert\">\n {message}\n </Alert>\n <Box sx={{ display: 'flex', gap: 1 }}>\n {onRestart ? (\n <Button variant=\"contained\" onClick={onRestart}>\n {'Start checkout again'}\n </Button>\n ) : null}\n {onCancel ? (\n <Button variant=\"text\" onClick={onCancel}>\n {'Back to the store'}\n </Button>\n ) : null}\n </Box>\n </Box>\n )\n}\n\nfunction PaymentForm({\n payLabel,\n defaultEmail,\n onCancel,\n onRestart,\n wallets,\n}: Pick<\n StorefrontPaymentElementProps,\n 'payLabel' | 'defaultEmail' | 'onCancel' | 'onRestart' | 'wallets'\n>) {\n // A `{ type: 'loading' | 'success' | 'error' }` union since react-stripe-js\n // v6: the session is not usable until Stripe.js has booted against it.\n const checkoutState = useCheckoutElements()\n const checkout =\n checkoutState.type === 'success' ? checkoutState.checkout : null\n const [status, setStatus] = useState<'idle' | 'confirming' | 'error'>('idle')\n const [message, setMessage] = useState('')\n const [email, setEmail] = useState(defaultEmail ?? '')\n const [emailError, setEmailError] = useState('')\n const [shippingBusy, setShippingBusy] = useState(false)\n\n /**\n * The email the SERVER put on the session, latched at first load. Stripe\n * refuses `updateEmail` on such a session, so it is shown, not edited. Read\n * once rather than live because the session's email also becomes set the\n * moment THIS form calls `updateEmail` — which must not turn the field the\n * shopper is typing in into read-only text.\n */\n const serverEmail = useRef<string | null | undefined>(undefined)\n if (checkout && serverEmail.current === undefined) {\n serverEmail.current = checkout.email ? String(checkout.email) : null\n }\n const emailLocked = Boolean(serverEmail.current)\n\n const sessionShippingOptions = checkout?.shippingOptions\n const shippingOptions = useMemo(\n () => sessionShippingOptions ?? [],\n [sessionShippingOptions],\n )\n const collectsShipping = shippingOptions.length > 0\n const selectedShippingId = checkout?.shipping?.shippingOption?.id ?? ''\n\n // The hosted page pre-selects the first rate, so the in-page one does too:\n // a session with options and no selection cannot be confirmed, and making\n // the shopper discover that at the pay button is a decline they did not\n // cause. Once per session — a later deselect is not something Stripe does.\n const autoSelected = useRef(false)\n useEffect(() => {\n if (!checkout || !collectsShipping || selectedShippingId) return\n if (autoSelected.current) return\n autoSelected.current = true\n void checkout.updateShippingOption(shippingOptions[0].id)\n }, [checkout, collectsShipping, selectedShippingId, shippingOptions])\n\n const chooseShipping = useCallback(\n async (id: string) => {\n if (!checkout || id === selectedShippingId) return\n setShippingBusy(true)\n try {\n const result = await checkout.updateShippingOption(id)\n if (result?.type === 'error') {\n setMessage(\n String(result.error?.message ?? '') ||\n 'That shipping option could not be selected.',\n )\n }\n } finally {\n setShippingBusy(false)\n }\n },\n [checkout, selectedShippingId],\n )\n\n // Re-price on the address once it is complete and has stopped changing:\n // the Address Element fires per keystroke, and under Stripe Tax every\n // update is a tax calculation. Confirm sends the element's value anyway, so\n // this exists for the TOTAL — the shopper sees tax before they pay.\n const addressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n useEffect(\n () => () => {\n if (addressTimer.current) clearTimeout(addressTimer.current)\n },\n [],\n )\n const onShippingAddressChange = useCallback(\n (event: StripeAddressElementChangeEvent) => {\n if (!checkout || !event.complete) return\n if (addressTimer.current) clearTimeout(addressTimer.current)\n const { name, address } = event.value\n addressTimer.current = setTimeout(() => {\n void checkout\n .updateShippingAddress({ name, address })\n .catch(() => undefined)\n }, ADDRESS_SETTLE_MS)\n },\n [checkout],\n )\n\n const commitEmail = useCallback(async (): Promise<boolean> => {\n if (!checkout || emailLocked) return true\n const trimmed = email.trim()\n if (!trimmed) {\n setEmailError('Enter your email for the receipt.')\n return false\n }\n if (checkout.email === trimmed) return true\n const result = await checkout.updateEmail(trimmed)\n if (result?.type === 'error') {\n setEmailError(\n String(result.error?.message ?? '') || 'Enter a valid email address.',\n )\n return false\n }\n setEmailError('')\n return true\n }, [checkout, email, emailLocked])\n\n const handlePay = useCallback(async () => {\n // Guarded rather than merely disabled: a disabled button is a rendering\n // fact, and a keyboard or a slow re-render can still land a second call\n // during a 3DS challenge.\n if (status === 'confirming' || !checkout) return\n setStatus('confirming')\n setMessage('')\n try {\n if (!(await commitEmail())) {\n setStatus('idle')\n return\n }\n const result = await checkout.confirm()\n if (result?.type === 'error') {\n // A decline, an expired card, an incomplete field, a failed 3DS\n // challenge. Recoverable: the form stays mounted with everything the\n // shopper typed still in it.\n setMessage(String(result.error?.message ?? '') || GENERIC_FAILURE)\n setStatus('error')\n return\n }\n // Success does NOT mean fulfilled, and nothing is reported to our server\n // here. Stripe redirects to the `return_url` the session carries; if it\n // has not by the time this resolves, the confirm is still in flight and\n // the button stays disabled rather than inviting a second attempt.\n } catch {\n // A throw is an integration error on Stripe's side of the contract, not\n // something the shopper did; its text is not written for them.\n setMessage(GENERIC_FAILURE)\n setStatus('error')\n }\n }, [checkout, commitEmail, status])\n\n if (checkoutState.type === 'error') {\n // The session could not be loaded — expired, or already completed in\n // another tab. Nothing typed here could be charged, so say so and offer a\n // fresh session or the way back rather than a form that cannot submit.\n return (\n <SessionNotice\n message=\"This checkout could not be loaded. Start again, or return to the store.\"\n onCancel={onCancel}\n onRestart={onRestart}\n />\n )\n }\n\n if (!checkout) {\n return (\n <Box sx={{ display: 'flex', justifyContent: 'center', py: 3 }}>\n <CircularProgress size={28} aria-label=\"Loading checkout\" />\n </Box>\n )\n }\n\n if (checkout.status?.type === 'expired') {\n return (\n <SessionNotice\n message=\"This checkout expired before it was paid. Nothing was charged.\"\n onCancel={onCancel}\n onRestart={onRestart}\n />\n )\n }\n\n if (checkout.status?.type === 'complete') {\n // Paid in another tab, or the return redirect is already under way.\n // Offering a restart here would be offering to charge twice.\n return (\n <Alert severity=\"success\" role=\"status\">\n {'This order is already paid.'}\n </Alert>\n )\n }\n\n const total = checkout.total\n const busy = status === 'confirming'\n const discount = total?.discount?.minorUnitsAmount ?? 0\n const tax = total?.taxExclusive?.minorUnitsAmount ?? 0\n const taxPending = checkout.tax?.status && checkout.tax.status !== 'ready'\n\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n {emailLocked ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Receipt to ${serverEmail.current}`}\n </Typography>\n ) : (\n <TextField\n type=\"email\"\n label=\"Email\"\n required\n fullWidth\n autoComplete=\"email\"\n value={email}\n onChange={(event) => {\n setEmail(event.target.value)\n if (emailError) setEmailError('')\n }}\n onBlur={() => {\n if (email.trim()) void commitEmail()\n }}\n error={Boolean(emailError)}\n helperText={emailError || 'Your receipt is sent here.'}\n disabled={busy}\n />\n )}\n {collectsShipping ? (\n <Box\n sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}\n data-testid=\"storefront-shipping\"\n >\n <Typography variant=\"subtitle2\">{'Shipping address'}</Typography>\n <ShippingAddressElement onChange={onShippingAddressChange} />\n <Typography variant=\"subtitle2\">{'Shipping method'}</Typography>\n <RadioGroup\n aria-label=\"Shipping method\"\n value={selectedShippingId}\n onChange={(event) => void chooseShipping(event.target.value)}\n >\n {shippingOptions.map((option) => (\n <FormControlLabel\n key={option.id}\n value={option.id}\n disabled={busy || shippingBusy}\n control={<Radio />}\n label={`${option.displayName || 'Shipping'} — ${option.amount}`}\n />\n ))}\n </RadioGroup>\n </Box>\n ) : null}\n <PaymentElement options={wallets ? { wallets } : undefined} />\n {total ? (\n <Box\n sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}\n data-testid=\"storefront-checkout-total\"\n >\n <SummaryRow label=\"Subtotal\" value={total.subtotal.amount} />\n {discount ? (\n <SummaryRow label=\"Discount\" value={`−${total.discount.amount}`} />\n ) : null}\n {collectsShipping ? (\n <SummaryRow\n label=\"Shipping\"\n value={selectedShippingId ? total.shippingRate.amount : '—'}\n />\n ) : null}\n {tax || taxPending ? (\n <SummaryRow\n label=\"Tax\"\n value={taxPending ? 'Calculated from your address' : total.taxExclusive.amount}\n />\n ) : null}\n <Divider />\n <SummaryRow label=\"Total\" value={total.total.amount} strong />\n </Box>\n ) : null}\n {message ? (\n // `error` severity is right here and wrong for a lockdown pause — this\n // really is \"your payment did not go through\", which is the one thing\n // a shopper must be told plainly.\n <Alert severity=\"error\" role=\"alert\">\n {message}\n </Alert>\n ) : null}\n <Box sx={{ display: 'flex', gap: 1 }}>\n <Button\n variant=\"contained\"\n onClick={handlePay}\n disabled={busy || shippingBusy}\n fullWidth\n >\n {busy ? 'Paying…' : payLabel || 'Pay now'}\n </Button>\n {onCancel ? (\n <Button variant=\"text\" onClick={onCancel} disabled={busy}>\n {'Cancel'}\n </Button>\n ) : null}\n </Box>\n </Box>\n )\n}\n\nexport function StorefrontPaymentElement({\n clientSecret,\n publishableKey,\n payLabel,\n defaultEmail,\n onCancel,\n onRestart,\n wallets,\n}: StorefrontPaymentElementProps) {\n const stripe = useMemo(() => getStripe(publishableKey), [publishableKey])\n const theme = useTheme()\n // Built once per session: the provider reads its options at mount and\n // ignores later changes, so a memo keyed on the theme as well would only\n // suggest a re-theme that never happens.\n const options = useMemo<StripeCheckoutElementsSdkOptions>(\n () => ({\n clientSecret,\n elementsOptions: {\n appearance: storefrontPaymentAppearance(theme),\n fonts: storefrontPaymentFonts(\n typeof theme.typography?.fontFamily === 'string'\n ? theme.typography.fontFamily\n : undefined,\n ),\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [clientSecret],\n )\n // Belt and braces with the server's own gate, which already refuses native\n // mode without a publishable key: rendering nothing beats rendering an empty\n // white box that looks like a broken payment form.\n if (!clientSecret || !stripe) return null\n return (\n <CheckoutElementsProvider stripe={stripe} options={options}>\n <Box sx={{ py: 2 }} data-testid=\"storefront-payment-element\">\n <PaymentForm\n payLabel={payLabel}\n defaultEmail={defaultEmail}\n onCancel={onCancel}\n onRestart={onRestart}\n wallets={wallets}\n />\n </Box>\n </CheckoutElementsProvider>\n )\n}\n\n/**\n * Re-exported so the old import path still resolves, but the definition now\n * lives in its own module (AGL-2486): a fallback imported FROM the lazily\n * loaded module drags that module into the eager bundle and cancels the\n * `lazy()`. Callers must import it from `./storefront-payment-element-fallback`\n * — importing it from here is exactly the bug, and re-exporting it does not\n * make that safe.\n */\nexport { StorefrontPaymentElementFallback } from './storefront-payment-element-fallback'\n\nexport default StorefrontPaymentElement\n"],"names":["CheckoutElementsProvider","PaymentElement","ShippingAddressElement","useCheckoutElements","loadStripe","Alert","Box","Button","CircularProgress","Divider","FormControlLabel","Radio","RadioGroup","TextField","Typography","useTheme","useCallback","useEffect","useMemo","useRef","useState","storefrontPaymentAppearance","storefrontPaymentFonts","stripePromises","Map","getStripe","publishableKey","promise","get","set","__resetStripePromises","clear","GENERIC_FAILURE","ADDRESS_SETTLE_MS","SummaryRow","label","value","strong","sx","display","justifyContent","gap","variant","color","SessionNotice","message","onCancel","onRestart","flexDirection","severity","role","onClick","PaymentForm","payLabel","defaultEmail","wallets","checkout","total","checkoutState","type","status","setStatus","setMessage","email","setEmail","emailError","setEmailError","shippingBusy","setShippingBusy","serverEmail","undefined","current","String","emailLocked","Boolean","sessionShippingOptions","shippingOptions","collectsShipping","length","selectedShippingId","shipping","shippingOption","id","autoSelected","updateShippingOption","chooseShipping","result","error","addressTimer","clearTimeout","onShippingAddressChange","event","complete","name","address","setTimeout","updateShippingAddress","catch","commitEmail","trimmed","trim","updateEmail","handlePay","confirm","py","size","aria-label","busy","discount","minorUnitsAmount","tax","taxExclusive","taxPending","required","fullWidth","autoComplete","onChange","target","onBlur","helperText","disabled","data-testid","map","option","control","displayName","amount","options","subtotal","shippingRate","StorefrontPaymentElement","clientSecret","stripe","theme","elementsOptions","appearance","fonts","typography","fontFamily","StorefrontPaymentElementFallback"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/components/storefront-payment-element.tsx"],"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\n'use client'\n\n// The `/checkout` entry, not the main one: react-stripe-js v6 moved the\n// Checkout Sessions provider, its hook and its own `PaymentElement` there, and\n// the main entry's `PaymentElement` is the Elements one, which does not mount\n// under a Checkout provider.\nimport {\n CheckoutElementsProvider,\n PaymentElement,\n ShippingAddressElement,\n useCheckoutElements,\n} from '@stripe/react-stripe-js/checkout'\n// `/pure` deliberately, not the bare entry (AGL-2486). The bare entry ends\n// with a module-scope `Promise.resolve().then(() => getStripePromise())`,\n// so merely EVALUATING it injects the js.stripe.com/v3 script tag —\n// 249 KB gzipped — on any page that reaches this module, whether or not\n// `loadStripe` is ever called. `/pure` is the same API without that\n// statement, so the script is fetched when we ask for it and not before.\nimport { loadStripe } from '@stripe/stripe-js/pure'\n// The TYPE only, and from the bare entry because `/pure` does not re-export\n// it. `import type` is erased entirely at compile time, so this adds no\n// runtime import and cannot re-introduce the script injection above.\nimport type {\n Stripe,\n StripeAddressElementChangeEvent,\n StripeCheckoutContact,\n StripeCheckoutElementsSdkOptions,\n} from '@stripe/stripe-js'\nimport Alert from '@mui/material/Alert'\nimport Box from '@mui/material/Box'\nimport Button from '@mui/material/Button'\nimport CircularProgress from '@mui/material/CircularProgress'\nimport Divider from '@mui/material/Divider'\nimport FormControlLabel from '@mui/material/FormControlLabel'\nimport Radio from '@mui/material/Radio'\nimport RadioGroup from '@mui/material/RadioGroup'\nimport TextField from '@mui/material/TextField'\nimport Typography from '@mui/material/Typography'\nimport { useTheme } from '@mui/material/styles'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n storefrontPaymentAppearance,\n storefrontPaymentFonts,\n} from './storefront-payment-appearance'\nimport type { StorefrontCheckoutWallets } from '../model/commerce-payment-methods'\n\n/**\n * The storefront Payment Element (AGL-1944) — the shopper pays on the\n * merchant's own domain instead of being sent to `checkout.stripe.com`.\n *\n * ## THIS COMPONENT DOES NOT FULFIL ANYTHING\n *\n * It has no success callback, and that absence is the design rather than an\n * omission. `libs/plugins/commerce/src/lib/server/billing-webhook.ts` creates\n * the order on `checkout.session.completed` and is the only thing that ever\n * does. An in-page flow makes trusting the browser far more tempting than a\n * redirect does — the form is right there, `confirm()` resolves in hand, and\n * posting \"done, please fulfil\" is one line away — and the browser is the one\n * participant in a payment that can lie about having paid.\n *\n * It is also the participant that can VANISH. A shopper who confirms and\n * closes the tab never runs a callback, so a flow that fulfils here loses the\n * order outright while keeping the money; a shopper who refreshes the return\n * page runs it twice. Neither is reachable from a design with no callback in\n * it. What happens after a successful confirm is a redirect to `return_url`,\n * carrying the `session_id` the server put there — so the page can SHOW the\n * shopper a result by looking the order up, without being TOLD the outcome by\n * this code.\n *\n * ## Declines and 3DS\n *\n * Both were handled for us by the redirect and now have to be handled here.\n *\n * A decline resolves as `{ type: 'error' }` and is rendered inline WITHOUT\n * unmounting the form: the shopper's basket, address and chosen method are all\n * still on screen and they can try another card. That is the whole difference\n * between a recoverable decline and a dead end, and it is why the error state\n * below sits beside the element rather than replacing it.\n *\n * A 3DS challenge is Stripe's own: `confirm()` presents the challenge and, for\n * redirect-based methods, sends the browser to `return_url` and back. Nothing\n * here has to model it — but the button must stay disabled across the whole\n * await, because a second `confirm()` during a challenge is exactly the\n * double-submit the server's idempotency claim exists to survive and there is\n * no reason to make it lean on that.\n *\n * ## Everything the hosted page collected, collected here (AGL-3606)\n *\n * The hosted page asked for an email, a shipping address and a shipping\n * method, and showed a running total. A `ui_mode: 'elements'` session asks\n * for none of that on its own — the Payment Element is only the payment\n * method — so each is mounted here, read off the SESSION rather than off\n * anything the storefront believes:\n *\n * - **Email.** A session created with `customer_email` (the cart's email\n * field) already has one and Stripe refuses `updateEmail` on it, so it is\n * shown read-only. Otherwise a required field, sent with `updateEmail`\n * before `confirm()` — the receipt and the order's customer both come from\n * it, and a sale without one is an order nobody can be told about.\n * - **Shipping.** Only when the session carries shipping options, which the\n * server attaches only when the store ships to the destination. The\n * Shipping Address Element collects the address (and, under Stripe Tax,\n * is where the tax is computed from); the picker lists the session's own\n * options and selects through `updateShippingOption`.\n * - **Tax address without shipping.** Left to the Payment Element, which\n * collects the country and postal code Stripe Tax needs when the session\n * has `automatic_tax` and no shipping address. A Billing Address Element\n * there would make `updateBillingAddress` conflict with it.\n * - **The total.** Rendered from `session.total`, which Stripe recomputes on\n * every address and option change — tax and shipping included — so the\n * number on the button is the number charged.\n * - **The look.** `elementsOptions.appearance` and `fonts` come from the site\n * theme (`storefront-payment-appearance.ts`), so Stripe's frames wear the\n * merchant's palette, radius and typeface.\n *\n * ## Expired and redirect-returned sessions\n *\n * A session that expired, or that cannot be loaded, offers a fresh one through\n * `onRestart` (a NEW attempt key, so the server does not replay the dead\n * session). A redirect-based method that fails comes back to `return_url`\n * with the session still open; `use-checkout-return-notice.ts` reads that on\n * the return page and says nothing was charged.\n */\n\nexport interface StorefrontPaymentElementProps {\n /** Stripe's instruction to the browser. Not a claim that money moved. */\n clientSecret: string\n publishableKey: string\n /** Shown on the pay button, so the merchant's own total reads through. */\n payLabel?: string\n /**\n * An email the storefront already knows but did not put on the session\n * (it prefills the field; the shopper can still change it). A session the\n * server created WITH an email shows that one read-only instead.\n */\n defaultEmail?: string\n /** Abandon the in-page form and return to the store. */\n onCancel?: () => void\n /**\n * Open a fresh session after this one expired or could not be loaded. The\n * caller must mint a NEW attempt key first, or the server replays the dead\n * session for the old one.\n */\n onRestart?: () => void\n /**\n * The wallets the merchant hid (AGL-3629). Stripe cannot exclude Apple Pay,\n * Google Pay or Link on the session, so the Payment Element is told instead.\n * Absent means every wallet the device supports shows.\n */\n wallets?: StorefrontCheckoutWallets\n /**\n * An address the shipping fields start with, where the session asks for\n * one: a quote's ship-to (AGL-4094). The shopper can still change it.\n */\n defaultShippingAddress?: StripeCheckoutContact\n}\n\n/**\n * `loadStripe` injects a script tag, so it is memoized per key rather than\n * called per render. Keyed because a page could in principle mount this for\n * two different merchants; in practice it is one, and the map costs nothing.\n */\nconst stripePromises = new Map<string, Promise<Stripe | null>>()\nfunction getStripe(publishableKey: string): Promise<Stripe | null> | null {\n if (!publishableKey) return null\n let promise = stripePromises.get(publishableKey)\n if (!promise) {\n promise = loadStripe(publishableKey)\n stripePromises.set(publishableKey, promise)\n }\n return promise\n}\n\n/** Test seam: `loadStripe` caches per key across a file's tests otherwise. */\nexport function __resetStripePromises(): void {\n stripePromises.clear()\n}\n\nconst GENERIC_FAILURE = 'That payment could not be completed. Try another card.'\n\n/** How long the address must sit still before the session re-prices on it. */\nconst ADDRESS_SETTLE_MS = 400\n\nfunction SummaryRow({\n label,\n value,\n strong,\n}: {\n label: string\n value: string\n strong?: boolean\n}) {\n return (\n <Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 2 }}>\n <Typography\n variant={strong ? 'subtitle1' : 'body2'}\n color={strong ? 'text.primary' : 'text.secondary'}\n >\n {label}\n </Typography>\n <Typography variant={strong ? 'subtitle1' : 'body2'}>{value}</Typography>\n </Box>\n )\n}\n\nfunction SessionNotice({\n message,\n onCancel,\n onRestart,\n}: {\n message: string\n onCancel?: () => void\n onRestart?: () => void\n}) {\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <Alert severity=\"error\" role=\"alert\">\n {message}\n </Alert>\n <Box sx={{ display: 'flex', gap: 1 }}>\n {onRestart ? (\n <Button variant=\"contained\" onClick={onRestart}>\n {'Start checkout again'}\n </Button>\n ) : null}\n {onCancel ? (\n <Button variant=\"text\" onClick={onCancel}>\n {'Back to the store'}\n </Button>\n ) : null}\n </Box>\n </Box>\n )\n}\n\nfunction PaymentForm({\n payLabel,\n defaultEmail,\n onCancel,\n onRestart,\n wallets,\n}: Pick<\n StorefrontPaymentElementProps,\n 'payLabel' | 'defaultEmail' | 'onCancel' | 'onRestart' | 'wallets'\n>) {\n // A `{ type: 'loading' | 'success' | 'error' }` union since react-stripe-js\n // v6: the session is not usable until Stripe.js has booted against it.\n const checkoutState = useCheckoutElements()\n const checkout =\n checkoutState.type === 'success' ? checkoutState.checkout : null\n const [status, setStatus] = useState<'idle' | 'confirming' | 'error'>('idle')\n const [message, setMessage] = useState('')\n const [email, setEmail] = useState(defaultEmail ?? '')\n const [emailError, setEmailError] = useState('')\n const [shippingBusy, setShippingBusy] = useState(false)\n\n /**\n * The email the SERVER put on the session, latched at first load. Stripe\n * refuses `updateEmail` on such a session, so it is shown, not edited. Read\n * once rather than live because the session's email also becomes set the\n * moment THIS form calls `updateEmail` — which must not turn the field the\n * shopper is typing in into read-only text.\n */\n const serverEmail = useRef<string | null | undefined>(undefined)\n if (checkout && serverEmail.current === undefined) {\n serverEmail.current = checkout.email ? String(checkout.email) : null\n }\n const emailLocked = Boolean(serverEmail.current)\n\n const sessionShippingOptions = checkout?.shippingOptions\n const shippingOptions = useMemo(\n () => sessionShippingOptions ?? [],\n [sessionShippingOptions],\n )\n const collectsShipping = shippingOptions.length > 0\n const selectedShippingId = checkout?.shipping?.shippingOption?.id ?? ''\n\n // The hosted page pre-selects the first rate, so the in-page one does too:\n // a session with options and no selection cannot be confirmed, and making\n // the shopper discover that at the pay button is a decline they did not\n // cause. Once per session — a later deselect is not something Stripe does.\n const autoSelected = useRef(false)\n useEffect(() => {\n if (!checkout || !collectsShipping || selectedShippingId) return\n if (autoSelected.current) return\n autoSelected.current = true\n void checkout.updateShippingOption(shippingOptions[0].id)\n }, [checkout, collectsShipping, selectedShippingId, shippingOptions])\n\n const chooseShipping = useCallback(\n async (id: string) => {\n if (!checkout || id === selectedShippingId) return\n setShippingBusy(true)\n try {\n const result = await checkout.updateShippingOption(id)\n if (result?.type === 'error') {\n setMessage(\n String(result.error?.message ?? '') ||\n 'That shipping option could not be selected.',\n )\n }\n } finally {\n setShippingBusy(false)\n }\n },\n [checkout, selectedShippingId],\n )\n\n // Re-price on the address once it is complete and has stopped changing:\n // the Address Element fires per keystroke, and under Stripe Tax every\n // update is a tax calculation. Confirm sends the element's value anyway, so\n // this exists for the TOTAL — the shopper sees tax before they pay.\n const addressTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n useEffect(\n () => () => {\n if (addressTimer.current) clearTimeout(addressTimer.current)\n },\n [],\n )\n const onShippingAddressChange = useCallback(\n (event: StripeAddressElementChangeEvent) => {\n if (!checkout || !event.complete) return\n if (addressTimer.current) clearTimeout(addressTimer.current)\n const { name, address } = event.value\n addressTimer.current = setTimeout(() => {\n void checkout\n .updateShippingAddress({ name, address })\n .catch(() => undefined)\n }, ADDRESS_SETTLE_MS)\n },\n [checkout],\n )\n\n const commitEmail = useCallback(async (): Promise<boolean> => {\n if (!checkout || emailLocked) return true\n const trimmed = email.trim()\n if (!trimmed) {\n setEmailError('Enter your email for the receipt.')\n return false\n }\n if (checkout.email === trimmed) return true\n const result = await checkout.updateEmail(trimmed)\n if (result?.type === 'error') {\n setEmailError(\n String(result.error?.message ?? '') || 'Enter a valid email address.',\n )\n return false\n }\n setEmailError('')\n return true\n }, [checkout, email, emailLocked])\n\n const handlePay = useCallback(async () => {\n // Guarded rather than merely disabled: a disabled button is a rendering\n // fact, and a keyboard or a slow re-render can still land a second call\n // during a 3DS challenge.\n if (status === 'confirming' || !checkout) return\n setStatus('confirming')\n setMessage('')\n try {\n if (!(await commitEmail())) {\n setStatus('idle')\n return\n }\n const result = await checkout.confirm()\n if (result?.type === 'error') {\n // A decline, an expired card, an incomplete field, a failed 3DS\n // challenge. Recoverable: the form stays mounted with everything the\n // shopper typed still in it.\n setMessage(String(result.error?.message ?? '') || GENERIC_FAILURE)\n setStatus('error')\n return\n }\n // Success does NOT mean fulfilled, and nothing is reported to our server\n // here. Stripe redirects to the `return_url` the session carries; if it\n // has not by the time this resolves, the confirm is still in flight and\n // the button stays disabled rather than inviting a second attempt.\n } catch {\n // A throw is an integration error on Stripe's side of the contract, not\n // something the shopper did; its text is not written for them.\n setMessage(GENERIC_FAILURE)\n setStatus('error')\n }\n }, [checkout, commitEmail, status])\n\n if (checkoutState.type === 'error') {\n // The session could not be loaded — expired, or already completed in\n // another tab. Nothing typed here could be charged, so say so and offer a\n // fresh session or the way back rather than a form that cannot submit.\n return (\n <SessionNotice\n message=\"This checkout could not be loaded. Start again, or return to the store.\"\n onCancel={onCancel}\n onRestart={onRestart}\n />\n )\n }\n\n if (!checkout) {\n return (\n <Box sx={{ display: 'flex', justifyContent: 'center', py: 3 }}>\n <CircularProgress size={28} aria-label=\"Loading checkout\" />\n </Box>\n )\n }\n\n if (checkout.status?.type === 'expired') {\n return (\n <SessionNotice\n message=\"This checkout expired before it was paid. Nothing was charged.\"\n onCancel={onCancel}\n onRestart={onRestart}\n />\n )\n }\n\n if (checkout.status?.type === 'complete') {\n // Paid in another tab, or the return redirect is already under way.\n // Offering a restart here would be offering to charge twice.\n return (\n <Alert severity=\"success\" role=\"status\">\n {'This order is already paid.'}\n </Alert>\n )\n }\n\n const total = checkout.total\n const busy = status === 'confirming'\n const discount = total?.discount?.minorUnitsAmount ?? 0\n const tax = total?.taxExclusive?.minorUnitsAmount ?? 0\n const taxPending = checkout.tax?.status && checkout.tax.status !== 'ready'\n\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n {emailLocked ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Receipt to ${serverEmail.current}`}\n </Typography>\n ) : (\n <TextField\n type=\"email\"\n label=\"Email\"\n required\n fullWidth\n autoComplete=\"email\"\n value={email}\n onChange={(event) => {\n setEmail(event.target.value)\n if (emailError) setEmailError('')\n }}\n onBlur={() => {\n if (email.trim()) void commitEmail()\n }}\n error={Boolean(emailError)}\n helperText={emailError || 'Your receipt is sent here.'}\n disabled={busy}\n />\n )}\n {collectsShipping ? (\n <Box\n sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}\n data-testid=\"storefront-shipping\"\n >\n <Typography variant=\"subtitle2\">{'Shipping address'}</Typography>\n <ShippingAddressElement onChange={onShippingAddressChange} />\n <Typography variant=\"subtitle2\">{'Shipping method'}</Typography>\n <RadioGroup\n aria-label=\"Shipping method\"\n value={selectedShippingId}\n onChange={(event) => void chooseShipping(event.target.value)}\n >\n {shippingOptions.map((option) => (\n <FormControlLabel\n key={option.id}\n value={option.id}\n disabled={busy || shippingBusy}\n control={<Radio />}\n label={`${option.displayName || 'Shipping'} — ${option.amount}`}\n />\n ))}\n </RadioGroup>\n </Box>\n ) : null}\n <PaymentElement options={wallets ? { wallets } : undefined} />\n {total ? (\n <Box\n sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}\n data-testid=\"storefront-checkout-total\"\n >\n <SummaryRow label=\"Subtotal\" value={total.subtotal.amount} />\n {discount ? (\n <SummaryRow label=\"Discount\" value={`−${total.discount.amount}`} />\n ) : null}\n {collectsShipping ? (\n <SummaryRow\n label=\"Shipping\"\n value={selectedShippingId ? total.shippingRate.amount : '—'}\n />\n ) : null}\n {tax || taxPending ? (\n <SummaryRow\n label=\"Tax\"\n value={taxPending ? 'Calculated from your address' : total.taxExclusive.amount}\n />\n ) : null}\n <Divider />\n <SummaryRow label=\"Total\" value={total.total.amount} strong />\n </Box>\n ) : null}\n {message ? (\n // `error` severity is right here and wrong for a lockdown pause — this\n // really is \"your payment did not go through\", which is the one thing\n // a shopper must be told plainly.\n <Alert severity=\"error\" role=\"alert\">\n {message}\n </Alert>\n ) : null}\n <Box sx={{ display: 'flex', gap: 1 }}>\n <Button\n variant=\"contained\"\n onClick={handlePay}\n disabled={busy || shippingBusy}\n fullWidth\n >\n {busy ? 'Paying…' : payLabel || 'Pay now'}\n </Button>\n {onCancel ? (\n <Button variant=\"text\" onClick={onCancel} disabled={busy}>\n {'Cancel'}\n </Button>\n ) : null}\n </Box>\n </Box>\n )\n}\n\nexport function StorefrontPaymentElement({\n clientSecret,\n publishableKey,\n payLabel,\n defaultEmail,\n onCancel,\n onRestart,\n wallets,\n defaultShippingAddress,\n}: StorefrontPaymentElementProps) {\n const stripe = useMemo(() => getStripe(publishableKey), [publishableKey])\n const theme = useTheme()\n // Built once per session: the provider reads its options at mount and\n // ignores later changes, so a memo keyed on the theme as well would only\n // suggest a re-theme that never happens.\n const options = useMemo<StripeCheckoutElementsSdkOptions>(\n () => ({\n clientSecret,\n elementsOptions: {\n appearance: storefrontPaymentAppearance(theme),\n fonts: storefrontPaymentFonts(\n typeof theme.typography?.fontFamily === 'string'\n ? theme.typography.fontFamily\n : undefined,\n ),\n },\n ...(defaultShippingAddress ? { defaultValues: { shippingAddress: defaultShippingAddress } } : {}),\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [clientSecret],\n )\n // Belt and braces with the server's own gate, which already refuses native\n // mode without a publishable key: rendering nothing beats rendering an empty\n // white box that looks like a broken payment form.\n if (!clientSecret || !stripe) return null\n return (\n <CheckoutElementsProvider stripe={stripe} options={options}>\n <Box sx={{ py: 2 }} data-testid=\"storefront-payment-element\">\n <PaymentForm\n payLabel={payLabel}\n defaultEmail={defaultEmail}\n onCancel={onCancel}\n onRestart={onRestart}\n wallets={wallets}\n />\n </Box>\n </CheckoutElementsProvider>\n )\n}\n\n/**\n * Re-exported so the old import path still resolves, but the definition now\n * lives in its own module (AGL-2486): a fallback imported FROM the lazily\n * loaded module drags that module into the eager bundle and cancels the\n * `lazy()`. Callers must import it from `./storefront-payment-element-fallback`\n * — importing it from here is exactly the bug, and re-exporting it does not\n * make that safe.\n */\nexport { StorefrontPaymentElementFallback } from './storefront-payment-element-fallback'\n\nexport default StorefrontPaymentElement\n"],"names":["CheckoutElementsProvider","PaymentElement","ShippingAddressElement","useCheckoutElements","loadStripe","Alert","Box","Button","CircularProgress","Divider","FormControlLabel","Radio","RadioGroup","TextField","Typography","useTheme","useCallback","useEffect","useMemo","useRef","useState","storefrontPaymentAppearance","storefrontPaymentFonts","stripePromises","Map","getStripe","publishableKey","promise","get","set","__resetStripePromises","clear","GENERIC_FAILURE","ADDRESS_SETTLE_MS","SummaryRow","label","value","strong","sx","display","justifyContent","gap","variant","color","SessionNotice","message","onCancel","onRestart","flexDirection","severity","role","onClick","PaymentForm","payLabel","defaultEmail","wallets","checkout","total","checkoutState","type","status","setStatus","setMessage","email","setEmail","emailError","setEmailError","shippingBusy","setShippingBusy","serverEmail","undefined","current","String","emailLocked","Boolean","sessionShippingOptions","shippingOptions","collectsShipping","length","selectedShippingId","shipping","shippingOption","id","autoSelected","updateShippingOption","chooseShipping","result","error","addressTimer","clearTimeout","onShippingAddressChange","event","complete","name","address","setTimeout","updateShippingAddress","catch","commitEmail","trimmed","trim","updateEmail","handlePay","confirm","py","size","aria-label","busy","discount","minorUnitsAmount","tax","taxExclusive","taxPending","required","fullWidth","autoComplete","onChange","target","onBlur","helperText","disabled","data-testid","map","option","control","displayName","amount","options","subtotal","shippingRate","StorefrontPaymentElement","clientSecret","defaultShippingAddress","stripe","theme","elementsOptions","appearance","fonts","typography","fontFamily","defaultValues","shippingAddress","StorefrontPaymentElementFallback"],"mappings":"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"}
@@ -229,7 +229,12 @@ export const ORDER_STATUS_OPTIONS = Object.keys(ORDER_STATUS_LABELS).map((status
229
229
  value: status,
230
230
  label: ORDER_STATUS_LABELS[status]
231
231
  }));
232
- export const ORDER_CHANNEL_OPTIONS = Object.keys(ORDER_CHANNEL_LABELS).map((channel)=>({
232
+ /*
233
+ * Every channel, `quote` included: a quote is listed here once it is paid
234
+ * (AGL-4094), as the order it became, and "Quote" finds those orders. An
235
+ * unpaid quote is never on this list (`commerce-quotes.ts`); the Quotes card
236
+ * lists it.
237
+ */ export const ORDER_CHANNEL_OPTIONS = Object.keys(ORDER_CHANNEL_LABELS).map((channel)=>({
233
238
  value: channel,
234
239
  label: ORDER_CHANNEL_LABELS[channel]
235
240
  }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/orders-list-query.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type {\n ListFilterClause,\n ListFilterOption,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type {\n ListQueryDeclaration,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n ORDER_CHANNEL_LABELS,\n ORDER_STATUS_LABELS,\n type OrderChannel,\n type OrderStatus,\n} from '../model/commerce-orders'\n\n/*\n * THE ORDERS LIST, ON ITS QUERY (AGL-3321).\n *\n * `hosts/{hostId}/orders`, newest first. Every filter the grid's panel offers\n * and the quick search's word is a predicate on the ONE Firestore query that\n * pages the list (`planListQuery` through `useListQuery`), over the fields\n * every writer stamps with `orderListFields` — so page two of a filtered list\n * is page two of the matches, not page two of the store narrowed afterwards.\n *\n * One order: `createdAtMs` descending, the field every writer stamps. Each\n * filterable field is then ONE `(field, createdAtMs DESC)` composite, and\n * index merging serves every combination of them; the date range is on that\n * same field, so it adds none. Eight composites in all, pinned by\n * `orders-list-query.spec.ts` against `cloud/firebase-firestore.indexes.json`.\n *\n * What one query cannot hold is refused by name above the grid\n * (`ListQueryNotices`, labelled by `listQueryRefusals` from these fields,\n * headers and options), never matched over the rows it returned: the search,\n * Customer `contains`, Order `contains` and Product each take the query's one\n * array clause, so two of them together keep the first and refuse the second.\n *\n * A second range (a total, say) would be a new `sorts` entry and one more\n * composite per filterable field beneath it — eight — plus the range field's\n * own order; nothing else here moves.\n *\n * ## Every header sorts (AGL-3680)\n *\n * `ORDER_LIST_COLUMN_SORTS`: Date either way, and Order, Customer, Channel and\n * Status, each on the field `orderListFields` stamps on every order —\n * `number` (null on an order with none: POS, draft and channel orders are\n * unnumbered), `customerEmailLower`, `channel`, `status`.\n * `tools/scripts/backfill-orders-list-fields.mjs` stamps `number` and an\n * absent `customerEmailLower` on the orders written before. Every order but\n * the default is `alone`: served with no filter or search on, it costs no\n * composite on this unscoped list, and asked under a filter it falls back to\n * newest first and says so. Total is computed from the refunds, so it sorts\n * the page on screen.\n */\n\n/** The grid columns, each as the query reads it. */\nexport const ORDER_LIST_FIELDS: readonly ListFilterField[] = [\n {\n // The number as typed, `#1042` or `1042`, by word prefix.\n column: 'orderLabel',\n kind: 'text',\n path: 'number',\n tokensPath: 'orderLabelTokens',\n operators: ['contains'],\n },\n {\n // `is` a whole address; `contains` an address's piece or its domain.\n column: 'customerEmail',\n kind: 'text',\n path: 'customerEmail',\n lowerPath: 'customerEmailLower',\n tokensPath: 'customerEmailTokens',\n operators: ['contains', 'equals'],\n },\n {\n column: 'channelKey',\n kind: 'exact',\n path: 'channel',\n presence: 'always',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: 'statusKey',\n kind: 'exact',\n path: 'status',\n presence: 'always',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: 'createdAtMs',\n kind: 'date',\n path: 'createdAtMs',\n storedAs: 'millis',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n // Every product in the order, matched whole — ids keep their case.\n column: 'productIds',\n kind: 'exact',\n path: 'productIds',\n tokensPath: 'productIds',\n verbatimTokens: true,\n operators: ['isAnyOf'],\n },\n {\n column: 'disputeKey',\n kind: 'exact',\n path: 'disputeKey',\n operators: ['equals', 'isAnyOf'],\n },\n]\n\n/**\n * The orders the grid's headers ask for, newest first FIRST — see \"Every\n * header sorts\" above.\n */\nexport const ORDER_LIST_COLUMN_SORTS: readonly ListQuerySort[] = [\n { path: 'createdAtMs', direction: 'desc', column: 'createdAtMs', label: 'Date' },\n { path: 'createdAtMs', direction: 'asc', column: 'createdAtMs', label: 'Date', alone: true },\n { path: 'number', direction: 'desc', column: 'orderLabel', label: 'Order', alone: true },\n { path: 'number', direction: 'asc', column: 'orderLabel', label: 'Order', alone: true },\n { path: 'customerEmailLower', direction: 'asc', column: 'customerEmail', label: 'Customer', alone: true },\n { path: 'customerEmailLower', direction: 'desc', column: 'customerEmail', label: 'Customer', alone: true },\n { path: 'channel', direction: 'asc', column: 'channelKey', label: 'Channel', alone: true },\n { path: 'channel', direction: 'desc', column: 'channelKey', label: 'Channel', alone: true },\n { path: 'status', direction: 'asc', column: 'statusKey', label: 'Status', alone: true },\n { path: 'status', direction: 'desc', column: 'statusKey', label: 'Status', alone: true },\n]\n\n/** The list's query: its fields, its header orders and its search. */\nexport const ORDER_LIST_QUERY: ListQueryDeclaration = {\n fields: ORDER_LIST_FIELDS,\n sorts: ORDER_LIST_COLUMN_SORTS,\n search: { tokensPath: 'searchTokens' },\n}\n\nexport const ORDER_LIST_HEADERS: Readonly<Record<string, string>> = {\n orderLabel: 'Order',\n customerEmail: 'Customer',\n channelKey: 'Channel',\n statusKey: 'Status',\n createdAtMs: 'Date',\n productIds: 'Product',\n disputeKey: 'Disputes',\n}\n\n/** The fields picked from choices, which the panel shows as a select. */\nexport const ORDER_LIST_SELECT_FIELDS: readonly string[] = [\n 'channelKey',\n 'statusKey',\n 'productIds',\n 'disputeKey',\n]\n\nexport const ORDER_STATUS_OPTIONS: readonly ListFilterOption[] = (\n Object.keys(ORDER_STATUS_LABELS) as OrderStatus[]\n).map((status) => ({ value: status, label: ORDER_STATUS_LABELS[status] }))\n\nexport const ORDER_CHANNEL_OPTIONS: readonly ListFilterOption[] = (\n Object.keys(ORDER_CHANNEL_LABELS) as OrderChannel[]\n).map((channel) => ({ value: channel, label: ORDER_CHANNEL_LABELS[channel] }))\n\n/*\n * The dispute badge's tones (`orderDisputeKey`), labelled as the badge\n * labels them. `lost` is the tone, not the raw dispute status: a dispute\n * that was WON also closes with money untouched, and listing it as charged\n * back would tell a merchant they lost a case they won.\n */\nexport const ORDER_DISPUTE_OPTIONS: readonly ListFilterOption[] = [\n { value: 'open', label: 'Open dispute' },\n { value: 'lost', label: 'Charged back' },\n { value: 'won', label: 'Dispute won' },\n { value: 'settled', label: 'Dispute closed' },\n]\n\n/** The clause the open-dispute banner's \"Show them\" sets. */\nexport const OPEN_DISPUTE_CLAUSE: ListFilterClause = {\n field: 'disputeKey',\n op: 'equals',\n value: 'open',\n}\n\n/**\n * The Customer clause the CRM's link seeds from `?email=` (AGL-2622).\n *\n * A whole address is `is`, served by the lower-cased key, so it finds that\n * buyer and no one whose address merely begins the same way. Anything else —\n * a domain, `@acme.com`, a name — is `contains`, a word prefix of the\n * address, so a domain finds every buyer at a company.\n */\nexport function ordersCustomerClause(raw: string): ListFilterClause | null {\n const value = raw.trim().replace(/^@+/, '')\n if (!value) return null\n const at = value.indexOf('@')\n return at > 0 && at < value.length - 1\n ? { field: 'customerEmail', op: 'equals', value }\n : { field: 'customerEmail', op: 'contains', value }\n}\n"],"names":["ORDER_CHANNEL_LABELS","ORDER_STATUS_LABELS","ORDER_LIST_FIELDS","column","kind","path","tokensPath","operators","lowerPath","presence","storedAs","verbatimTokens","ORDER_LIST_COLUMN_SORTS","direction","label","alone","ORDER_LIST_QUERY","fields","sorts","search","ORDER_LIST_HEADERS","orderLabel","customerEmail","channelKey","statusKey","createdAtMs","productIds","disputeKey","ORDER_LIST_SELECT_FIELDS","ORDER_STATUS_OPTIONS","Object","keys","map","status","value","ORDER_CHANNEL_OPTIONS","channel","ORDER_DISPUTE_OPTIONS","OPEN_DISPUTE_CLAUSE","field","op","ordersCustomerClause","raw","trim","replace","at","indexOf","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/orders-list-query.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport type {\n ListFilterClause,\n ListFilterOption,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport type {\n ListQueryDeclaration,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n ORDER_CHANNEL_LABELS,\n ORDER_STATUS_LABELS,\n type OrderChannel,\n type OrderStatus,\n} from '../model/commerce-orders'\n\n/*\n * THE ORDERS LIST, ON ITS QUERY (AGL-3321).\n *\n * `hosts/{hostId}/orders`, newest first. Every filter the grid's panel offers\n * and the quick search's word is a predicate on the ONE Firestore query that\n * pages the list (`planListQuery` through `useListQuery`), over the fields\n * every writer stamps with `orderListFields` — so page two of a filtered list\n * is page two of the matches, not page two of the store narrowed afterwards.\n *\n * One order: `createdAtMs` descending, the field every writer stamps. Each\n * filterable field is then ONE `(field, createdAtMs DESC)` composite, and\n * index merging serves every combination of them; the date range is on that\n * same field, so it adds none. Eight composites in all, pinned by\n * `orders-list-query.spec.ts` against `cloud/firebase-firestore.indexes.json`.\n *\n * What one query cannot hold is refused by name above the grid\n * (`ListQueryNotices`, labelled by `listQueryRefusals` from these fields,\n * headers and options), never matched over the rows it returned: the search,\n * Customer `contains`, Order `contains` and Product each take the query's one\n * array clause, so two of them together keep the first and refuse the second.\n *\n * A second range (a total, say) would be a new `sorts` entry and one more\n * composite per filterable field beneath it — eight — plus the range field's\n * own order; nothing else here moves.\n *\n * ## Every header sorts (AGL-3680)\n *\n * `ORDER_LIST_COLUMN_SORTS`: Date either way, and Order, Customer, Channel and\n * Status, each on the field `orderListFields` stamps on every order —\n * `number` (null on an order with none: POS, draft and channel orders are\n * unnumbered), `customerEmailLower`, `channel`, `status`.\n * `tools/scripts/backfill-orders-list-fields.mjs` stamps `number` and an\n * absent `customerEmailLower` on the orders written before. Every order but\n * the default is `alone`: served with no filter or search on, it costs no\n * composite on this unscoped list, and asked under a filter it falls back to\n * newest first and says so. Total is computed from the refunds, so it sorts\n * the page on screen.\n */\n\n/** The grid columns, each as the query reads it. */\nexport const ORDER_LIST_FIELDS: readonly ListFilterField[] = [\n {\n // The number as typed, `#1042` or `1042`, by word prefix.\n column: 'orderLabel',\n kind: 'text',\n path: 'number',\n tokensPath: 'orderLabelTokens',\n operators: ['contains'],\n },\n {\n // `is` a whole address; `contains` an address's piece or its domain.\n column: 'customerEmail',\n kind: 'text',\n path: 'customerEmail',\n lowerPath: 'customerEmailLower',\n tokensPath: 'customerEmailTokens',\n operators: ['contains', 'equals'],\n },\n {\n column: 'channelKey',\n kind: 'exact',\n path: 'channel',\n presence: 'always',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: 'statusKey',\n kind: 'exact',\n path: 'status',\n presence: 'always',\n operators: ['equals', 'isAnyOf'],\n },\n {\n column: 'createdAtMs',\n kind: 'date',\n path: 'createdAtMs',\n storedAs: 'millis',\n presence: 'always',\n operators: ['is', 'after', 'onOrAfter', 'before', 'onOrBefore'],\n },\n {\n // Every product in the order, matched whole — ids keep their case.\n column: 'productIds',\n kind: 'exact',\n path: 'productIds',\n tokensPath: 'productIds',\n verbatimTokens: true,\n operators: ['isAnyOf'],\n },\n {\n column: 'disputeKey',\n kind: 'exact',\n path: 'disputeKey',\n operators: ['equals', 'isAnyOf'],\n },\n]\n\n/**\n * The orders the grid's headers ask for, newest first FIRST — see \"Every\n * header sorts\" above.\n */\nexport const ORDER_LIST_COLUMN_SORTS: readonly ListQuerySort[] = [\n { path: 'createdAtMs', direction: 'desc', column: 'createdAtMs', label: 'Date' },\n { path: 'createdAtMs', direction: 'asc', column: 'createdAtMs', label: 'Date', alone: true },\n { path: 'number', direction: 'desc', column: 'orderLabel', label: 'Order', alone: true },\n { path: 'number', direction: 'asc', column: 'orderLabel', label: 'Order', alone: true },\n { path: 'customerEmailLower', direction: 'asc', column: 'customerEmail', label: 'Customer', alone: true },\n { path: 'customerEmailLower', direction: 'desc', column: 'customerEmail', label: 'Customer', alone: true },\n { path: 'channel', direction: 'asc', column: 'channelKey', label: 'Channel', alone: true },\n { path: 'channel', direction: 'desc', column: 'channelKey', label: 'Channel', alone: true },\n { path: 'status', direction: 'asc', column: 'statusKey', label: 'Status', alone: true },\n { path: 'status', direction: 'desc', column: 'statusKey', label: 'Status', alone: true },\n]\n\n/** The list's query: its fields, its header orders and its search. */\nexport const ORDER_LIST_QUERY: ListQueryDeclaration = {\n fields: ORDER_LIST_FIELDS,\n sorts: ORDER_LIST_COLUMN_SORTS,\n search: { tokensPath: 'searchTokens' },\n}\n\nexport const ORDER_LIST_HEADERS: Readonly<Record<string, string>> = {\n orderLabel: 'Order',\n customerEmail: 'Customer',\n channelKey: 'Channel',\n statusKey: 'Status',\n createdAtMs: 'Date',\n productIds: 'Product',\n disputeKey: 'Disputes',\n}\n\n/** The fields picked from choices, which the panel shows as a select. */\nexport const ORDER_LIST_SELECT_FIELDS: readonly string[] = [\n 'channelKey',\n 'statusKey',\n 'productIds',\n 'disputeKey',\n]\n\nexport const ORDER_STATUS_OPTIONS: readonly ListFilterOption[] = (\n Object.keys(ORDER_STATUS_LABELS) as OrderStatus[]\n).map((status) => ({ value: status, label: ORDER_STATUS_LABELS[status] }))\n\n/*\n * Every channel, `quote` included: a quote is listed here once it is paid\n * (AGL-4094), as the order it became, and \"Quote\" finds those orders. An\n * unpaid quote is never on this list (`commerce-quotes.ts`); the Quotes card\n * lists it.\n */\nexport const ORDER_CHANNEL_OPTIONS: readonly ListFilterOption[] = (\n Object.keys(ORDER_CHANNEL_LABELS) as OrderChannel[]\n).map((channel) => ({ value: channel, label: ORDER_CHANNEL_LABELS[channel] }))\n\n/*\n * The dispute badge's tones (`orderDisputeKey`), labelled as the badge\n * labels them. `lost` is the tone, not the raw dispute status: a dispute\n * that was WON also closes with money untouched, and listing it as charged\n * back would tell a merchant they lost a case they won.\n */\nexport const ORDER_DISPUTE_OPTIONS: readonly ListFilterOption[] = [\n { value: 'open', label: 'Open dispute' },\n { value: 'lost', label: 'Charged back' },\n { value: 'won', label: 'Dispute won' },\n { value: 'settled', label: 'Dispute closed' },\n]\n\n/** The clause the open-dispute banner's \"Show them\" sets. */\nexport const OPEN_DISPUTE_CLAUSE: ListFilterClause = {\n field: 'disputeKey',\n op: 'equals',\n value: 'open',\n}\n\n/**\n * The Customer clause the CRM's link seeds from `?email=` (AGL-2622).\n *\n * A whole address is `is`, served by the lower-cased key, so it finds that\n * buyer and no one whose address merely begins the same way. Anything else —\n * a domain, `@acme.com`, a name — is `contains`, a word prefix of the\n * address, so a domain finds every buyer at a company.\n */\nexport function ordersCustomerClause(raw: string): ListFilterClause | null {\n const value = raw.trim().replace(/^@+/, '')\n if (!value) return null\n const at = value.indexOf('@')\n return at > 0 && at < value.length - 1\n ? { field: 'customerEmail', op: 'equals', value }\n : { field: 'customerEmail', op: 'contains', value }\n}\n"],"names":["ORDER_CHANNEL_LABELS","ORDER_STATUS_LABELS","ORDER_LIST_FIELDS","column","kind","path","tokensPath","operators","lowerPath","presence","storedAs","verbatimTokens","ORDER_LIST_COLUMN_SORTS","direction","label","alone","ORDER_LIST_QUERY","fields","sorts","search","ORDER_LIST_HEADERS","orderLabel","customerEmail","channelKey","statusKey","createdAtMs","productIds","disputeKey","ORDER_LIST_SELECT_FIELDS","ORDER_STATUS_OPTIONS","Object","keys","map","status","value","ORDER_CHANNEL_OPTIONS","channel","ORDER_DISPUTE_OPTIONS","OPEN_DISPUTE_CLAUSE","field","op","ordersCustomerClause","raw","trim","replace","at","indexOf","length"],"mappings":"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"}
@@ -0,0 +1,30 @@
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
+ /**
18
+ * The customer's quote page (AGL-4094), where a quote's pay link opens.
19
+ *
20
+ * The component id is persisted in screen documents; never rename it. The
21
+ * path is where the site serves the page when it has no page of its own
22
+ * there. It is `pay-quote` rather than `quote` because a business that sells
23
+ * made-to-order work often has a "Request a quote" page at `/quote` already,
24
+ * and a page of the site's own at an address always wins over this one: a pay
25
+ * link there would open the request form.
26
+ */
27
+ export declare const QUOTE_PAGE_COMPONENT_ID = "quote-page";
28
+ export declare const QUOTE_PAGE_PATH = "pay-quote";
29
+ /** The page's address for one quote and its signed link token, relative to the site. */
30
+ export declare function quotePageHref(quoteId: string, token: string): string;
@@ -0,0 +1,35 @@
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
+ * The customer's quote page (AGL-4094), where a quote's pay link opens.
18
+ *
19
+ * The component id is persisted in screen documents; never rename it. The
20
+ * path is where the site serves the page when it has no page of its own
21
+ * there. It is `pay-quote` rather than `quote` because a business that sells
22
+ * made-to-order work often has a "Request a quote" page at `/quote` already,
23
+ * and a page of the site's own at an address always wins over this one: a pay
24
+ * link there would open the request form.
25
+ */ export const QUOTE_PAGE_COMPONENT_ID = 'quote-page';
26
+ export const QUOTE_PAGE_PATH = 'pay-quote';
27
+ /** The page's address for one quote and its signed link token, relative to the site. */ export function quotePageHref(quoteId, token) {
28
+ const query = new URLSearchParams({
29
+ q: quoteId,
30
+ t: token
31
+ });
32
+ return `/${QUOTE_PAGE_PATH}?${query.toString()}`;
33
+ }
34
+
35
+ //# sourceMappingURL=quote-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/constants/quote-page.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\n/**\n * The customer's quote page (AGL-4094), where a quote's pay link opens.\n *\n * The component id is persisted in screen documents; never rename it. The\n * path is where the site serves the page when it has no page of its own\n * there. It is `pay-quote` rather than `quote` because a business that sells\n * made-to-order work often has a \"Request a quote\" page at `/quote` already,\n * and a page of the site's own at an address always wins over this one: a pay\n * link there would open the request form.\n */\nexport const QUOTE_PAGE_COMPONENT_ID = 'quote-page'\n\nexport const QUOTE_PAGE_PATH = 'pay-quote'\n\n/** The page's address for one quote and its signed link token, relative to the site. */\nexport function quotePageHref(quoteId: string, token: string): string {\n const query = new URLSearchParams({ q: quoteId, t: token })\n return `/${QUOTE_PAGE_PATH}?${query.toString()}`\n}\n"],"names":["QUOTE_PAGE_COMPONENT_ID","QUOTE_PAGE_PATH","quotePageHref","quoteId","token","query","URLSearchParams","q","t","toString"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GACD,OAAO,MAAMA,0BAA0B,aAAY;AAEnD,OAAO,MAAMC,kBAAkB,YAAW;AAE1C,sFAAsF,GACtF,OAAO,SAASC,cAAcC,OAAe,EAAEC,KAAa;IAC1D,MAAMC,QAAQ,IAAIC,gBAAgB;QAAEC,GAAGJ;QAASK,GAAGJ;IAAM;IACzD,OAAO,CAAC,CAAC,EAAEH,gBAAgB,CAAC,EAAEI,MAAMI,QAAQ,IAAI;AAClD"}
@@ -23,6 +23,7 @@
23
23
  * I/O here.
24
24
  */
25
25
  import type { OrderLineModifier } from './product-modifiers';
26
+ import type { OrderQuote } from './commerce-quotes';
26
27
  import type { PaymentRisk } from '@aglyn/aglyn/app-utils/payment-risk';
27
28
  import type { ProductType } from './commerce';
28
29
  import type { StorefrontTaxMode } from './commerce-tax-decision';
@@ -31,7 +32,8 @@ import type { PosOfflineOrderStamp } from './commerce-pos-offline';
31
32
  import type { OrderFulfillmentMethod, OrderLocalDelivery, OrderPickup } from './order-local-fulfillment';
32
33
  export type OrderStatus = 'pending' | 'paid' | 'partially_fulfilled' | 'fulfilled' | 'delivered' | 'cancelled' | 'refunded';
33
34
  /**
34
- * Where the sale came through. `subscription` is a recurring cycle of a
35
+ * Where the sale came through. `quote` is an order a business quoted before
36
+ * the customer paid it (AGL-4093, `commerce-quotes.ts`). `subscription` is a recurring cycle of a
35
37
  * PHYSICAL subscription product (AGL-1750): each paid invoice mints one order
36
38
  * so fulfilment has something to pick, pack and label against, and so
37
39
  * recurring revenue reaches the surfaces that read `orders`. Digital and
@@ -39,7 +41,7 @@ export type OrderStatus = 'pending' | 'paid' | 'partially_fulfilled' | 'fulfille
39
41
  * there is nothing to ship, and AGL-1732's "a subscription is not an order"
40
42
  * stands for them.
41
43
  */
42
- export type OrderChannel = 'online' | 'pos' | 'draft' | 'subscription' | 'marketplace';
44
+ export type OrderChannel = 'online' | 'pos' | 'draft' | 'subscription' | 'marketplace' | 'quote';
43
45
  /**
44
46
  * Where an order sold on an outside channel came from (AGL-3638): a
45
47
  * marketplace that took the buyer's money and pays the merchant out. Written
@@ -83,7 +85,20 @@ export interface OrderChannelSource {
83
85
  }
84
86
  /** Snapshot of what was bought — self-contained for history. */
85
87
  export interface OrderLineItem {
88
+ /**
89
+ * The product sold. EMPTY on a line typed on a quote (`custom`), which no
90
+ * product stands behind: every reader that looks a product up by this id
91
+ * skips an empty one (AGL-4093).
92
+ */
86
93
  productId: string;
94
+ /** A line typed on a quote rather than picked from the catalog (AGL-4093). */
95
+ custom?: boolean;
96
+ /** Included at no charge (AGL-4093): `unitAmountCents` is 0. */
97
+ included?: boolean;
98
+ /** What the line is, in the business's words, beyond its name (AGL-4093). */
99
+ description?: string;
100
+ /** The product was tax-exempt when the line was priced (AGL-4093). */
101
+ taxExempt?: boolean;
87
102
  variantId?: string;
88
103
  /** Display snapshot at purchase time. */
89
104
  name: string;
@@ -623,6 +638,8 @@ export interface HostOrder {
623
638
  invoiceId?: string;
624
639
  /** Draft orders (AGL-287): the link sent to the buyer. */
625
640
  paymentLinkUrl?: string;
641
+ /** What only a quote has (channel `quote`, AGL-4093); see `commerce-quotes.ts`. */
642
+ quote?: OrderQuote;
626
643
  refundedCents?: number;
627
644
  /**
628
645
  * Line indexes an admin refunded BY NAME (AGL-2454), and therefore the lines
@@ -1120,6 +1137,12 @@ export declare function formatOrderNumber(order: Pick<HostOrder, 'number'>, docI
1120
1137
  * into the v1 shape for display; already-shaped orders pass through.
1121
1138
  */
1122
1139
  export declare function liftLegacyOrder(raw: Partial<HostOrder>): HostOrder;
1140
+ /**
1141
+ * What a report groups a line under: its product, or for a line typed on a
1142
+ * quote (no product) its name, so two different typed lines never pool into
1143
+ * one empty-id bucket.
1144
+ */
1145
+ export declare function orderLineProductKey(line: Pick<OrderLineItem, 'productId' | 'name'>): string;
1123
1146
  /**
1124
1147
  * Does this order contain `productId`?
1125
1148
  *
@@ -625,6 +625,14 @@ export function computeCheckoutSessionTotals(lineItems, session, parts) {
625
625
  }
626
626
  });
627
627
  }
628
+ /**
629
+ * What a report groups a line under: its product, or for a line typed on a
630
+ * quote (no product) its name, so two different typed lines never pool into
631
+ * one empty-id bucket.
632
+ */ export function orderLineProductKey(line) {
633
+ var _line_name;
634
+ return line.productId ? line.productId : `custom:${(_line_name = line.name) != null ? _line_name : ''}`;
635
+ }
628
636
  /**
629
637
  * Does this order contain `productId`?
630
638
  *
@@ -859,7 +867,8 @@ export function computeCheckoutSessionTotals(lineItems, session, parts) {
859
867
  pos: 'POS',
860
868
  draft: 'Draft',
861
869
  subscription: 'Subscription',
862
- marketplace: 'Marketplace'
870
+ marketplace: 'Marketplace',
871
+ quote: 'Quote'
863
872
  };
864
873
  /** Human label for a channel, tolerating a legacy/unknown value. */ export function orderChannelLabel(channel) {
865
874
  var _ref, _ORDER_CHANNEL_LABELS_;