@aglyn/plugins-commerce 1.0.0-beta.218 → 1.0.0-beta.219
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 +1 -0
- package/package.json +13 -13
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -19
- package/src/index.js.map +1 -1
- package/src/lib/components/cart.js +2 -1
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/catalog-organization-card.component.js +44 -18
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +2 -1
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/member-posts-card.component.js +1 -1
- package/src/lib/components/console/member-posts-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +8 -7
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +114 -63
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-zones.d.ts +184 -0
- package/src/lib/components/console/product-zones.js +54 -0
- package/src/lib/components/console/product-zones.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.js +3 -2
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.d.ts +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +2 -1
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/store-settings-card.component.js +2 -2
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -1
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
- package/src/lib/components/product-detail.js +2 -1
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.js +1 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/constants/storefront-payments.d.ts +33 -0
- package/src/lib/constants/storefront-payments.js +35 -0
- package/src/lib/constants/storefront-payments.js.map +1 -0
- package/src/lib/declarations.server.d.ts +3 -1
- package/src/lib/declarations.server.js +19 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-record-routes.d.ts +27 -0
- package/src/lib/model/commerce-record-routes.js +72 -0
- package/src/lib/model/commerce-record-routes.js.map +1 -0
- package/src/lib/model/commerce-search-sources.d.ts +21 -0
- package/src/lib/model/commerce-search-sources.js +40 -0
- package/src/lib/model/commerce-search-sources.js.map +1 -0
- package/src/lib/model/storefront-tax-summary.d.ts +121 -0
- package/src/lib/model/storefront-tax-summary.js +148 -0
- package/src/lib/model/storefront-tax-summary.js.map +1 -0
- package/src/lib/plan-entitlements.d.ts +35 -0
- package/src/lib/plan-entitlements.js +54 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +9 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +135 -168
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +2 -2
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout.js +2 -2
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/connect.js +5 -5
- package/src/lib/server/connect.js.map +1 -1
- package/src/lib/server/draft-order.js +2 -2
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/membership-content.js +1 -1
- package/src/lib/server/membership-content.js.map +1 -1
- package/src/lib/server/pos-order.js +2 -2
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/product-record-index.d.ts +19 -0
- package/src/lib/server/product-record-index.js +115 -0
- package/src/lib/server/product-record-index.js.map +1 -0
- package/src/lib/server/reserve.js +2 -2
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/revenue-source.d.ts +130 -0
- package/src/lib/server/revenue-source.js +286 -0
- package/src/lib/server/revenue-source.js.map +1 -0
- package/src/lib/server/tax-profile.d.ts +8 -5
- package/src/lib/server/tax-profile.js +22 -5
- package/src/lib/server/tax-profile.js.map +1 -1
- package/src/lib/server/tax-return-source.d.ts +125 -0
- package/src/lib/server/tax-return-source.js +524 -0
- package/src/lib/server/tax-return-source.js.map +1 -0
- package/src/lib/server/tax-summary.d.ts +92 -0
- package/src/lib/server/tax-summary.js +258 -0
- package/src/lib/server/tax-summary.js.map +1 -0
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js +23 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.d.ts +33 -0
- package/src/lib/tenant-emails.js +511 -0
- package/src/lib/tenant-emails.js.map +1 -0
- package/src/lib/testing/stand-in-crm-record-routes.d.ts +25 -0
- package/src/lib/testing/stand-in-crm-record-routes.js +46 -0
- package/src/lib/testing/stand-in-crm-record-routes.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/order-detail-dialog.component.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'use client'\n\nimport * as CommerceModel from '../../model'\nimport { escapeHtml } from '../../utils/escape-html'\n// The CRM's address from the SHARED builder, not the plugin (AGL-2622): the\n// CRM already reaches marketing, and marketing reaches commerce, so an\n// import of the CRM from here would close a cycle between the three\n// plugins. The shared builder is pinned against the CRM's own routes by a\n// spec, so the address is the same one the hub resolves.\nimport { crmContactByEmailHref } from '@aglyn/aglyn'\nimport { describePaymentRisk } from '@aglyn/aglyn/app-utils/payment-risk'\nimport { AppLink, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { doc, runTransaction, updateDoc } from 'firebase/firestore'\nimport { useParams } from 'next/navigation'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface OrderDetailDialogProps {\n hostId: string\n order: (CommerceModel.HostOrder & { $id: string }) | null\n onClose: () => void\n}\n\nconst STATUS_COLOR: Record<\n string,\n 'default' | 'success' | 'warning' | 'error' | 'info'\n> = {\n pending: 'warning',\n paid: 'info',\n partially_fulfilled: 'info',\n fulfilled: 'success',\n delivered: 'success',\n cancelled: 'default',\n refunded: 'error',\n}\n\n/**\n * The dispute chip's colour (AGL-1796).\n *\n * `open` is WARNING rather than error because it is the case that still has a\n * deadline on it — the merchant can still act, and a red chip on a case they\n * might win says the money is gone when it is not. `lost` is the one that took\n * the money, and shares `refunded`'s red for that reason.\n */\nexport const DISPUTE_COLOR: Record<\n CommerceModel.OrderDisputeTone,\n 'default' | 'success' | 'warning' | 'error'\n> = {\n open: 'warning',\n lost: 'error',\n won: 'success',\n settled: 'default',\n}\n\nconst usd = (cents: number | undefined) =>\n `$${((cents ?? 0) / 100).toFixed(2)}`\n\n/**\n * Order detail (AGL-287): timeline, line items, totals, and the actions\n * the status machine allows — fulfill with tracking, refund, cancel, mark\n * delivered (each a server route that re-asks the transition under the\n * write), notes, printable packing slip.\n */\nexport function OrderDetailDialog(props: OrderDetailDialogProps) {\n const { hostId, order: rawOrder, onClose } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [tracking, setTracking] = useState<{ carrier: string; number: string } | null>(null)\n const [note, setNote] = useState('')\n const [busy, setBusy] = useState(false)\n\n const order = rawOrder ? CommerceModel.liftLegacyOrder(rawOrder) : null\n const orderId = rawOrder?.$id\n /*\n * Where the buyer's CONTACT is (AGL-2622). A checkout that carried an\n * address updated a person in the CRM — a customer, with this order on\n * their timeline — and the dialog links there by the address, because\n * the contact's id is minted at capture and nothing on the order holds\n * it. The Contacts list is the lookup and opens the record on one match.\n * A guest checkout with no address updated nobody, so there is no link.\n * Built from the route params already in the URL — no document is read\n * to draw a link — and absent until they settle, so the dialog never\n * offers a link to a half-built address.\n */\n const params = useParams<{ orgSlug?: string; host?: string }>()\n const customerCrmHref =\n params?.orgSlug && params?.host && order?.customerEmail\n ? crmContactByEmailHref(\n { orgSlug: String(params.orgSlug), host: String(params.host) },\n String(order.customerEmail),\n )\n : null\n\n /**\n * The one client write left in this dialog, and it is timeline-only ON\n * PURPOSE: notes carry no status, so `ORDER_TRANSITIONS` has nothing to say\n * about them and they stay out of the server routes' way. Every status\n * write goes through a route that re-asks the transition under the write —\n * cancel (AGL-1818), fulfil and mark-delivered (AGL-1819), refund\n * (AGL-1696). The moment a status rides along with a note, it must move\n * too; `order-fulfill-wiring.spec.tsx` pins the patch to `timeline` alone.\n */\n const write = useCallback(\n async (patch: Record<string, unknown>) => {\n if (!orderId) return\n await updateDoc(doc(firestore, 'hosts', hostId, 'orders', orderId), patch)\n },\n [firestore, hostId, orderId],\n )\n\n /**\n * Fulfil and mark-delivered go through `/api/commerce/fulfill-order`\n * (AGL-1819), not a client write — the same stale-dialog hole the cancel\n * swap (AGL-1818) closed: `can(…)` below only decides whether to RENDER\n * the buttons, so a dialog held open while the order was refunded in\n * another tab used to write `fulfilled` straight onto it. The route\n * re-asks `canTransitionOrder` inside the transaction that writes the\n * status, and that race now comes back as a 409 carrying the state that\n * refused.\n *\n * The messages follow the AGL-1784/1786 contract — say what happened: a\n * 409 is the guard refusing, not the machinery failing; the route's own\n * 500 is a rolled-back transaction (nothing landed, retry is safe and\n * correct); an answer with no JSON word from the handler, or no answer at\n * all, is NOT KNOWN — never \"nothing happened\". A retry is safe in every\n * case because the route writes once: a repeat finds the order already in\n * the target status and returns `already` without appending a second copy\n * of the same fulfillment.\n */\n const transition = useCallback(\n async (\n to: 'fulfilled' | 'delivered',\n extra: Record<string, string>,\n copy: { done: string; already: string; subject: string; action: string },\n ) => {\n if (!orderId) return false\n setBusy(true)\n try {\n let response: Response\n try {\n response = await authorizedFetch(\n user,\n '/api/commerce/fulfill-order',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, orderId, to, ...extra }),\n },\n )\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n `The request did not complete, so it is NOT known whether ${copy.subject}. ` +\n 'Reopen the order to check — retrying is safe, the server ' +\n 'writes it once.',\n { variant: 'error', allowDuplicate: true },\n )\n return false\n }\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok) {\n if (response.status === 409) {\n // The transition guard refusing: the order moved on (refunded,\n // cancelled) while this dialog was open. The route's message\n // names the state that refused it, and nothing was written.\n enqueueSnackbar(\n payload?.error ?? `This order can no longer be ${to}`,\n {\n variant: 'warning',\n allowDuplicate: true,\n },\n )\n } else if (payload?.error && response.status < 500) {\n // A refusal that wrote nothing — session, role, or an order that\n // is not there. The route's own word, verbatim.\n enqueueSnackbar(payload.error, {\n variant: 'error',\n allowDuplicate: true,\n })\n } else if (payload?.error) {\n // The route's 500 is a rolled-back transaction: nothing landed.\n // Only the handler's own JSON word licenses saying so.\n enqueueSnackbar(\n `${payload.error} — nothing changed, the order is as it was. Retrying is safe.`,\n { variant: 'error', allowDuplicate: true },\n )\n } else {\n // No JSON word from the handler (a gateway page, a proxy\n // timeout): whether the write landed is NOT known.\n enqueueSnackbar(\n `The ${copy.action} failed (${response.status}) and it is NOT ` +\n 'known whether it landed. Reopen the order to check — ' +\n 'retrying is safe, the server writes it once.',\n { variant: 'error', allowDuplicate: true },\n )\n }\n return false\n }\n enqueueSnackbar(payload?.already ? copy.already : copy.done, {\n variant: 'success',\n persist: false,\n })\n return true\n } finally {\n setBusy(false)\n }\n },\n [orderId, user, hostId, enqueueSnackbar],\n )\n\n const handleFulfill = useCallback(async () => {\n if (!order || !tracking) return\n const done = await transition(\n 'fulfilled',\n {\n ...(tracking.carrier ? { carrier: tracking.carrier } : {}),\n ...(tracking.number ? { trackingNumber: tracking.number } : {}),\n },\n {\n done: 'Order fulfilled',\n already: 'Order was already fulfilled',\n subject: 'the order was fulfilled',\n action: 'fulfillment',\n },\n )\n // The refused or unknown case keeps the form open — there is nothing to\n // celebrate, and the tracking is not yet recorded anywhere.\n if (done) setTracking(null)\n }, [order, tracking, transition])\n\n const handleDelivered = useCallback(async () => {\n if (!order) return\n await transition(\n 'delivered',\n {},\n {\n done: 'Order marked delivered',\n already: 'Order was already marked delivered',\n subject: 'the order was marked delivered',\n action: 'delivery update',\n },\n )\n }, [order, transition])\n\n /**\n * Cancel goes through `/api/commerce/cancel-order` (AGL-1818), not a client\n * write. The status flip and the stock release are one transaction there,\n * and the route re-asks `canTransitionOrder` under the same lock — the\n * `can('cancelled')` gate below only decides whether to RENDER the button,\n * so a dialog held open while the order ships in another tab used to write\n * `cancelled` straight onto a `fulfilled` order. Now that race comes back\n * as a 409 carrying the state that refused it.\n *\n * The messages follow the AGL-1784/1786 contract — say what happened:\n * a 409 is the guard refusing, not the cancel machinery failing; the\n * route's own 500 is a rolled-back transaction (nothing landed, retry is\n * safe and correct); an answer with no JSON word from the handler, or no\n * answer at all, is NOT KNOWN — never \"nothing happened\". A retry is safe\n * in every case because the route cancels once: a second cancel finds\n * `cancelled` and returns success without touching stock.\n */\n const handleCancel = useCallback(async () => {\n if (!order || !orderId) return\n const confirmed = await confirm({\n title: 'Cancel this order?',\n description:\n 'The buyer is NOT refunded automatically — refund first if they paid.',\n confirmationText: 'Cancel order',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy(true)\n try {\n let response: Response\n try {\n response = await authorizedFetch(user, '/api/commerce/cancel-order', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, orderId }),\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n 'The request did not complete, so it is NOT known whether the ' +\n 'order was canceled. Reopen the order to check — retrying is ' +\n 'safe, the server cancels once.',\n { variant: 'error', allowDuplicate: true },\n )\n return\n }\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok) {\n if (response.status === 409) {\n // The transition guard refusing: the order moved on (shipped,\n // refunded) while this dialog was open. The route's message names\n // the state that refused it, and nothing was written.\n enqueueSnackbar(\n payload?.error ?? 'This order can no longer be canceled',\n {\n variant: 'warning',\n allowDuplicate: true,\n },\n )\n } else if (payload?.error && response.status < 500) {\n // A refusal that wrote nothing — session, role, or an order that\n // is not there. The route's own word, verbatim.\n enqueueSnackbar(payload.error, {\n variant: 'error',\n allowDuplicate: true,\n })\n } else if (payload?.error) {\n // The route's 500 is a rolled-back transaction: nothing landed and\n // the order is still open. Only the handler's own JSON word\n // licenses saying so.\n enqueueSnackbar(\n `${payload.error} — nothing changed, the order is still open. Retrying is safe.`,\n { variant: 'error', allowDuplicate: true },\n )\n } else {\n // No JSON word from the handler (a gateway page, a proxy timeout):\n // whether the cancel landed is NOT known.\n enqueueSnackbar(\n `The cancel failed (${response.status}) and it is NOT known ` +\n 'whether it landed. Reopen the order to check — retrying is ' +\n 'safe, the server cancels once.',\n { variant: 'error', allowDuplicate: true },\n )\n }\n return\n }\n const units = Number(payload?.units ?? 0)\n enqueueSnackbar(\n payload?.alreadyCancelled\n ? 'Order was already canceled'\n : units > 0\n ? `Order canceled — ${units} ${units === 1 ? 'unit' : 'units'} returned to stock`\n : 'Order canceled',\n { variant: 'success', persist: false },\n )\n } finally {\n setBusy(false)\n }\n }, [order, orderId, user, hostId, confirm, enqueueSnackbar])\n\n /**\n * Idempotency key for ONE refund attempt (AGL-1696), the same shape the POS\n * register uses for a sale (AGL-1691).\n *\n * Minted lazily so a retry of this attempt — a lost response, a second\n * click, the admin reopening the dialog after a timeout — reuses it and the\n * server replays instead of sending the money again. Retired on a definitive\n * answer, which makes the NEXT refund a new attempt: two partial refunds of\n * the same amount on the same order are two real refunds, and keying on the\n * order (or the amount) would silently swallow the second.\n *\n * NOT retired on a network failure or a 5xx — that is precisely the window\n * the key exists to cover.\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [orderId])\n\n /**\n * `lineItemIds` scopes the refund to named lines (AGL-2454).\n *\n * Without it a partial refund is an AMOUNT and nothing else, so the product\n * cannot know which goods came back and revokes nothing — a 99%-refunded\n * order kept its downloads, licence keys, gated pages and verified review.\n * Naming the lines is the only way that question gets an honest answer, and\n * the route derives the amount FROM the lines rather than trusting a figure\n * typed beside them.\n */\n const handleRefund = useCallback(async (lineItemIds?: number[]) => {\n if (!order || !orderId) return\n const lines = order.lineItems ?? []\n const scoped = (lineItemIds ?? []).length > 0\n // The SERVER's figure, not a second derivation of it. This was\n // the list value `unitAmountCents x quantity`, which on a discounted order\n // is more than the buyer paid — so the dialog quoted one number and the\n // route refunded another. One function answers both.\n const scopedCents = CommerceModel.orderLineRefundCents(\n order,\n lineItemIds ?? [],\n )\n const confirmed = await confirm({\n title: scoped ? 'Refund this line?' : 'Refund this order?',\n description:\n (scoped\n ? `Refunds ${usd(scopedCents)} for ${(lineItemIds ?? [])\n .map((index) => lines[index]?.name ?? `line ${index}`)\n .join(', ')} through Stripe, and withdraws that line's ` +\n 'downloads, license keys and gated content. A license key already ' +\n 'sent to the buyer is retired, not returned to your pool — they ' +\n 'still hold the string, so reissuing it would give two people one ' +\n 'working key.'\n : `Refunds ${usd(\n (order.totals?.totalCents ?? order.amountCents ?? 0) -\n (order.refundedCents ?? 0),\n )} to the buyer through Stripe.`) +\n // An open INQUIRY keeps this button live on purpose (AGL-1820):\n // Stripe names a full refund as the way to resolve one before it\n // escalates to a chargeback. Said here so the admin reads the refund\n // as the documented exit, not a risk taken against the badge.\n (CommerceModel.orderHasOpenDispute(order) &&\n !CommerceModel.orderDisputeBlocksRefund(order)\n ? ' An inquiry is open on this charge — a full refund resolves it' +\n ' before it can become a chargeback.'\n : ''),\n confirmationText: 'Refund',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy(true)\n // `randomUUID` needs a secure context, which the console always is, but\n // fall back rather than throw on a refund.\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await authorizedFetch(user, '/api/commerce/refund', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n orderId,\n ...(scoped ? { lineItemIds } : {}),\n }),\n })\n const payload = await response.json()\n // A definitive answer ends this attempt either way: the next refund is a\n // new one and must get a new key. A 5xx or a thrown fetch keeps the key,\n // so pressing Refund again dedupes rather than sends a second transfer.\n if (response.status < 500) attemptKey.current = ''\n if (!response.ok) {\n // A 409 is a guard REFUSING — the status machine, or the open-dispute\n // block (AGL-1809), whose body explains what to do instead — not the\n // refund machinery failing. The route wrote nothing, so it surfaces\n // verbatim as a warning, same as the cancel path's 409 (AGL-1818).\n enqueueSnackbar(payload?.error ?? 'Refund failed', {\n variant: response.status === 409 ? 'warning' : 'error',\n allowDuplicate: true,\n })\n } else {\n enqueueSnackbar('Refund issued', { variant: 'success', persist: false })\n }\n } finally {\n setBusy(false)\n }\n }, [order, orderId, user, hostId, confirm, enqueueSnackbar])\n\n /**\n * Answer the restock question a reversal flagged (AGL-1806).\n *\n * The answer MOVES NO STOCK, which is AGL-1797's ledger argument and it is\n * binding: the products hub's \"Adjust stock\" is the one stock writer (it\n * writes the counts AND the `InventoryAdjustment` row the hub renders as\n * history), so \"Restocked\" here is clicked AFTER the merchant used it, and\n * \"No restock\" says the goods are not recoverable. Either answer only sets\n * the `resolution` triple `OrderRestockCheck` declares and says so on the\n * timeline.\n *\n * A TRANSACTION rather than the dialog's plain `write`, keyed on the\n * rendered `flaggedAtMs`, because an answer must land ONCE. The flag writer\n * re-asks an ANSWERED question after the next reversal, and the cancel\n * route (AGL-1808) answers an open one itself when it returns the stock —\n * so a stale dialog writing blind would either re-answer a settled question\n * or replace a brand-new one with a resolved copy of the old. The\n * transaction re-reads on the server (the web SDK will not run one from\n * cache — the products hub's `writeGuardedBySeed` refusal of unconfirmed\n * data, by other means) and refuses both cases with the truth: nothing was\n * written.\n */\n const handleRestockAnswer = useCallback(\n async (resolution: 'restocked' | 'dismissed') => {\n const check = order?.restockCheck\n if (!order || !orderId || !check || check.resolution) return\n setBusy(true)\n try {\n const reference = doc(firestore, 'hosts', hostId, 'orders', orderId)\n const verdict = await runTransaction(firestore, async (transaction) => {\n const snapshot = await transaction.get(reference)\n if (!snapshot.exists()) return 'gone'\n const current = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n const fresh = current.restockCheck\n if (!fresh || fresh.resolution) return 'answered'\n if (fresh.flaggedAtMs !== check.flaggedAtMs) return 'changed'\n const resolvedAtMs = Date.now()\n const uid = String((user as any)?.uid ?? '')\n transaction.update(reference, {\n restockCheck: {\n ...fresh,\n resolution,\n resolvedAtMs,\n ...(uid ? { resolvedBy: uid } : {}),\n } satisfies CommerceModel.OrderRestockCheck,\n // The SERVER's timeline, not the dialog's — a note landed from\n // another tab must survive the append.\n timeline: CommerceModel.appendOrderEvent(\n current,\n 'restock-check',\n resolution === 'restocked'\n ? 'answered — restocked'\n : 'answered — no restock',\n resolvedAtMs,\n ),\n })\n return 'recorded'\n })\n if (verdict === 'recorded') {\n enqueueSnackbar(\n resolution === 'restocked'\n ? 'Recorded as restocked'\n : 'Recorded — no restock',\n { variant: 'success', persist: false },\n )\n } else if (verdict === 'answered') {\n // The cancel route, or another admin, got there first.\n enqueueSnackbar(\n 'This restock question was already answered — nothing changed.',\n { variant: 'info', persist: false },\n )\n } else if (verdict === 'changed') {\n // A later reversal re-asked; this dialog shows the OLD question.\n enqueueSnackbar(\n 'The restock question changed while this dialog was open — ' +\n 'nothing was written. Reopen the order to see the current one.',\n { variant: 'warning', allowDuplicate: true },\n )\n } else {\n enqueueSnackbar('This order no longer exists — nothing was written.', {\n variant: 'warning',\n allowDuplicate: true,\n })\n }\n } catch (error) {\n // A transaction is all-or-nothing, and a thrown one did not commit.\n console.error(error)\n enqueueSnackbar(\n 'The answer was not recorded — it needs a live connection and the ' +\n 'editor or admin role. Nothing changed; answering again is safe.',\n { variant: 'error', allowDuplicate: true },\n )\n } finally {\n setBusy(false)\n }\n },\n [order, orderId, firestore, hostId, user, enqueueSnackbar],\n )\n\n const handleNote = useCallback(async () => {\n if (!order || !note.trim()) return\n await write({\n timeline: CommerceModel.appendOrderEvent(order, 'note', note.trim().slice(0, 500)),\n })\n setNote('')\n }, [order, note, write])\n\n const handlePackingSlip = useCallback(() => {\n if (!order) return\n const win = window.open('', '_blank', 'width=600,height=800')\n if (!win) return\n // ESCAPED (AGL-2283). This is `document.write` into an `about:blank`\n // popup, which INHERITS the console's origin — and `order.shippingAddress`\n // is copied verbatim from Stripe's `shipping_details`, i.e. typed by the\n // SHOPPER. A name of `<img src=x onerror=…>` ran script against the\n // merchant's own authenticated session. React guards nothing here because\n // none of this is React. Line names and SKUs are merchant-authored and\n // lower severity, but they are the same construction and get the same\n // treatment rather than a case-by-case argument.\n const rows = (order.lineItems ?? [])\n .map(\n (line) =>\n `<tr><td>${escapeHtml(line.quantity)}×</td><td>${escapeHtml(\n line.name,\n )}${\n line.variantLabel ? ` — ${escapeHtml(line.variantLabel)}` : ''\n }</td><td>${escapeHtml(line.sku ?? '')}</td></tr>`,\n )\n .join('')\n const address = order.shippingAddress\n win.document.write(\n `<h2>Packing slip ${escapeHtml(\n CommerceModel.formatOrderNumber(order, orderId),\n )}</h2>` +\n (address\n ? `<p>${[address.name, address.line1, address.line2, `${address.city ?? ''} ${address.state ?? ''} ${address.postalCode ?? ''}`, address.country].filter(Boolean).map((part) => escapeHtml(part)).join('<br/>')}</p>`\n : '') +\n `<table border=\"0\" cellpadding=\"6\">${rows}</table>`,\n )\n win.document.close()\n // Print from the opener, not from an injected inline script (AGL-523) —\n // the popup inherits this document's CSP, where an un-nonced inline\n // script is blocked under the enforcing policy. Same reasoning as the POS\n // receipt.\n win.focus()\n win.print()\n }, [order, orderId])\n\n if (!order) return null\n const totals = order.totals\n const can = (to: CommerceModel.OrderStatus) => CommerceModel.canTransitionOrder(order.status, to)\n // A lost chargeback leaves `status: 'refunded'` (AGL-1787), so the status\n // chip alone tells the merchant they chose this. The badge is the correction.\n const dispute = CommerceModel.describeOrderDispute(order)\n // An early fraud warning or a Radar review on the payment (AGL-3360).\n const paymentRisk = describePaymentRisk(order.paymentRisk)\n const reversal = CommerceModel.splitOrderReversal(order)\n // The restock question a reversal flagged, while it is still open\n // (AGL-1806). Absent `resolution` IS the open state — the writer only ever\n // re-flags an answered check (AGL-1797).\n const restock =\n order.restockCheck && !order.restockCheck.resolution\n ? order.restockCheck\n : null\n // Whether the reversal NAMED the lines this question is about (AGL-2325).\n // AGL-2454 gave a refund a line selection, and `flagOrderRestock` now scopes\n // the question to it — so the \"we cannot tell which goods came back\" caveat\n // below is true of an amount-only reversal and false of this one. Asked of\n // the order's own withdrawal record rather than of a flag on the check, so a\n // prompt written before line indexes existed reads as the amount-only case\n // it was.\n const restockLinesNamed = Boolean(\n restock?.lines?.length &&\n restock.lines.every(\n (line) =>\n line.lineIndex != null &&\n CommerceModel.orderLineRefunded(order, line.lineIndex),\n ),\n )\n\n return (\n <Dialog open onClose={onClose} maxWidth=\"sm\" fullWidth>\n <DialogTitle>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <span>{`Order ${CommerceModel.formatOrderNumber(order, orderId)}`}</span>\n <Chip\n label={order.status.replace('_', ' ')}\n size=\"small\"\n color={STATUS_COLOR[order.status] ?? 'default'}\n variant=\"outlined\"\n />\n {dispute ? (\n <Tooltip title={dispute.detail}>\n <Chip\n label={dispute.label}\n size=\"small\"\n color={DISPUTE_COLOR[dispute.tone]}\n variant=\"filled\"\n />\n </Tooltip>\n ) : null}\n {paymentRisk ? (\n <Tooltip title={paymentRisk.detail}>\n <Chip\n label={paymentRisk.label}\n size=\"small\"\n color=\"warning\"\n variant=\"filled\"\n />\n </Tooltip>\n ) : null}\n {order.channel && order.channel !== 'online' ? (\n <Chip label={order.channel} size=\"small\" variant=\"outlined\" />\n ) : null}\n </Stack>\n </DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {[order.customerName, order.customerEmail]\n .filter(Boolean)\n .join(' · ') || 'Guest buyer'}\n </Typography>\n {(order.lineItems ?? []).map((line, index) => {\n // WHAT THIS LINE IS STILL ENTITLED TO, per line (AGL-2454). A refund\n // used to be a number on the order and nothing more, so the merchant\n // could not see — and the product could not enforce — which goods had\n // come back.\n const withdrawn = CommerceModel.orderLineRefunded(order, index)\n return (\n <Stack key={index} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography\n variant=\"body2\"\n sx={{\n flex: 1,\n ...(withdrawn ? { textDecoration: 'line-through' } : {}),\n }}\n color={withdrawn ? 'text.secondary' : 'text.primary'}\n >\n {`${line.quantity}× ${line.name}` +\n (line.variantLabel ? ` — ${line.variantLabel}` : '')}\n </Typography>\n {withdrawn ? (\n <Chip label=\"refunded\" size=\"small\" color=\"error\" variant=\"outlined\" />\n ) : can('refunded') &&\n !CommerceModel.orderDisputeBlocksRefund(order) ? (\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy}\n onClick={() => handleRefund([index])}\n >\n {'Refund line'}\n </Button>\n ) : null}\n <Typography variant=\"body2\">\n {usd(line.unitAmountCents * line.quantity)}\n </Typography>\n </Stack>\n )\n })}\n {CommerceModel.orderRefundState(order) !== 'none' ? (\n // The state, said in words (AGL-2454). \"Partially refunded, no lines\n // withdrawn\" is the honest reading of an amount-only refund, and it\n // is what tells a merchant their buyer still has the downloads.\n <Typography variant=\"caption\" color=\"error\">\n {CommerceModel.orderRefundSummary(order)}\n </Typography>\n ) : null}\n {totals ? (\n <>\n <Divider />\n {(\n [\n ['Items', totals.itemsCents],\n ['Shipping', totals.shippingCents],\n ['Tax', totals.taxCents],\n ['Discount', -totals.discountCents],\n ['Total', totals.totalCents],\n ] as Array<[string, number]>\n ).map(([label, cents]) => (\n <Stack key={label} direction=\"row\">\n <Typography\n variant=\"body2\"\n sx={{ flex: 1 }}\n color={label === 'Total' ? 'text.primary' : 'text.secondary'}\n >\n {label}\n </Typography>\n <Typography\n variant=\"body2\"\n sx={{ fontWeight: label === 'Total' ? 600 : 400 }}\n >\n {`${cents < 0 ? '-' : ''}${usd(Math.abs(cents))}`}\n </Typography>\n </Stack>\n ))}\n {/*\n HOW the tax on this order was computed (AGL-2451). The `Tax` row\n above states an amount and nothing about which machinery produced\n it, and a merchant reading one order could not tell Stripe Tax\n (computed against Aglyn's registrations) from their own configured\n rate. Those are different facts about whose registration the money\n is held under, and the order is where a merchant looks.\n\n Describes the mechanism and makes NO remittance determination —\n the constraint AGL-2440's report ships under. An order written\n before the stamp existed says so, rather than being rendered as\n \"no tax\": absent means NOT RECORDED, and `describeOrderTaxMode`\n is where that distinction is kept.\n */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {CommerceModel.describeOrderTaxMode(order.taxMode)}\n </Typography>\n {/*\n Both figures live in `refundedCents` (AGL-1787), so a lost\n chargeback used to render as \"Refunded $62.00\" — the merchant's\n own decision, spelled the same as money taken from them. Split by\n door, and show BOTH when a partial refund and a chargeback each\n took a piece.\n */}\n {reversal.refundedCents ? (\n <Typography variant=\"caption\" color=\"error\">\n {`Refunded ${usd(reversal.refundedCents)}`}\n </Typography>\n ) : null}\n {reversal.chargedBackCents ? (\n <Typography variant=\"caption\" color=\"error\">\n {`Charged back ${usd(reversal.chargedBackCents)}`}\n </Typography>\n ) : null}\n </>\n ) : null}\n {restock ? (\n <>\n <Divider />\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Restock check'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${restock.units} ${\n restock.units === 1 ? 'unit' : 'units'\n } may need restocking after this ${\n restock.kind === 'chargeback' ? 'chargeback' : 'refund'\n }.` +\n // The chargeback default is NO, same as the timeline's\n // wording: the shopper kept the item and took the money.\n (restock.kind === 'chargeback'\n ? ' The shopper kept the goods unless they actually came back.'\n : '') +\n // On a partial the flagged units are the MOST it could be —\n // UNLESS the refund named its lines (AGL-2325), in which\n // case the question is scoped to exactly those and the\n // merchant is only being asked whether the goods came back.\n (restock.fullyReversed\n ? ''\n : restockLinesNamed\n ? ' Only part of the money came back: these are the lines withdrawn by this refund, so the units are theirs — only you know whether the goods came back.'\n : ' Only part of the money came back, so these units are an upper bound — only you know which goods returned.')}\n </Typography>\n {restock.lines.map((line, index) => (\n <Typography key={index} variant=\"caption\" color=\"text.secondary\">\n {`${line.quantity}× ${line.name ?? line.productId}` +\n (line.variantLabel ? ` — ${line.variantLabel}` : '')}\n </Typography>\n ))}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'If goods came back, put them on the shelf with \"Adjust ' +\n 'stock\" in the products hub first — these answers move no ' +\n 'stock, they only clear this question.'}\n </Typography>\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={busy}\n onClick={() => handleRestockAnswer('restocked')}\n >\n {'Restocked'}\n </Button>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => handleRestockAnswer('dismissed')}\n >\n {'No restock'}\n </Button>\n </Stack>\n </Stack>\n </>\n ) : null}\n <Divider />\n <Typography variant=\"subtitle2\">{'Timeline'}</Typography>\n {(order.timeline ?? [])\n .slice()\n .reverse()\n .map((event, index) => (\n <Typography key={index} variant=\"caption\" color=\"text.secondary\">\n {`${new Date(event.atMs).toLocaleString()} — ${event.event}` +\n (event.detail ? `: ${event.detail}` : '')}\n </Typography>\n ))}\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Add note\"\n value={note}\n onChange={(event) => setNote(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <Button size=\"small\" disabled={!note.trim()} onClick={handleNote}>\n {'Add'}\n </Button>\n </Stack>\n {tracking ? (\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Carrier\"\n value={tracking.carrier}\n onChange={(event) =>\n setTracking({ ...tracking, carrier: event.target.value })\n }\n size=\"small\"\n sx={{ width: 140 }}\n placeholder=\"UPS\"\n />\n <TextField\n label=\"Tracking number\"\n value={tracking.number}\n onChange={(event) =>\n setTracking({ ...tracking, number: event.target.value })\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={busy}\n onClick={handleFulfill}\n >\n {'Fulfill'}\n </Button>\n </Stack>\n ) : null}\n {order.paymentLinkUrl && order.status === 'pending' ? (\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n navigator.clipboard.writeText(String(order.paymentLinkUrl))\n enqueueSnackbar('Payment link copied', {\n variant: 'success',\n persist: false,\n })\n }}\n >\n {'Copy payment link'}\n </Button>\n ) : null}\n </DialogContent>\n <DialogActions>\n {customerCrmHref ? (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={customerCrmHref}\n >\n {'View customer in CRM'}\n </Button>\n ) : null}\n <Button onClick={handlePackingSlip}>{'Packing slip'}</Button>\n {can('cancelled') ? (\n <Button color=\"error\" disabled={busy} onClick={handleCancel}>\n {'Cancel order'}\n </Button>\n ) : null}\n {can('refunded') ? (\n // Disabled on `orderDisputeBlocksRefund`, NOT the badge's broader\n // `orderHasOpenDispute` (AGL-1820): a formal open dispute means the\n // bank already pulled the funds and the route answers 409\n // (AGL-1809), while an open INQUIRY shows the badge but must keep\n // this button live — a full refund is how an inquiry is resolved.\n CommerceModel.orderDisputeBlocksRefund(order) ? (\n <Tooltip\n title={\n 'A chargeback is open on this order, and refunding would not' +\n ' withdraw it — the bank has already taken the disputed' +\n ' amount, so a refund on top would pay the shopper twice.' +\n ' Respond to the dispute or accept it in the Stripe' +\n ' dashboard; refund any remainder once it settles.'\n }\n >\n {/* A disabled button fires no pointer events; the wrapper\n carries the hover for the tooltip. */}\n <span>\n <Button color=\"error\" disabled>\n {'Refund'}\n </Button>\n </span>\n </Tooltip>\n ) : (\n <Button\n color=\"error\"\n disabled={busy}\n onClick={() => handleRefund()}\n >\n {'Refund'}\n </Button>\n )\n ) : null}\n {(can('fulfilled') || can('partially_fulfilled')) && !tracking ? (\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setTracking({ carrier: '', number: '' })}\n >\n {'Fulfill…'}\n </Button>\n ) : null}\n {can('delivered') ? (\n <Button disabled={busy} onClick={handleDelivered}>\n {'Mark delivered'}\n </Button>\n ) : null}\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\nOrderDetailDialog.displayName = 'OrderDetailDialog'\n\nexport default OrderDetailDialog\n"],"names":["CommerceModel","escapeHtml","crmContactByEmailHref","describePaymentRisk","AppLink","useConfirmationContext","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","Stack","TextField","Tooltip","Typography","doc","runTransaction","updateDoc","useParams","useCallback","useEffect","useRef","useState","useFirestore","useUser","authorizedFetch","STATUS_COLOR","pending","paid","partially_fulfilled","fulfilled","delivered","cancelled","refunded","DISPUTE_COLOR","open","lost","won","settled","usd","cents","toFixed","OrderDetailDialog","props","order","restock","hostId","rawOrder","onClose","firestore","data","user","enqueueSnackbar","confirm","tracking","setTracking","note","setNote","busy","setBusy","liftLegacyOrder","orderId","$id","params","customerCrmHref","orgSlug","host","customerEmail","String","write","patch","transition","to","extra","copy","response","method","headers","body","JSON","stringify","error","console","subject","variant","allowDuplicate","payload","json","catch","ok","status","action","already","done","persist","handleFulfill","carrier","number","trackingNumber","handleDelivered","handleCancel","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","units","Number","alreadyCancelled","attemptKey","current","handleRefund","lineItemIds","lines","lineItems","scoped","length","scopedCents","orderLineRefundCents","map","index","name","join","totals","totalCents","amountCents","refundedCents","orderHasOpenDispute","orderDisputeBlocksRefund","globalThis","crypto","randomUUID","Date","now","Math","random","toString","slice","handleRestockAnswer","resolution","check","restockCheck","reference","verdict","transaction","snapshot","get","exists","fresh","flaggedAtMs","resolvedAtMs","uid","update","resolvedBy","timeline","appendOrderEvent","handleNote","trim","handlePackingSlip","address","win","window","rows","line","quantity","variantLabel","sku","shippingAddress","document","formatOrderNumber","line1","line2","city","state","postalCode","country","filter","Boolean","part","close","focus","print","can","canTransitionOrder","dispute","describeOrderDispute","paymentRisk","reversal","splitOrderReversal","restockLinesNamed","every","lineIndex","orderLineRefunded","maxWidth","fullWidth","direction","spacing","sx","alignItems","span","label","replace","size","detail","tone","channel","display","flexDirection","gap","customerName","withdrawn","flex","textDecoration","disabled","onClick","unitAmountCents","orderRefundState","orderRefundSummary","itemsCents","shippingCents","taxCents","discountCents","fontWeight","abs","describeOrderTaxMode","taxMode","chargedBackCents","kind","fullyReversed","productId","reverse","event","atMs","toLocaleString","value","onChange","target","width","placeholder","paymentLinkUrl","alignSelf","navigator","clipboard","writeText","component","componentVariant","nativeButton","href","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/order-detail-dialog.component.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'use client'\n\nimport * as CommerceModel from '../../model'\nimport { escapeHtml } from '../../utils/escape-html'\n// The CRM's address from the SHARED builder, not the plugin (AGL-2622): the\n// CRM already reaches marketing, and marketing reaches commerce, so an\n// import of the CRM from here would close a cycle between the three\n// plugins. The shared builder is pinned against the CRM's own routes by a\n// spec, so the address is the same one the hub resolves.\nimport { pluginRecordByEmailHref } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { describePaymentRisk } from '@aglyn/aglyn/app-utils/payment-risk'\nimport { AppLink, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n Stack,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { doc, runTransaction, updateDoc } from 'firebase/firestore'\nimport { useParams } from 'next/navigation'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface OrderDetailDialogProps {\n hostId: string\n order: (CommerceModel.HostOrder & { $id: string }) | null\n onClose: () => void\n}\n\nconst STATUS_COLOR: Record<\n string,\n 'default' | 'success' | 'warning' | 'error' | 'info'\n> = {\n pending: 'warning',\n paid: 'info',\n partially_fulfilled: 'info',\n fulfilled: 'success',\n delivered: 'success',\n cancelled: 'default',\n refunded: 'error',\n}\n\n/**\n * The dispute chip's colour (AGL-1796).\n *\n * `open` is WARNING rather than error because it is the case that still has a\n * deadline on it — the merchant can still act, and a red chip on a case they\n * might win says the money is gone when it is not. `lost` is the one that took\n * the money, and shares `refunded`'s red for that reason.\n */\nexport const DISPUTE_COLOR: Record<\n CommerceModel.OrderDisputeTone,\n 'default' | 'success' | 'warning' | 'error'\n> = {\n open: 'warning',\n lost: 'error',\n won: 'success',\n settled: 'default',\n}\n\nconst usd = (cents: number | undefined) =>\n `$${((cents ?? 0) / 100).toFixed(2)}`\n\n/**\n * Order detail (AGL-287): timeline, line items, totals, and the actions\n * the status machine allows — fulfill with tracking, refund, cancel, mark\n * delivered (each a server route that re-asks the transition under the\n * write), notes, printable packing slip.\n */\nexport function OrderDetailDialog(props: OrderDetailDialogProps) {\n const { hostId, order: rawOrder, onClose } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [tracking, setTracking] = useState<{ carrier: string; number: string } | null>(null)\n const [note, setNote] = useState('')\n const [busy, setBusy] = useState(false)\n\n const order = rawOrder ? CommerceModel.liftLegacyOrder(rawOrder) : null\n const orderId = rawOrder?.$id\n /*\n * Where the buyer's CONTACT is (AGL-2622). A checkout that carried an\n * address updated a person in the CRM — a customer, with this order on\n * their timeline — and the dialog links there by the address, because\n * the contact's id is minted at capture and nothing on the order holds\n * it. The Contacts list is the lookup and opens the record on one match,\n * at the address the plugin that keeps contacts publishes; where none\n * does, there is no link. A guest checkout with no address updated\n * nobody, so there is no link either. Built from the route params already\n * in the URL — no document is read to draw a link — and absent until they\n * settle, so the dialog never offers a link to a half-built address.\n */\n const params = useParams<{ orgSlug?: string; host?: string }>()\n const customerCrmHref =\n params?.orgSlug && params?.host && order?.customerEmail\n ? pluginRecordByEmailHref(\n 'contact',\n { orgSlug: String(params.orgSlug), host: String(params.host) },\n String(order.customerEmail),\n )\n : null\n\n /**\n * The one client write left in this dialog, and it is timeline-only ON\n * PURPOSE: notes carry no status, so `ORDER_TRANSITIONS` has nothing to say\n * about them and they stay out of the server routes' way. Every status\n * write goes through a route that re-asks the transition under the write —\n * cancel (AGL-1818), fulfil and mark-delivered (AGL-1819), refund\n * (AGL-1696). The moment a status rides along with a note, it must move\n * too; `order-fulfill-wiring.spec.tsx` pins the patch to `timeline` alone.\n */\n const write = useCallback(\n async (patch: Record<string, unknown>) => {\n if (!orderId) return\n await updateDoc(doc(firestore, 'hosts', hostId, 'orders', orderId), patch)\n },\n [firestore, hostId, orderId],\n )\n\n /**\n * Fulfil and mark-delivered go through `/api/commerce/fulfill-order`\n * (AGL-1819), not a client write — the same stale-dialog hole the cancel\n * swap (AGL-1818) closed: `can(…)` below only decides whether to RENDER\n * the buttons, so a dialog held open while the order was refunded in\n * another tab used to write `fulfilled` straight onto it. The route\n * re-asks `canTransitionOrder` inside the transaction that writes the\n * status, and that race now comes back as a 409 carrying the state that\n * refused.\n *\n * The messages follow the AGL-1784/1786 contract — say what happened: a\n * 409 is the guard refusing, not the machinery failing; the route's own\n * 500 is a rolled-back transaction (nothing landed, retry is safe and\n * correct); an answer with no JSON word from the handler, or no answer at\n * all, is NOT KNOWN — never \"nothing happened\". A retry is safe in every\n * case because the route writes once: a repeat finds the order already in\n * the target status and returns `already` without appending a second copy\n * of the same fulfillment.\n */\n const transition = useCallback(\n async (\n to: 'fulfilled' | 'delivered',\n extra: Record<string, string>,\n copy: { done: string; already: string; subject: string; action: string },\n ) => {\n if (!orderId) return false\n setBusy(true)\n try {\n let response: Response\n try {\n response = await authorizedFetch(\n user,\n '/api/commerce/fulfill-order',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, orderId, to, ...extra }),\n },\n )\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n `The request did not complete, so it is NOT known whether ${copy.subject}. ` +\n 'Reopen the order to check — retrying is safe, the server ' +\n 'writes it once.',\n { variant: 'error', allowDuplicate: true },\n )\n return false\n }\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok) {\n if (response.status === 409) {\n // The transition guard refusing: the order moved on (refunded,\n // cancelled) while this dialog was open. The route's message\n // names the state that refused it, and nothing was written.\n enqueueSnackbar(\n payload?.error ?? `This order can no longer be ${to}`,\n {\n variant: 'warning',\n allowDuplicate: true,\n },\n )\n } else if (payload?.error && response.status < 500) {\n // A refusal that wrote nothing — session, role, or an order that\n // is not there. The route's own word, verbatim.\n enqueueSnackbar(payload.error, {\n variant: 'error',\n allowDuplicate: true,\n })\n } else if (payload?.error) {\n // The route's 500 is a rolled-back transaction: nothing landed.\n // Only the handler's own JSON word licenses saying so.\n enqueueSnackbar(\n `${payload.error} — nothing changed, the order is as it was. Retrying is safe.`,\n { variant: 'error', allowDuplicate: true },\n )\n } else {\n // No JSON word from the handler (a gateway page, a proxy\n // timeout): whether the write landed is NOT known.\n enqueueSnackbar(\n `The ${copy.action} failed (${response.status}) and it is NOT ` +\n 'known whether it landed. Reopen the order to check — ' +\n 'retrying is safe, the server writes it once.',\n { variant: 'error', allowDuplicate: true },\n )\n }\n return false\n }\n enqueueSnackbar(payload?.already ? copy.already : copy.done, {\n variant: 'success',\n persist: false,\n })\n return true\n } finally {\n setBusy(false)\n }\n },\n [orderId, user, hostId, enqueueSnackbar],\n )\n\n const handleFulfill = useCallback(async () => {\n if (!order || !tracking) return\n const done = await transition(\n 'fulfilled',\n {\n ...(tracking.carrier ? { carrier: tracking.carrier } : {}),\n ...(tracking.number ? { trackingNumber: tracking.number } : {}),\n },\n {\n done: 'Order fulfilled',\n already: 'Order was already fulfilled',\n subject: 'the order was fulfilled',\n action: 'fulfillment',\n },\n )\n // The refused or unknown case keeps the form open — there is nothing to\n // celebrate, and the tracking is not yet recorded anywhere.\n if (done) setTracking(null)\n }, [order, tracking, transition])\n\n const handleDelivered = useCallback(async () => {\n if (!order) return\n await transition(\n 'delivered',\n {},\n {\n done: 'Order marked delivered',\n already: 'Order was already marked delivered',\n subject: 'the order was marked delivered',\n action: 'delivery update',\n },\n )\n }, [order, transition])\n\n /**\n * Cancel goes through `/api/commerce/cancel-order` (AGL-1818), not a client\n * write. The status flip and the stock release are one transaction there,\n * and the route re-asks `canTransitionOrder` under the same lock — the\n * `can('cancelled')` gate below only decides whether to RENDER the button,\n * so a dialog held open while the order ships in another tab used to write\n * `cancelled` straight onto a `fulfilled` order. Now that race comes back\n * as a 409 carrying the state that refused it.\n *\n * The messages follow the AGL-1784/1786 contract — say what happened:\n * a 409 is the guard refusing, not the cancel machinery failing; the\n * route's own 500 is a rolled-back transaction (nothing landed, retry is\n * safe and correct); an answer with no JSON word from the handler, or no\n * answer at all, is NOT KNOWN — never \"nothing happened\". A retry is safe\n * in every case because the route cancels once: a second cancel finds\n * `cancelled` and returns success without touching stock.\n */\n const handleCancel = useCallback(async () => {\n if (!order || !orderId) return\n const confirmed = await confirm({\n title: 'Cancel this order?',\n description:\n 'The buyer is NOT refunded automatically — refund first if they paid.',\n confirmationText: 'Cancel order',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy(true)\n try {\n let response: Response\n try {\n response = await authorizedFetch(user, '/api/commerce/cancel-order', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, orderId }),\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar(\n 'The request did not complete, so it is NOT known whether the ' +\n 'order was canceled. Reopen the order to check — retrying is ' +\n 'safe, the server cancels once.',\n { variant: 'error', allowDuplicate: true },\n )\n return\n }\n const payload: any = await response.json().catch(() => ({}))\n if (!response.ok) {\n if (response.status === 409) {\n // The transition guard refusing: the order moved on (shipped,\n // refunded) while this dialog was open. The route's message names\n // the state that refused it, and nothing was written.\n enqueueSnackbar(\n payload?.error ?? 'This order can no longer be canceled',\n {\n variant: 'warning',\n allowDuplicate: true,\n },\n )\n } else if (payload?.error && response.status < 500) {\n // A refusal that wrote nothing — session, role, or an order that\n // is not there. The route's own word, verbatim.\n enqueueSnackbar(payload.error, {\n variant: 'error',\n allowDuplicate: true,\n })\n } else if (payload?.error) {\n // The route's 500 is a rolled-back transaction: nothing landed and\n // the order is still open. Only the handler's own JSON word\n // licenses saying so.\n enqueueSnackbar(\n `${payload.error} — nothing changed, the order is still open. Retrying is safe.`,\n { variant: 'error', allowDuplicate: true },\n )\n } else {\n // No JSON word from the handler (a gateway page, a proxy timeout):\n // whether the cancel landed is NOT known.\n enqueueSnackbar(\n `The cancel failed (${response.status}) and it is NOT known ` +\n 'whether it landed. Reopen the order to check — retrying is ' +\n 'safe, the server cancels once.',\n { variant: 'error', allowDuplicate: true },\n )\n }\n return\n }\n const units = Number(payload?.units ?? 0)\n enqueueSnackbar(\n payload?.alreadyCancelled\n ? 'Order was already canceled'\n : units > 0\n ? `Order canceled — ${units} ${units === 1 ? 'unit' : 'units'} returned to stock`\n : 'Order canceled',\n { variant: 'success', persist: false },\n )\n } finally {\n setBusy(false)\n }\n }, [order, orderId, user, hostId, confirm, enqueueSnackbar])\n\n /**\n * Idempotency key for ONE refund attempt (AGL-1696), the same shape the POS\n * register uses for a sale (AGL-1691).\n *\n * Minted lazily so a retry of this attempt — a lost response, a second\n * click, the admin reopening the dialog after a timeout — reuses it and the\n * server replays instead of sending the money again. Retired on a definitive\n * answer, which makes the NEXT refund a new attempt: two partial refunds of\n * the same amount on the same order are two real refunds, and keying on the\n * order (or the amount) would silently swallow the second.\n *\n * NOT retired on a network failure or a 5xx — that is precisely the window\n * the key exists to cover.\n */\n const attemptKey = useRef('')\n useEffect(() => {\n attemptKey.current = ''\n }, [orderId])\n\n /**\n * `lineItemIds` scopes the refund to named lines (AGL-2454).\n *\n * Without it a partial refund is an AMOUNT and nothing else, so the product\n * cannot know which goods came back and revokes nothing — a 99%-refunded\n * order kept its downloads, licence keys, gated pages and verified review.\n * Naming the lines is the only way that question gets an honest answer, and\n * the route derives the amount FROM the lines rather than trusting a figure\n * typed beside them.\n */\n const handleRefund = useCallback(async (lineItemIds?: number[]) => {\n if (!order || !orderId) return\n const lines = order.lineItems ?? []\n const scoped = (lineItemIds ?? []).length > 0\n // The SERVER's figure, not a second derivation of it. This was\n // the list value `unitAmountCents x quantity`, which on a discounted order\n // is more than the buyer paid — so the dialog quoted one number and the\n // route refunded another. One function answers both.\n const scopedCents = CommerceModel.orderLineRefundCents(\n order,\n lineItemIds ?? [],\n )\n const confirmed = await confirm({\n title: scoped ? 'Refund this line?' : 'Refund this order?',\n description:\n (scoped\n ? `Refunds ${usd(scopedCents)} for ${(lineItemIds ?? [])\n .map((index) => lines[index]?.name ?? `line ${index}`)\n .join(', ')} through Stripe, and withdraws that line's ` +\n 'downloads, license keys and gated content. A license key already ' +\n 'sent to the buyer is retired, not returned to your pool — they ' +\n 'still hold the string, so reissuing it would give two people one ' +\n 'working key.'\n : `Refunds ${usd(\n (order.totals?.totalCents ?? order.amountCents ?? 0) -\n (order.refundedCents ?? 0),\n )} to the buyer through Stripe.`) +\n // An open INQUIRY keeps this button live on purpose (AGL-1820):\n // Stripe names a full refund as the way to resolve one before it\n // escalates to a chargeback. Said here so the admin reads the refund\n // as the documented exit, not a risk taken against the badge.\n (CommerceModel.orderHasOpenDispute(order) &&\n !CommerceModel.orderDisputeBlocksRefund(order)\n ? ' An inquiry is open on this charge — a full refund resolves it' +\n ' before it can become a chargeback.'\n : ''),\n confirmationText: 'Refund',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy(true)\n // `randomUUID` needs a secure context, which the console always is, but\n // fall back rather than throw on a refund.\n if (!attemptKey.current) {\n attemptKey.current =\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n }\n try {\n const response = await authorizedFetch(user, '/api/commerce/refund', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Idempotency-Key': attemptKey.current,\n },\n body: JSON.stringify({\n hostId,\n orderId,\n ...(scoped ? { lineItemIds } : {}),\n }),\n })\n const payload = await response.json()\n // A definitive answer ends this attempt either way: the next refund is a\n // new one and must get a new key. A 5xx or a thrown fetch keeps the key,\n // so pressing Refund again dedupes rather than sends a second transfer.\n if (response.status < 500) attemptKey.current = ''\n if (!response.ok) {\n // A 409 is a guard REFUSING — the status machine, or the open-dispute\n // block (AGL-1809), whose body explains what to do instead — not the\n // refund machinery failing. The route wrote nothing, so it surfaces\n // verbatim as a warning, same as the cancel path's 409 (AGL-1818).\n enqueueSnackbar(payload?.error ?? 'Refund failed', {\n variant: response.status === 409 ? 'warning' : 'error',\n allowDuplicate: true,\n })\n } else {\n enqueueSnackbar('Refund issued', { variant: 'success', persist: false })\n }\n } finally {\n setBusy(false)\n }\n }, [order, orderId, user, hostId, confirm, enqueueSnackbar])\n\n /**\n * Answer the restock question a reversal flagged (AGL-1806).\n *\n * The answer MOVES NO STOCK, which is AGL-1797's ledger argument and it is\n * binding: the products hub's \"Adjust stock\" is the one stock writer (it\n * writes the counts AND the `InventoryAdjustment` row the hub renders as\n * history), so \"Restocked\" here is clicked AFTER the merchant used it, and\n * \"No restock\" says the goods are not recoverable. Either answer only sets\n * the `resolution` triple `OrderRestockCheck` declares and says so on the\n * timeline.\n *\n * A TRANSACTION rather than the dialog's plain `write`, keyed on the\n * rendered `flaggedAtMs`, because an answer must land ONCE. The flag writer\n * re-asks an ANSWERED question after the next reversal, and the cancel\n * route (AGL-1808) answers an open one itself when it returns the stock —\n * so a stale dialog writing blind would either re-answer a settled question\n * or replace a brand-new one with a resolved copy of the old. The\n * transaction re-reads on the server (the web SDK will not run one from\n * cache — the products hub's `writeGuardedBySeed` refusal of unconfirmed\n * data, by other means) and refuses both cases with the truth: nothing was\n * written.\n */\n const handleRestockAnswer = useCallback(\n async (resolution: 'restocked' | 'dismissed') => {\n const check = order?.restockCheck\n if (!order || !orderId || !check || check.resolution) return\n setBusy(true)\n try {\n const reference = doc(firestore, 'hosts', hostId, 'orders', orderId)\n const verdict = await runTransaction(firestore, async (transaction) => {\n const snapshot = await transaction.get(reference)\n if (!snapshot.exists()) return 'gone'\n const current = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n const fresh = current.restockCheck\n if (!fresh || fresh.resolution) return 'answered'\n if (fresh.flaggedAtMs !== check.flaggedAtMs) return 'changed'\n const resolvedAtMs = Date.now()\n const uid = String((user as any)?.uid ?? '')\n transaction.update(reference, {\n restockCheck: {\n ...fresh,\n resolution,\n resolvedAtMs,\n ...(uid ? { resolvedBy: uid } : {}),\n } satisfies CommerceModel.OrderRestockCheck,\n // The SERVER's timeline, not the dialog's — a note landed from\n // another tab must survive the append.\n timeline: CommerceModel.appendOrderEvent(\n current,\n 'restock-check',\n resolution === 'restocked'\n ? 'answered — restocked'\n : 'answered — no restock',\n resolvedAtMs,\n ),\n })\n return 'recorded'\n })\n if (verdict === 'recorded') {\n enqueueSnackbar(\n resolution === 'restocked'\n ? 'Recorded as restocked'\n : 'Recorded — no restock',\n { variant: 'success', persist: false },\n )\n } else if (verdict === 'answered') {\n // The cancel route, or another admin, got there first.\n enqueueSnackbar(\n 'This restock question was already answered — nothing changed.',\n { variant: 'info', persist: false },\n )\n } else if (verdict === 'changed') {\n // A later reversal re-asked; this dialog shows the OLD question.\n enqueueSnackbar(\n 'The restock question changed while this dialog was open — ' +\n 'nothing was written. Reopen the order to see the current one.',\n { variant: 'warning', allowDuplicate: true },\n )\n } else {\n enqueueSnackbar('This order no longer exists — nothing was written.', {\n variant: 'warning',\n allowDuplicate: true,\n })\n }\n } catch (error) {\n // A transaction is all-or-nothing, and a thrown one did not commit.\n console.error(error)\n enqueueSnackbar(\n 'The answer was not recorded — it needs a live connection and the ' +\n 'editor or admin role. Nothing changed; answering again is safe.',\n { variant: 'error', allowDuplicate: true },\n )\n } finally {\n setBusy(false)\n }\n },\n [order, orderId, firestore, hostId, user, enqueueSnackbar],\n )\n\n const handleNote = useCallback(async () => {\n if (!order || !note.trim()) return\n await write({\n timeline: CommerceModel.appendOrderEvent(order, 'note', note.trim().slice(0, 500)),\n })\n setNote('')\n }, [order, note, write])\n\n const handlePackingSlip = useCallback(() => {\n if (!order) return\n const win = window.open('', '_blank', 'width=600,height=800')\n if (!win) return\n // ESCAPED (AGL-2283). This is `document.write` into an `about:blank`\n // popup, which INHERITS the console's origin — and `order.shippingAddress`\n // is copied verbatim from Stripe's `shipping_details`, i.e. typed by the\n // SHOPPER. A name of `<img src=x onerror=…>` ran script against the\n // merchant's own authenticated session. React guards nothing here because\n // none of this is React. Line names and SKUs are merchant-authored and\n // lower severity, but they are the same construction and get the same\n // treatment rather than a case-by-case argument.\n const rows = (order.lineItems ?? [])\n .map(\n (line) =>\n `<tr><td>${escapeHtml(line.quantity)}×</td><td>${escapeHtml(\n line.name,\n )}${\n line.variantLabel ? ` — ${escapeHtml(line.variantLabel)}` : ''\n }</td><td>${escapeHtml(line.sku ?? '')}</td></tr>`,\n )\n .join('')\n const address = order.shippingAddress\n win.document.write(\n `<h2>Packing slip ${escapeHtml(\n CommerceModel.formatOrderNumber(order, orderId),\n )}</h2>` +\n (address\n ? `<p>${[address.name, address.line1, address.line2, `${address.city ?? ''} ${address.state ?? ''} ${address.postalCode ?? ''}`, address.country].filter(Boolean).map((part) => escapeHtml(part)).join('<br/>')}</p>`\n : '') +\n `<table border=\"0\" cellpadding=\"6\">${rows}</table>`,\n )\n win.document.close()\n // Print from the opener, not from an injected inline script (AGL-523) —\n // the popup inherits this document's CSP, where an un-nonced inline\n // script is blocked under the enforcing policy. Same reasoning as the POS\n // receipt.\n win.focus()\n win.print()\n }, [order, orderId])\n\n if (!order) return null\n const totals = order.totals\n const can = (to: CommerceModel.OrderStatus) => CommerceModel.canTransitionOrder(order.status, to)\n // A lost chargeback leaves `status: 'refunded'` (AGL-1787), so the status\n // chip alone tells the merchant they chose this. The badge is the correction.\n const dispute = CommerceModel.describeOrderDispute(order)\n // An early fraud warning or a Radar review on the payment (AGL-3360).\n const paymentRisk = describePaymentRisk(order.paymentRisk)\n const reversal = CommerceModel.splitOrderReversal(order)\n // The restock question a reversal flagged, while it is still open\n // (AGL-1806). Absent `resolution` IS the open state — the writer only ever\n // re-flags an answered check (AGL-1797).\n const restock =\n order.restockCheck && !order.restockCheck.resolution\n ? order.restockCheck\n : null\n // Whether the reversal NAMED the lines this question is about (AGL-2325).\n // AGL-2454 gave a refund a line selection, and `flagOrderRestock` now scopes\n // the question to it — so the \"we cannot tell which goods came back\" caveat\n // below is true of an amount-only reversal and false of this one. Asked of\n // the order's own withdrawal record rather than of a flag on the check, so a\n // prompt written before line indexes existed reads as the amount-only case\n // it was.\n const restockLinesNamed = Boolean(\n restock?.lines?.length &&\n restock.lines.every(\n (line) =>\n line.lineIndex != null &&\n CommerceModel.orderLineRefunded(order, line.lineIndex),\n ),\n )\n\n return (\n <Dialog open onClose={onClose} maxWidth=\"sm\" fullWidth>\n <DialogTitle>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <span>{`Order ${CommerceModel.formatOrderNumber(order, orderId)}`}</span>\n <Chip\n label={order.status.replace('_', ' ')}\n size=\"small\"\n color={STATUS_COLOR[order.status] ?? 'default'}\n variant=\"outlined\"\n />\n {dispute ? (\n <Tooltip title={dispute.detail}>\n <Chip\n label={dispute.label}\n size=\"small\"\n color={DISPUTE_COLOR[dispute.tone]}\n variant=\"filled\"\n />\n </Tooltip>\n ) : null}\n {paymentRisk ? (\n <Tooltip title={paymentRisk.detail}>\n <Chip\n label={paymentRisk.label}\n size=\"small\"\n color=\"warning\"\n variant=\"filled\"\n />\n </Tooltip>\n ) : null}\n {order.channel && order.channel !== 'online' ? (\n <Chip label={order.channel} size=\"small\" variant=\"outlined\" />\n ) : null}\n </Stack>\n </DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {[order.customerName, order.customerEmail]\n .filter(Boolean)\n .join(' · ') || 'Guest buyer'}\n </Typography>\n {(order.lineItems ?? []).map((line, index) => {\n // WHAT THIS LINE IS STILL ENTITLED TO, per line (AGL-2454). A refund\n // used to be a number on the order and nothing more, so the merchant\n // could not see — and the product could not enforce — which goods had\n // come back.\n const withdrawn = CommerceModel.orderLineRefunded(order, index)\n return (\n <Stack key={index} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography\n variant=\"body2\"\n sx={{\n flex: 1,\n ...(withdrawn ? { textDecoration: 'line-through' } : {}),\n }}\n color={withdrawn ? 'text.secondary' : 'text.primary'}\n >\n {`${line.quantity}× ${line.name}` +\n (line.variantLabel ? ` — ${line.variantLabel}` : '')}\n </Typography>\n {withdrawn ? (\n <Chip label=\"refunded\" size=\"small\" color=\"error\" variant=\"outlined\" />\n ) : can('refunded') &&\n !CommerceModel.orderDisputeBlocksRefund(order) ? (\n <Button\n size=\"small\"\n color=\"error\"\n disabled={busy}\n onClick={() => handleRefund([index])}\n >\n {'Refund line'}\n </Button>\n ) : null}\n <Typography variant=\"body2\">\n {usd(line.unitAmountCents * line.quantity)}\n </Typography>\n </Stack>\n )\n })}\n {CommerceModel.orderRefundState(order) !== 'none' ? (\n // The state, said in words (AGL-2454). \"Partially refunded, no lines\n // withdrawn\" is the honest reading of an amount-only refund, and it\n // is what tells a merchant their buyer still has the downloads.\n <Typography variant=\"caption\" color=\"error\">\n {CommerceModel.orderRefundSummary(order)}\n </Typography>\n ) : null}\n {totals ? (\n <>\n <Divider />\n {(\n [\n ['Items', totals.itemsCents],\n ['Shipping', totals.shippingCents],\n ['Tax', totals.taxCents],\n ['Discount', -totals.discountCents],\n ['Total', totals.totalCents],\n ] as Array<[string, number]>\n ).map(([label, cents]) => (\n <Stack key={label} direction=\"row\">\n <Typography\n variant=\"body2\"\n sx={{ flex: 1 }}\n color={label === 'Total' ? 'text.primary' : 'text.secondary'}\n >\n {label}\n </Typography>\n <Typography\n variant=\"body2\"\n sx={{ fontWeight: label === 'Total' ? 600 : 400 }}\n >\n {`${cents < 0 ? '-' : ''}${usd(Math.abs(cents))}`}\n </Typography>\n </Stack>\n ))}\n {/*\n HOW the tax on this order was computed (AGL-2451). The `Tax` row\n above states an amount and nothing about which machinery produced\n it, and a merchant reading one order could not tell Stripe Tax\n (computed against Aglyn's registrations) from their own configured\n rate. Those are different facts about whose registration the money\n is held under, and the order is where a merchant looks.\n\n Describes the mechanism and makes NO remittance determination —\n the constraint AGL-2440's report ships under. An order written\n before the stamp existed says so, rather than being rendered as\n \"no tax\": absent means NOT RECORDED, and `describeOrderTaxMode`\n is where that distinction is kept.\n */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {CommerceModel.describeOrderTaxMode(order.taxMode)}\n </Typography>\n {/*\n Both figures live in `refundedCents` (AGL-1787), so a lost\n chargeback used to render as \"Refunded $62.00\" — the merchant's\n own decision, spelled the same as money taken from them. Split by\n door, and show BOTH when a partial refund and a chargeback each\n took a piece.\n */}\n {reversal.refundedCents ? (\n <Typography variant=\"caption\" color=\"error\">\n {`Refunded ${usd(reversal.refundedCents)}`}\n </Typography>\n ) : null}\n {reversal.chargedBackCents ? (\n <Typography variant=\"caption\" color=\"error\">\n {`Charged back ${usd(reversal.chargedBackCents)}`}\n </Typography>\n ) : null}\n </>\n ) : null}\n {restock ? (\n <>\n <Divider />\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Restock check'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${restock.units} ${\n restock.units === 1 ? 'unit' : 'units'\n } may need restocking after this ${\n restock.kind === 'chargeback' ? 'chargeback' : 'refund'\n }.` +\n // The chargeback default is NO, same as the timeline's\n // wording: the shopper kept the item and took the money.\n (restock.kind === 'chargeback'\n ? ' The shopper kept the goods unless they actually came back.'\n : '') +\n // On a partial the flagged units are the MOST it could be —\n // UNLESS the refund named its lines (AGL-2325), in which\n // case the question is scoped to exactly those and the\n // merchant is only being asked whether the goods came back.\n (restock.fullyReversed\n ? ''\n : restockLinesNamed\n ? ' Only part of the money came back: these are the lines withdrawn by this refund, so the units are theirs — only you know whether the goods came back.'\n : ' Only part of the money came back, so these units are an upper bound — only you know which goods returned.')}\n </Typography>\n {restock.lines.map((line, index) => (\n <Typography key={index} variant=\"caption\" color=\"text.secondary\">\n {`${line.quantity}× ${line.name ?? line.productId}` +\n (line.variantLabel ? ` — ${line.variantLabel}` : '')}\n </Typography>\n ))}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'If goods came back, put them on the shelf with \"Adjust ' +\n 'stock\" in the products hub first — these answers move no ' +\n 'stock, they only clear this question.'}\n </Typography>\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={busy}\n onClick={() => handleRestockAnswer('restocked')}\n >\n {'Restocked'}\n </Button>\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => handleRestockAnswer('dismissed')}\n >\n {'No restock'}\n </Button>\n </Stack>\n </Stack>\n </>\n ) : null}\n <Divider />\n <Typography variant=\"subtitle2\">{'Timeline'}</Typography>\n {(order.timeline ?? [])\n .slice()\n .reverse()\n .map((event, index) => (\n <Typography key={index} variant=\"caption\" color=\"text.secondary\">\n {`${new Date(event.atMs).toLocaleString()} — ${event.event}` +\n (event.detail ? `: ${event.detail}` : '')}\n </Typography>\n ))}\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Add note\"\n value={note}\n onChange={(event) => setNote(event.target.value)}\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <Button size=\"small\" disabled={!note.trim()} onClick={handleNote}>\n {'Add'}\n </Button>\n </Stack>\n {tracking ? (\n <Stack direction=\"row\" spacing={1}>\n <TextField\n label=\"Carrier\"\n value={tracking.carrier}\n onChange={(event) =>\n setTracking({ ...tracking, carrier: event.target.value })\n }\n size=\"small\"\n sx={{ width: 140 }}\n placeholder=\"UPS\"\n />\n <TextField\n label=\"Tracking number\"\n value={tracking.number}\n onChange={(event) =>\n setTracking({ ...tracking, number: event.target.value })\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n <Button\n size=\"small\"\n variant=\"contained\"\n color=\"primary\"\n disabled={busy}\n onClick={handleFulfill}\n >\n {'Fulfill'}\n </Button>\n </Stack>\n ) : null}\n {order.paymentLinkUrl && order.status === 'pending' ? (\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => {\n navigator.clipboard.writeText(String(order.paymentLinkUrl))\n enqueueSnackbar('Payment link copied', {\n variant: 'success',\n persist: false,\n })\n }}\n >\n {'Copy payment link'}\n </Button>\n ) : null}\n </DialogContent>\n <DialogActions>\n {customerCrmHref ? (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={customerCrmHref}\n >\n {'View customer in CRM'}\n </Button>\n ) : null}\n <Button onClick={handlePackingSlip}>{'Packing slip'}</Button>\n {can('cancelled') ? (\n <Button color=\"error\" disabled={busy} onClick={handleCancel}>\n {'Cancel order'}\n </Button>\n ) : null}\n {can('refunded') ? (\n // Disabled on `orderDisputeBlocksRefund`, NOT the badge's broader\n // `orderHasOpenDispute` (AGL-1820): a formal open dispute means the\n // bank already pulled the funds and the route answers 409\n // (AGL-1809), while an open INQUIRY shows the badge but must keep\n // this button live — a full refund is how an inquiry is resolved.\n CommerceModel.orderDisputeBlocksRefund(order) ? (\n <Tooltip\n title={\n 'A chargeback is open on this order, and refunding would not' +\n ' withdraw it — the bank has already taken the disputed' +\n ' amount, so a refund on top would pay the shopper twice.' +\n ' Respond to the dispute or accept it in the Stripe' +\n ' dashboard; refund any remainder once it settles.'\n }\n >\n {/* A disabled button fires no pointer events; the wrapper\n carries the hover for the tooltip. */}\n <span>\n <Button color=\"error\" disabled>\n {'Refund'}\n </Button>\n </span>\n </Tooltip>\n ) : (\n <Button\n color=\"error\"\n disabled={busy}\n onClick={() => handleRefund()}\n >\n {'Refund'}\n </Button>\n )\n ) : null}\n {(can('fulfilled') || can('partially_fulfilled')) && !tracking ? (\n <Button\n variant=\"contained\"\n color=\"primary\"\n onClick={() => setTracking({ carrier: '', number: '' })}\n >\n {'Fulfill…'}\n </Button>\n ) : null}\n {can('delivered') ? (\n <Button disabled={busy} onClick={handleDelivered}>\n {'Mark delivered'}\n </Button>\n ) : null}\n <Button onClick={onClose}>{'Close'}</Button>\n </DialogActions>\n </Dialog>\n )\n}\nOrderDetailDialog.displayName = 'OrderDetailDialog'\n\nexport default OrderDetailDialog\n"],"names":["CommerceModel","escapeHtml","pluginRecordByEmailHref","describePaymentRisk","AppLink","useConfirmationContext","useSnackbar","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","Stack","TextField","Tooltip","Typography","doc","runTransaction","updateDoc","useParams","useCallback","useEffect","useRef","useState","useFirestore","useUser","authorizedFetch","STATUS_COLOR","pending","paid","partially_fulfilled","fulfilled","delivered","cancelled","refunded","DISPUTE_COLOR","open","lost","won","settled","usd","cents","toFixed","OrderDetailDialog","props","order","restock","hostId","rawOrder","onClose","firestore","data","user","enqueueSnackbar","confirm","tracking","setTracking","note","setNote","busy","setBusy","liftLegacyOrder","orderId","$id","params","customerCrmHref","orgSlug","host","customerEmail","String","write","patch","transition","to","extra","copy","response","method","headers","body","JSON","stringify","error","console","subject","variant","allowDuplicate","payload","json","catch","ok","status","action","already","done","persist","handleFulfill","carrier","number","trackingNumber","handleDelivered","handleCancel","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","units","Number","alreadyCancelled","attemptKey","current","handleRefund","lineItemIds","lines","lineItems","scoped","length","scopedCents","orderLineRefundCents","map","index","name","join","totals","totalCents","amountCents","refundedCents","orderHasOpenDispute","orderDisputeBlocksRefund","globalThis","crypto","randomUUID","Date","now","Math","random","toString","slice","handleRestockAnswer","resolution","check","restockCheck","reference","verdict","transaction","snapshot","get","exists","fresh","flaggedAtMs","resolvedAtMs","uid","update","resolvedBy","timeline","appendOrderEvent","handleNote","trim","handlePackingSlip","address","win","window","rows","line","quantity","variantLabel","sku","shippingAddress","document","formatOrderNumber","line1","line2","city","state","postalCode","country","filter","Boolean","part","close","focus","print","can","canTransitionOrder","dispute","describeOrderDispute","paymentRisk","reversal","splitOrderReversal","restockLinesNamed","every","lineIndex","orderLineRefunded","maxWidth","fullWidth","direction","spacing","sx","alignItems","span","label","replace","size","detail","tone","channel","display","flexDirection","gap","customerName","withdrawn","flex","textDecoration","disabled","onClick","unitAmountCents","orderRefundState","orderRefundSummary","itemsCents","shippingCents","taxCents","discountCents","fontWeight","abs","describeOrderTaxMode","taxMode","chargedBackCents","kind","fullyReversed","productId","reverse","event","atMs","toLocaleString","value","onChange","target","width","placeholder","paymentLinkUrl","alignSelf","navigator","clipboard","writeText","component","componentVariant","nativeButton","href","displayName"],"mappings":"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"}
|
|
@@ -71,7 +71,7 @@ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
|
71
71
|
* production profile carrying it named a TEST-mode account: this card said
|
|
72
72
|
* "Charges enabled" about three storefronts that could not take a single
|
|
73
73
|
* payment. Only a literal boolean counts as recorded — the same
|
|
74
|
-
* three-valued reading `
|
|
74
|
+
* three-valued reading `merchantAccountIsReady` uses on the server, so the
|
|
75
75
|
* chip and the checkout cannot disagree.
|
|
76
76
|
*/ const modeVerified = typeof (profile == null ? void 0 : profile.stripeAccountLivemode) === 'boolean';
|
|
77
77
|
const chargesConnected = stripeState === 'loaded' && Boolean(profile == null ? void 0 : profile.stripeChargesEnabled);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/payments-settings-card.component.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'use client'\n\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, Chip, Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface PaymentsSettingsCardProps {\n hostId: string\n}\n\n/**\n * Merchant payments card (AGL-284): Stripe Connect Express onboarding\n * status for the org owner + the plan's transaction-fee ladder so the\n * \"upgrade to kill fees\" motion (AGL-278) is visible where it matters.\n */\nexport function PaymentsSettingsCard(props: PaymentsSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n /**\n * A one-time Stripe Express dashboard link, minted by the connect route on\n * request. Not persisted: login links are single-use and short-lived, so a\n * stored one would be a button that fails the second time it is pressed.\n */\n const [dashboardUrl, setDashboardUrl] = useState<string | null>(null)\n const { org, ready: planReady } = useOrgPlan(hostId)\n /** Bumped by Retry to re-subscribe the profile listen (AGL-1380). */\n const [retryNonce, setRetryNonce] = useState(0)\n const uid = (user as any)?.uid as string | undefined\n const { data: profile, status: profileStatus } = useFirestoreDoc<any>(\n // Held at null rather than addressed as `profiles/-pending-` — that\n // sentinel issued a guaranteed-denied read on every pre-auth mount, and\n // a denial lands in `status: 'error'`, which now means something.\n () => (uid ? doc(firestore, 'profiles', uid) : null),\n [firestore, uid, retryNonce],\n )\n const isOwner = Boolean(\n org?.ownerUid && (user as any)?.uid === org.ownerUid,\n )\n /**\n * Three outcomes, not two (AGL-1380). An absent profile doc is the value\n * BEFORE an answer as much as it is the value of a merchant who never\n * connected Stripe, so \"Not set up\" off a falsy `stripeChargesEnabled` is a\n * claim about this org's payment setup made without having heard back. It\n * is wrong in the direction that provokes action: the same falsy value\n * turns the button into \"Set up payments\", which sends a merchant who is\n * already taking money into Stripe onboarding.\n */\n const stripeState: 'pending' | 'error' | 'loaded' =\n !uid || profileStatus === 'loading'\n ? 'pending'\n : profileStatus === 'error'\n ? 'error'\n : 'loaded'\n /**\n * WHICH STRIPE WORLD THE STORED ACCOUNT BELONGS TO (AGL-2471).\n *\n * `stripeChargesEnabled: true` was the whole readiness claim, and a\n * production profile carrying it named a TEST-mode account: this card said\n * \"Charges enabled\" about three storefronts that could not take a single\n * payment. Only a literal boolean counts as recorded — the same\n * three-valued reading `connectLinkageIsReady` uses on the server, so the\n * chip and the checkout cannot disagree.\n */\n const modeVerified = typeof profile?.stripeAccountLivemode === 'boolean'\n const chargesConnected =\n stripeState === 'loaded' && Boolean(profile?.stripeChargesEnabled)\n const modeUnverified = chargesConnected && !modeVerified\n const chargesEnabled = chargesConnected && modeVerified\n const physicalPct = Aglyn.resolveTransactionFeePct(org, 'physical')\n const digitalPct = Aglyn.resolveTransactionFeePct(org, 'digital')\n const commerceEnabled = Aglyn.checkEntitlement(org, 'commerce')\n\n const handleConnect = useCallback(async () => {\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/connect', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId }),\n })\n const payload = await response.json()\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'Payments are not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Payment setup failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n if (payload.chargesEnabled) {\n // \"% who connect Stripe\" — a GTM plan §6 activation metric\n // (AGL-1561). Gated on the TRANSITION, not the state: this handler\n // is a re-entrant create-or-resume, so it answers `chargesEnabled:\n // true` on every subsequent visit to a connected account, and a bare\n // event here would count one merchant once per click forever.\n // `chargesEnabled` is the state read from the profile BEFORE this\n // request, so the event fires only when onboarding actually completed.\n if (!chargesEnabled) trackEvent('stripe_connected', {})\n // CHARGES AND PAYOUTS ARE TWO FLAGS, AND ONLY ONE OF THEM IS THIS\n // SENTENCE (AGL-1997). `chargesEnabled` says Stripe will let the\n // account take money; whether the money can LEAVE is `payoutsEnabled`,\n // which the route answers alongside it. Charges-yes/payouts-no is an\n // ordinary Stripe state — verification pending or lapsed, no payout\n // method — and announcing \"Payments are enabled\" in it is false in the\n // direction that costs the merchant: they sell believing they are\n // paid, and the funds sit in a Connect account that cannot release\n // them. Read three-valued, so only a literal `false` from Stripe\n // downgrades the claim; an absent flag means unasked, not off.\n const payoutsBlocked = payload.payoutsEnabled === false\n // A WARNING THAT NAMES A PROBLEM MUST OFFER THE FIX.\n //\n // Stripe is the only place payouts can be released — an Express\n // account has no password and no direct login — so telling the\n // merchant their funds are stranded and then keeping them here is half\n // an answer. The route now mints the remediation link for exactly this\n // state; going there is the whole point of pressing the button.\n if (payoutsBlocked && payload.url) {\n return void window.location.assign(payload.url)\n }\n // Payouts are flowing, so the link is the Express dashboard: balance,\n // payout schedule, and the reason a payout failed. Held in state\n // rather than followed, because this click was a status check and\n // navigating away from one nobody asked to leave is its own defect.\n if (!payoutsBlocked && typeof payload.dashboardUrl === 'string') {\n setDashboardUrl(payload.dashboardUrl)\n }\n return void enqueueSnackbar(\n payoutsBlocked\n ? 'Connected — payouts are not released yet'\n : 'Payments are enabled',\n {\n variant: payoutsBlocked ? 'warning' : 'success',\n persist: false,\n },\n )\n }\n if (payload.url) window.location.assign(payload.url)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [user, hostId, chargesEnabled, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Payments'}\n help={pluginDocsHelp('commerceEndToEnd', {\n anchor: '#1-connect-payments',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {'Stripe account'}\n </Typography>\n {/*\n No chip at all on error: both labels it could carry — \"Charges\n enabled\" and \"Not set up\" — assert something we failed to find\n out. Whatever Stripe is doing right now it carries on doing.\n */}\n {stripeState === 'error' ? null : (\n <Chip\n size=\"small\"\n label={\n stripeState === 'pending'\n ? 'Checking…'\n : chargesEnabled\n ? 'Charges enabled'\n : modeUnverified\n ? 'Not verified'\n : 'Not set up'\n }\n color={\n stripeState === 'pending'\n ? 'default'\n : chargesEnabled\n ? 'success'\n : modeUnverified\n ? 'error'\n : 'warning'\n }\n variant=\"outlined\"\n />\n )}\n </Stack>\n {stripeState === 'error' ? (\n <Alert\n severity=\"error\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n onClick={() => setRetryNonce((n) => n + 1)}\n >\n {'Retry'}\n </Button>\n }\n >\n {'We couldn’t load your payment setup. This says nothing about ' +\n 'whether payments are on — we could not reach the setting to ' +\n 'find out. Nothing has changed.'}\n </Alert>\n ) : null}\n {/*\n Every line below reads the plan, and an org doc still in flight\n resolves to the FREE tier (AGL-1064) — which would flash \"Selling\n requires a paid plan\" and the wrong fee percentages at a paying\n seller, then correct itself. Say nothing until it is known.\n */}\n {!planReady ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your plan…'}\n </Typography>\n ) : (\n <>\n {!commerceEnabled ? (\n <Alert severity=\"info\">\n {'Selling requires a paid plan — upgrade on the billing page.'}\n </Alert>\n ) : null}\n {/*\n THE LEGAL NAME, not the org's resolved `productName`\n (AGL-2351). This sentence says who takes money off the\n merchant's own Stripe account, and the application fee lands in\n the platform Stripe account — held by the operating entity, not\n by a white-label agency reselling the console. Naming the\n agency here would assert a fee relationship that does not\n exist. `PLATFORM_BRAND_LEGAL_NAME` still configures cleanly for\n a self-host operator, who really is the entity collecting it.\n */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Buyers pay on your own Stripe account; ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} collects a platform fee ` +\n 'per sale based on your plan:'}\n </Typography>\n {/* The DIGITAL rate is not products-only (AGL-2315). A paid\n booking is a service sale and resolves through the same\n `'service'`/digital axis, so a bookings-only merchant pays this\n exact number — and this card was the one place they could read\n their own rate while naming only \"products\". Stated here rather\n than adding a third figure: it is one rate, not two. */}\n <Typography variant=\"body2\">\n {`Physical products: ${physicalPct}% · Digital products, ` +\n `services & bookings: ${digitalPct}%`}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {' — higher plans reduce fees to 0%'}\n </Typography>\n ) : null}\n </Typography>\n {/* CARD PROCESSING, PASSED THROUGH AT COST (AGL-2152).\n\n Unconditional, including on the 0% plans, because it is\n exactly those plans where the merchant would otherwise read\n \"0%\" and be surprised by a deduction. Their PLATFORM fee\n really is zero — the figure below is Stripe's own charge for\n taking the payment, which every storefront sale incurs and\n which used to be absorbed silently out of the platform's\n balance rather than passed through.\n\n Named as a range rather than one number because the buyer\n chooses the payment method after the session is created:\n cards settle at the low end, buy-now-pay-later at the high\n end, and the deduction is sized for the dearest so a sale can\n never cost more to process than it collects. */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Card processing is passed through at cost on top of that: ' +\n `${Aglyn.MARKETPLACE_PROCESSING_PERCENT_CARD}–` +\n `${Aglyn.MARKETPLACE_PROCESSING_PERCENT_BNPL}% + ` +\n `${Aglyn.MARKETPLACE_PROCESSING_FIXED_CENTS}¢ per online ` +\n 'sale, what Stripe charges to take the payment. It is not a ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} margin.`}\n </Typography>\n {/* EVERY TENDER, AND HOW EACH IS COLLECTED (AGL-2111).\n\n Until now this card said \"per sale\" and meant it for online and\n card sales only: cash and room-charge sales at the register\n recorded no fee at all, so a merchant could read this sentence,\n ring everything as cash, and pay nothing. The fee now attaches\n to the sale on every tender, which makes the sentence above\n true — and creates a second, visible collection route the\n merchant has to be told about BEFORE it appears on their\n invoice, because it is money leaving by a door they have never\n seen it leave by.\n\n Shown only when a rate actually applies: on a 0% plan there is\n nothing to collect either way, and the paragraph would be noise\n on the card of the merchants who upgraded to escape fees. */}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The fee is charged on the sale, not on how it was paid — ' +\n 'in-person cash and room-charge sales carry it too. On card ' +\n 'sales it is deducted from your Stripe payout; on cash and ' +\n 'room-charge sales there is no payout to deduct from, so it ' +\n `is added to your monthly ${Aglyn.PLATFORM_BRAND_LEGAL_NAME} ` +\n 'invoice instead.'}\n </Typography>\n ) : null}\n </>\n )}\n {/*\n AGL-2471: connected, and refused by every money door because the\n account was never verified against this deployment's Stripe mode.\n \"Not set up\" would be a lie in the other direction — the merchant\n did set something up — so the card names what is actually wrong and\n what to do about it.\n */}\n {modeUnverified ? (\n <Alert severity=\"error\">\n {'Your Stripe account has not been verified for payments on ' +\n 'this site, so sales will be refused. Set up payments again ' +\n 'to verify it — if it was connected against Stripe test mode, ' +\n 'you will need to connect a live account.'}\n </Alert>\n ) : null}\n {/*\n The button's own label is a claim — \"Set up payments\" tells the\n owner they have none. Withheld until the answer is in, for the same\n reason as the chip (AGL-1380); on error the Retry above is the only\n action offered, because acting on a setup we could not read is how\n a connected merchant gets sent back through onboarding.\n */}\n {/*\n WHERE THE MONEY STOPPED. Mirrored onto this profile by the\n `payout.failed` handler and retired by the next successful payout, so\n it names a live problem rather than a scar. Without it a merchant sold\n into an account that could not release the funds and the card said\n \"Payments are enabled\" the whole time — the storefront looked healthy\n from every angle available to them.\n */}\n {stripeState === 'loaded' && profile?.lastPayoutFailureAtMs ? (\n <Alert severity=\"warning\" variant=\"outlined\">\n {`A payout of $${(\n Number(profile.lastPayoutFailureCents ?? 0) / 100\n ).toFixed(2)} did not reach your bank on ${new Date(\n Number(profile.lastPayoutFailureAtMs),\n ).toLocaleDateString()} — ${String(\n profile.lastPayoutFailureReason ?? 'Stripe did not say why',\n )} The money is still in your Stripe account. Open Stripe to fix your payout details.`}\n </Alert>\n ) : null}\n {!planReady || stripeState !== 'loaded' ? null : isOwner ? (\n <>\n <Button\n size=\"small\"\n variant={chargesEnabled ? 'text' : 'contained'}\n color=\"primary\"\n disabled={busy}\n onClick={handleConnect}\n sx={{ alignSelf: 'flex-start' }}\n >\n {chargesEnabled\n ? 'Refresh status'\n : busy\n ? 'Opening Stripe…'\n : 'Set up payments'}\n </Button>\n {/*\n Where the money actually is. Aglyn records none of it —\n no balance, no payout schedule, and no `payout.failed` handling\n anywhere — so a merchant whose payout bounced has no way to learn\n that from this console. An Express account has no direct login\n either, which leaves a link minted by the platform as the only\n route in. `target=\"_blank\"` because it leaves the app entirely.\n */}\n {dashboardUrl ? (\n <Button\n size=\"small\"\n variant=\"text\"\n color=\"primary\"\n href={dashboardUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n sx={{ alignSelf: 'flex-start' }}\n >\n {'View payouts in Stripe'}\n </Button>\n ) : null}\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only the organization owner can set up payments.'}\n </Typography>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nPaymentsSettingsCard.displayName = 'PaymentsSettingsCard'\n\nexport default PaymentsSettingsCard\n"],"names":["trackEvent","Aglyn","CardDisplay","useSnackbar","Alert","Button","Chip","Stack","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","useOrgPlan","pluginDocsHelp","authorizedFetch","PaymentsSettingsCard","props","profile","hostId","firestore","data","user","enqueueSnackbar","busy","setBusy","dashboardUrl","setDashboardUrl","org","ready","planReady","retryNonce","setRetryNonce","uid","status","profileStatus","isOwner","Boolean","ownerUid","stripeState","modeVerified","stripeAccountLivemode","chargesConnected","stripeChargesEnabled","modeUnverified","chargesEnabled","physicalPct","resolveTransactionFeePct","digitalPct","commerceEnabled","checkEntitlement","handleConnect","response","method","headers","body","JSON","stringify","payload","json","variant","persist","ok","error","allowDuplicate","payoutsBlocked","payoutsEnabled","url","window","location","assign","console","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","flex","size","label","color","severity","action","onClick","n","PLATFORM_BRAND_LEGAL_NAME","component","MARKETPLACE_PROCESSING_PERCENT_CARD","MARKETPLACE_PROCESSING_PERCENT_BNPL","MARKETPLACE_PROCESSING_FIXED_CENTS","lastPayoutFailureAtMs","Number","lastPayoutFailureCents","toFixed","Date","toLocaleDateString","String","lastPayoutFailureReason","disabled","alignSelf","href","target","rel","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/payments-settings-card.component.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'use client'\n\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, Chip, Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\n\nexport interface PaymentsSettingsCardProps {\n hostId: string\n}\n\n/**\n * Merchant payments card (AGL-284): Stripe Connect Express onboarding\n * status for the org owner + the plan's transaction-fee ladder so the\n * \"upgrade to kill fees\" motion (AGL-278) is visible where it matters.\n */\nexport function PaymentsSettingsCard(props: PaymentsSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n /**\n * A one-time Stripe Express dashboard link, minted by the connect route on\n * request. Not persisted: login links are single-use and short-lived, so a\n * stored one would be a button that fails the second time it is pressed.\n */\n const [dashboardUrl, setDashboardUrl] = useState<string | null>(null)\n const { org, ready: planReady } = useOrgPlan(hostId)\n /** Bumped by Retry to re-subscribe the profile listen (AGL-1380). */\n const [retryNonce, setRetryNonce] = useState(0)\n const uid = (user as any)?.uid as string | undefined\n const { data: profile, status: profileStatus } = useFirestoreDoc<any>(\n // Held at null rather than addressed as `profiles/-pending-` — that\n // sentinel issued a guaranteed-denied read on every pre-auth mount, and\n // a denial lands in `status: 'error'`, which now means something.\n () => (uid ? doc(firestore, 'profiles', uid) : null),\n [firestore, uid, retryNonce],\n )\n const isOwner = Boolean(\n org?.ownerUid && (user as any)?.uid === org.ownerUid,\n )\n /**\n * Three outcomes, not two (AGL-1380). An absent profile doc is the value\n * BEFORE an answer as much as it is the value of a merchant who never\n * connected Stripe, so \"Not set up\" off a falsy `stripeChargesEnabled` is a\n * claim about this org's payment setup made without having heard back. It\n * is wrong in the direction that provokes action: the same falsy value\n * turns the button into \"Set up payments\", which sends a merchant who is\n * already taking money into Stripe onboarding.\n */\n const stripeState: 'pending' | 'error' | 'loaded' =\n !uid || profileStatus === 'loading'\n ? 'pending'\n : profileStatus === 'error'\n ? 'error'\n : 'loaded'\n /**\n * WHICH STRIPE WORLD THE STORED ACCOUNT BELONGS TO (AGL-2471).\n *\n * `stripeChargesEnabled: true` was the whole readiness claim, and a\n * production profile carrying it named a TEST-mode account: this card said\n * \"Charges enabled\" about three storefronts that could not take a single\n * payment. Only a literal boolean counts as recorded — the same\n * three-valued reading `merchantAccountIsReady` uses on the server, so the\n * chip and the checkout cannot disagree.\n */\n const modeVerified = typeof profile?.stripeAccountLivemode === 'boolean'\n const chargesConnected =\n stripeState === 'loaded' && Boolean(profile?.stripeChargesEnabled)\n const modeUnverified = chargesConnected && !modeVerified\n const chargesEnabled = chargesConnected && modeVerified\n const physicalPct = Aglyn.resolveTransactionFeePct(org, 'physical')\n const digitalPct = Aglyn.resolveTransactionFeePct(org, 'digital')\n const commerceEnabled = Aglyn.checkEntitlement(org, 'commerce')\n\n const handleConnect = useCallback(async () => {\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/connect', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId }),\n })\n const payload = await response.json()\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'Payments are not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Payment setup failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n if (payload.chargesEnabled) {\n // \"% who connect Stripe\" — a GTM plan §6 activation metric\n // (AGL-1561). Gated on the TRANSITION, not the state: this handler\n // is a re-entrant create-or-resume, so it answers `chargesEnabled:\n // true` on every subsequent visit to a connected account, and a bare\n // event here would count one merchant once per click forever.\n // `chargesEnabled` is the state read from the profile BEFORE this\n // request, so the event fires only when onboarding actually completed.\n if (!chargesEnabled) trackEvent('stripe_connected', {})\n // CHARGES AND PAYOUTS ARE TWO FLAGS, AND ONLY ONE OF THEM IS THIS\n // SENTENCE (AGL-1997). `chargesEnabled` says Stripe will let the\n // account take money; whether the money can LEAVE is `payoutsEnabled`,\n // which the route answers alongside it. Charges-yes/payouts-no is an\n // ordinary Stripe state — verification pending or lapsed, no payout\n // method — and announcing \"Payments are enabled\" in it is false in the\n // direction that costs the merchant: they sell believing they are\n // paid, and the funds sit in a Connect account that cannot release\n // them. Read three-valued, so only a literal `false` from Stripe\n // downgrades the claim; an absent flag means unasked, not off.\n const payoutsBlocked = payload.payoutsEnabled === false\n // A WARNING THAT NAMES A PROBLEM MUST OFFER THE FIX.\n //\n // Stripe is the only place payouts can be released — an Express\n // account has no password and no direct login — so telling the\n // merchant their funds are stranded and then keeping them here is half\n // an answer. The route now mints the remediation link for exactly this\n // state; going there is the whole point of pressing the button.\n if (payoutsBlocked && payload.url) {\n return void window.location.assign(payload.url)\n }\n // Payouts are flowing, so the link is the Express dashboard: balance,\n // payout schedule, and the reason a payout failed. Held in state\n // rather than followed, because this click was a status check and\n // navigating away from one nobody asked to leave is its own defect.\n if (!payoutsBlocked && typeof payload.dashboardUrl === 'string') {\n setDashboardUrl(payload.dashboardUrl)\n }\n return void enqueueSnackbar(\n payoutsBlocked\n ? 'Connected — payouts are not released yet'\n : 'Payments are enabled',\n {\n variant: payoutsBlocked ? 'warning' : 'success',\n persist: false,\n },\n )\n }\n if (payload.url) window.location.assign(payload.url)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [user, hostId, chargesEnabled, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Payments'}\n help={pluginDocsHelp('commerceEndToEnd', {\n anchor: '#1-connect-payments',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {'Stripe account'}\n </Typography>\n {/*\n No chip at all on error: both labels it could carry — \"Charges\n enabled\" and \"Not set up\" — assert something we failed to find\n out. Whatever Stripe is doing right now it carries on doing.\n */}\n {stripeState === 'error' ? null : (\n <Chip\n size=\"small\"\n label={\n stripeState === 'pending'\n ? 'Checking…'\n : chargesEnabled\n ? 'Charges enabled'\n : modeUnverified\n ? 'Not verified'\n : 'Not set up'\n }\n color={\n stripeState === 'pending'\n ? 'default'\n : chargesEnabled\n ? 'success'\n : modeUnverified\n ? 'error'\n : 'warning'\n }\n variant=\"outlined\"\n />\n )}\n </Stack>\n {stripeState === 'error' ? (\n <Alert\n severity=\"error\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n onClick={() => setRetryNonce((n) => n + 1)}\n >\n {'Retry'}\n </Button>\n }\n >\n {'We couldn’t load your payment setup. This says nothing about ' +\n 'whether payments are on — we could not reach the setting to ' +\n 'find out. Nothing has changed.'}\n </Alert>\n ) : null}\n {/*\n Every line below reads the plan, and an org doc still in flight\n resolves to the FREE tier (AGL-1064) — which would flash \"Selling\n requires a paid plan\" and the wrong fee percentages at a paying\n seller, then correct itself. Say nothing until it is known.\n */}\n {!planReady ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your plan…'}\n </Typography>\n ) : (\n <>\n {!commerceEnabled ? (\n <Alert severity=\"info\">\n {'Selling requires a paid plan — upgrade on the billing page.'}\n </Alert>\n ) : null}\n {/*\n THE LEGAL NAME, not the org's resolved `productName`\n (AGL-2351). This sentence says who takes money off the\n merchant's own Stripe account, and the application fee lands in\n the platform Stripe account — held by the operating entity, not\n by a white-label agency reselling the console. Naming the\n agency here would assert a fee relationship that does not\n exist. `PLATFORM_BRAND_LEGAL_NAME` still configures cleanly for\n a self-host operator, who really is the entity collecting it.\n */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Buyers pay on your own Stripe account; ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} collects a platform fee ` +\n 'per sale based on your plan:'}\n </Typography>\n {/* The DIGITAL rate is not products-only (AGL-2315). A paid\n booking is a service sale and resolves through the same\n `'service'`/digital axis, so a bookings-only merchant pays this\n exact number — and this card was the one place they could read\n their own rate while naming only \"products\". Stated here rather\n than adding a third figure: it is one rate, not two. */}\n <Typography variant=\"body2\">\n {`Physical products: ${physicalPct}% · Digital products, ` +\n `services & bookings: ${digitalPct}%`}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {' — higher plans reduce fees to 0%'}\n </Typography>\n ) : null}\n </Typography>\n {/* CARD PROCESSING, PASSED THROUGH AT COST (AGL-2152).\n\n Unconditional, including on the 0% plans, because it is\n exactly those plans where the merchant would otherwise read\n \"0%\" and be surprised by a deduction. Their PLATFORM fee\n really is zero — the figure below is Stripe's own charge for\n taking the payment, which every storefront sale incurs and\n which used to be absorbed silently out of the platform's\n balance rather than passed through.\n\n Named as a range rather than one number because the buyer\n chooses the payment method after the session is created:\n cards settle at the low end, buy-now-pay-later at the high\n end, and the deduction is sized for the dearest so a sale can\n never cost more to process than it collects. */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Card processing is passed through at cost on top of that: ' +\n `${Aglyn.MARKETPLACE_PROCESSING_PERCENT_CARD}–` +\n `${Aglyn.MARKETPLACE_PROCESSING_PERCENT_BNPL}% + ` +\n `${Aglyn.MARKETPLACE_PROCESSING_FIXED_CENTS}¢ per online ` +\n 'sale, what Stripe charges to take the payment. It is not a ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} margin.`}\n </Typography>\n {/* EVERY TENDER, AND HOW EACH IS COLLECTED (AGL-2111).\n\n Until now this card said \"per sale\" and meant it for online and\n card sales only: cash and room-charge sales at the register\n recorded no fee at all, so a merchant could read this sentence,\n ring everything as cash, and pay nothing. The fee now attaches\n to the sale on every tender, which makes the sentence above\n true — and creates a second, visible collection route the\n merchant has to be told about BEFORE it appears on their\n invoice, because it is money leaving by a door they have never\n seen it leave by.\n\n Shown only when a rate actually applies: on a 0% plan there is\n nothing to collect either way, and the paragraph would be noise\n on the card of the merchants who upgraded to escape fees. */}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The fee is charged on the sale, not on how it was paid — ' +\n 'in-person cash and room-charge sales carry it too. On card ' +\n 'sales it is deducted from your Stripe payout; on cash and ' +\n 'room-charge sales there is no payout to deduct from, so it ' +\n `is added to your monthly ${Aglyn.PLATFORM_BRAND_LEGAL_NAME} ` +\n 'invoice instead.'}\n </Typography>\n ) : null}\n </>\n )}\n {/*\n AGL-2471: connected, and refused by every money door because the\n account was never verified against this deployment's Stripe mode.\n \"Not set up\" would be a lie in the other direction — the merchant\n did set something up — so the card names what is actually wrong and\n what to do about it.\n */}\n {modeUnverified ? (\n <Alert severity=\"error\">\n {'Your Stripe account has not been verified for payments on ' +\n 'this site, so sales will be refused. Set up payments again ' +\n 'to verify it — if it was connected against Stripe test mode, ' +\n 'you will need to connect a live account.'}\n </Alert>\n ) : null}\n {/*\n The button's own label is a claim — \"Set up payments\" tells the\n owner they have none. Withheld until the answer is in, for the same\n reason as the chip (AGL-1380); on error the Retry above is the only\n action offered, because acting on a setup we could not read is how\n a connected merchant gets sent back through onboarding.\n */}\n {/*\n WHERE THE MONEY STOPPED. Mirrored onto this profile by the\n `payout.failed` handler and retired by the next successful payout, so\n it names a live problem rather than a scar. Without it a merchant sold\n into an account that could not release the funds and the card said\n \"Payments are enabled\" the whole time — the storefront looked healthy\n from every angle available to them.\n */}\n {stripeState === 'loaded' && profile?.lastPayoutFailureAtMs ? (\n <Alert severity=\"warning\" variant=\"outlined\">\n {`A payout of $${(\n Number(profile.lastPayoutFailureCents ?? 0) / 100\n ).toFixed(2)} did not reach your bank on ${new Date(\n Number(profile.lastPayoutFailureAtMs),\n ).toLocaleDateString()} — ${String(\n profile.lastPayoutFailureReason ?? 'Stripe did not say why',\n )} The money is still in your Stripe account. Open Stripe to fix your payout details.`}\n </Alert>\n ) : null}\n {!planReady || stripeState !== 'loaded' ? null : isOwner ? (\n <>\n <Button\n size=\"small\"\n variant={chargesEnabled ? 'text' : 'contained'}\n color=\"primary\"\n disabled={busy}\n onClick={handleConnect}\n sx={{ alignSelf: 'flex-start' }}\n >\n {chargesEnabled\n ? 'Refresh status'\n : busy\n ? 'Opening Stripe…'\n : 'Set up payments'}\n </Button>\n {/*\n Where the money actually is. Aglyn records none of it —\n no balance, no payout schedule, and no `payout.failed` handling\n anywhere — so a merchant whose payout bounced has no way to learn\n that from this console. An Express account has no direct login\n either, which leaves a link minted by the platform as the only\n route in. `target=\"_blank\"` because it leaves the app entirely.\n */}\n {dashboardUrl ? (\n <Button\n size=\"small\"\n variant=\"text\"\n color=\"primary\"\n href={dashboardUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n sx={{ alignSelf: 'flex-start' }}\n >\n {'View payouts in Stripe'}\n </Button>\n ) : null}\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only the organization owner can set up payments.'}\n </Typography>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nPaymentsSettingsCard.displayName = 'PaymentsSettingsCard'\n\nexport default PaymentsSettingsCard\n"],"names":["trackEvent","Aglyn","CardDisplay","useSnackbar","Alert","Button","Chip","Stack","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","useOrgPlan","pluginDocsHelp","authorizedFetch","PaymentsSettingsCard","props","profile","hostId","firestore","data","user","enqueueSnackbar","busy","setBusy","dashboardUrl","setDashboardUrl","org","ready","planReady","retryNonce","setRetryNonce","uid","status","profileStatus","isOwner","Boolean","ownerUid","stripeState","modeVerified","stripeAccountLivemode","chargesConnected","stripeChargesEnabled","modeUnverified","chargesEnabled","physicalPct","resolveTransactionFeePct","digitalPct","commerceEnabled","checkEntitlement","handleConnect","response","method","headers","body","JSON","stringify","payload","json","variant","persist","ok","error","allowDuplicate","payoutsBlocked","payoutsEnabled","url","window","location","assign","console","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","flex","size","label","color","severity","action","onClick","n","PLATFORM_BRAND_LEGAL_NAME","component","MARKETPLACE_PROCESSING_PERCENT_CARD","MARKETPLACE_PROCESSING_PERCENT_BNPL","MARKETPLACE_PROCESSING_FIXED_CENTS","lastPayoutFailureAtMs","Number","lastPayoutFailureCents","toFixed","Date","toLocaleDateString","String","lastPayoutFailureReason","disabled","alignSelf","href","target","rel","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,UAAU,QAAQ,0CAAyC;AACpE,YAAYC,WAAW,eAAc;AACrC,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SAASC,KAAK,EAAEC,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACtE,SAASC,GAAG,QAAQ,qBAAoB;AACxC,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAC7C,SAASC,YAAY,EAAEC,OAAO,QAAQ,iCAAgC;AACtE,SAASC,eAAe,QAAQ,iCAAgC;AAChE,SAASC,UAAU,QAAQ,iCAAgC;AAC3D,SAASC,cAAc,QAAQ,eAAc;AAC7C,SAASC,eAAe,QAAQ,2CAA0C;AAM1E;;;;CAIC,GACD,OAAO,SAASC,qBAAqBC,KAAgC;QAgVhDC,iCAIPA;IAnVZ,MAAM,EAAEC,MAAM,EAAE,GAAGF;IACnB,MAAMG,YAAYV;IAClB,MAAM,EAAEW,MAAMC,IAAI,EAAE,GAAGX;IACvB,MAAM,EAAEY,eAAe,EAAE,GAAGtB;IAC5B,MAAM,CAACuB,MAAMC,QAAQ,GAAGhB,SAAS;IACjC;;;;GAIC,GACD,MAAM,CAACiB,cAAcC,gBAAgB,GAAGlB,SAAwB;IAChE,MAAM,EAAEmB,GAAG,EAAEC,OAAOC,SAAS,EAAE,GAAGjB,WAAWM;IAC7C,mEAAmE,GACnE,MAAM,CAACY,YAAYC,cAAc,GAAGvB,SAAS;IAC7C,MAAMwB,MAAOX,wBAAD,AAACA,KAAcW,GAAG;IAC9B,MAAM,EAAEZ,MAAMH,OAAO,EAAEgB,QAAQC,aAAa,EAAE,GAAGvB,gBAC/C,oEAAoE;IACpE,wEAAwE;IACxE,kEAAkE;IAClE,IAAOqB,MAAM1B,IAAIa,WAAW,YAAYa,OAAO,MAC/C;QAACb;QAAWa;QAAKF;KAAW;IAE9B,MAAMK,UAAUC,QACdT,CAAAA,uBAAAA,IAAKU,QAAQ,KAAI,CAAChB,wBAAD,AAACA,KAAcW,GAAG,MAAKL,IAAIU,QAAQ;IAEtD;;;;;;;;GAQC,GACD,MAAMC,cACJ,CAACN,OAAOE,kBAAkB,YACtB,YACAA,kBAAkB,UAChB,UACA;IACR;;;;;;;;;GASC,GACD,MAAMK,eAAe,QAAOtB,2BAAAA,QAASuB,qBAAqB,MAAK;IAC/D,MAAMC,mBACJH,gBAAgB,YAAYF,QAAQnB,2BAAAA,QAASyB,oBAAoB;IACnE,MAAMC,iBAAiBF,oBAAoB,CAACF;IAC5C,MAAMK,iBAAiBH,oBAAoBF;IAC3C,MAAMM,cAAc/C,MAAMgD,wBAAwB,CAACnB,KAAK;IACxD,MAAMoB,aAAajD,MAAMgD,wBAAwB,CAACnB,KAAK;IACvD,MAAMqB,kBAAkBlD,MAAMmD,gBAAgB,CAACtB,KAAK;IAEpD,MAAMuB,gBAAgB3C,YAAY;QAChCiB,QAAQ;QACR,IAAI;YACF,MAAM2B,WAAW,MAAMrC,gBAAgBO,MAAM,yBAAyB;gBACpE+B,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CC,MAAMC,KAAKC,SAAS,CAAC;oBAAEtC;gBAAO;YAChC;YACA,MAAMuC,UAAU,MAAMN,SAASO,IAAI;YACnC,IAAIP,SAASlB,MAAM,KAAK,KAAK;gBAC3B,OAAO,KAAKX,gBACV,kDACA;oBAAEqC,SAAS;oBAAQC,SAAS;gBAAM;YAEtC;YACA,IAAI,CAACT,SAASU,EAAE,EAAE;;gBAChB,OAAO,KAAKvC,wBAAgBmC,2BAAAA,QAASK,KAAK,mBAAI,wBAAwB;oBACpEH,SAAS;oBACTI,gBAAgB;gBAClB;YACF;YACA,IAAIN,QAAQb,cAAc,EAAE;gBAC1B,2DAA2D;gBAC3D,mEAAmE;gBACnE,mEAAmE;gBACnE,qEAAqE;gBACrE,8DAA8D;gBAC9D,kEAAkE;gBAClE,uEAAuE;gBACvE,IAAI,CAACA,gBAAgB/C,WAAW,oBAAoB,CAAC;gBACrD,kEAAkE;gBAClE,iEAAiE;gBACjE,uEAAuE;gBACvE,qEAAqE;gBACrE,oEAAoE;gBACpE,uEAAuE;gBACvE,kEAAkE;gBAClE,mEAAmE;gBACnE,iEAAiE;gBACjE,+DAA+D;gBAC/D,MAAMmE,iBAAiBP,QAAQQ,cAAc,KAAK;gBAClD,qDAAqD;gBACrD,EAAE;gBACF,gEAAgE;gBAChE,+DAA+D;gBAC/D,uEAAuE;gBACvE,uEAAuE;gBACvE,gEAAgE;gBAChE,IAAID,kBAAkBP,QAAQS,GAAG,EAAE;oBACjC,OAAO,KAAKC,OAAOC,QAAQ,CAACC,MAAM,CAACZ,QAAQS,GAAG;gBAChD;gBACA,sEAAsE;gBACtE,iEAAiE;gBACjE,kEAAkE;gBAClE,oEAAoE;gBACpE,IAAI,CAACF,kBAAkB,OAAOP,QAAQhC,YAAY,KAAK,UAAU;oBAC/DC,gBAAgB+B,QAAQhC,YAAY;gBACtC;gBACA,OAAO,KAAKH,gBACV0C,iBACI,6CACA,wBACJ;oBACEL,SAASK,iBAAiB,YAAY;oBACtCJ,SAAS;gBACX;YAEJ;YACA,IAAIH,QAAQS,GAAG,EAAEC,OAAOC,QAAQ,CAACC,MAAM,CAACZ,QAAQS,GAAG;QACrD,EAAE,OAAOJ,OAAO;YACdQ,QAAQR,KAAK,CAACA;YACdxC,gBAAgB,yBAAyB;gBACvCqC,SAAS;gBACTI,gBAAgB;YAClB;QACF,SAAU;YACRvC,QAAQ;QACV;IACF,GAAG;QAACH;QAAMH;QAAQ0B;QAAgBtB;KAAgB;IAElD,qBACE,KAACvB;QACCwE,QAAQ;QACRC,MAAM3D,eAAe,oBAAoB;YACvC4D,QAAQ;QACV;QACAC,cAAc;QACdC,cAAc;kBAEd,cAAA,MAACvE;YAAMwE,SAAS;;8BACd,MAACxE;oBAAMyE,WAAU;oBAAMD,SAAS;oBAAGE,IAAI;wBAAEC,YAAY;oBAAS;;sCAC5D,KAAC1E;4BAAWsD,SAAQ;4BAAQmB,IAAI;gCAAEE,MAAM;4BAAE;sCACvC;;wBAOF1C,gBAAgB,UAAU,qBACzB,KAACnC;4BACC8E,MAAK;4BACLC,OACE5C,gBAAgB,YACZ,cACAM,iBACE,oBACAD,iBACE,iBACA;4BAEVwC,OACE7C,gBAAgB,YACZ,YACAM,iBACE,YACAD,iBACE,UACA;4BAEVgB,SAAQ;;;;gBAIbrB,gBAAgB,wBACf,KAACrC;oBACCmF,UAAS;oBACTC,sBACE,KAACnF;wBACCiF,OAAM;wBACNF,MAAK;wBACLK,SAAS,IAAMvD,cAAc,CAACwD,IAAMA,IAAI;kCAEvC;;8BAIJ,kEACC,iEACA;qBAEF;gBAOH,CAAC1D,0BACA,KAACxB;oBAAWsD,SAAQ;oBAAQwB,OAAM;8BAC/B;mCAGH;;wBACG,CAACnC,gCACA,KAAC/C;4BAAMmF,UAAS;sCACb;6BAED;sCAWJ,KAAC/E;4BAAWsD,SAAQ;4BAAQwB,OAAM;sCAC/B,4CACC,GAAGrF,MAAM0F,yBAAyB,CAAC,yBAAyB,CAAC,GAC7D;;sCAQJ,MAACnF;4BAAWsD,SAAQ;;gCACjB,CAAC,mBAAmB,EAAEd,YAAY,sBAAsB,CAAC,GACxD,CAAC,qBAAqB,EAAEE,WAAW,CAAC,CAAC;gCACtCF,cAAc,KAAKE,aAAa,kBAC/B,KAAC1C;oCACCoF,WAAU;oCACV9B,SAAQ;oCACRwB,OAAM;8CAEL;qCAED;;;sCAiBN,KAAC9E;4BAAWsD,SAAQ;4BAAQwB,OAAM;sCAC/B,+DACC,GAAGrF,MAAM4F,mCAAmC,CAAC,CAAC,CAAC,GAC/C,GAAG5F,MAAM6F,mCAAmC,CAAC,IAAI,CAAC,GAClD,GAAG7F,MAAM8F,kCAAkC,CAAC,aAAa,CAAC,GAC1D,gEACA,GAAG9F,MAAM0F,yBAAyB,CAAC,QAAQ,CAAC;;wBAiB/C3C,cAAc,KAAKE,aAAa,kBAC/B,KAAC1C;4BAAWsD,SAAQ;4BAAQwB,OAAM;sCAC/B,8DACC,gEACA,+DACA,gEACA,CAAC,yBAAyB,EAAErF,MAAM0F,yBAAyB,CAAC,CAAC,CAAC,GAC9D;6BAEF;;;gBAUP7C,+BACC,KAAC1C;oBAAMmF,UAAS;8BACb,+DACC,gEACA,kEACA;qBAEF;gBAgBH9C,gBAAgB,aAAYrB,2BAAAA,QAAS4E,qBAAqB,kBACzD,KAAC5F;oBAAMmF,UAAS;oBAAUzB,SAAQ;8BAC/B,CAAC,aAAa,EAAE,AACfmC,CAAAA,QAAO7E,kCAAAA,QAAQ8E,sBAAsB,YAA9B9E,kCAAkC,KAAK,GAAE,EAChD+E,OAAO,CAAC,GAAG,4BAA4B,EAAE,IAAIC,KAC7CH,OAAO7E,QAAQ4E,qBAAqB,GACpCK,kBAAkB,GAAG,GAAG,EAAEC,QAC1BlF,mCAAAA,QAAQmF,uBAAuB,YAA/BnF,mCAAmC,0BACnC,mFAAmF,CAAC;qBAEtF;gBACH,CAACY,aAAaS,gBAAgB,WAAW,OAAOH,wBAC/C;;sCACE,KAACjC;4BACC+E,MAAK;4BACLtB,SAASf,iBAAiB,SAAS;4BACnCuC,OAAM;4BACNkB,UAAU9E;4BACV+D,SAASpC;4BACT4B,IAAI;gCAAEwB,WAAW;4BAAa;sCAE7B1D,iBACG,mBACArB,OACE,oBACA;;wBAUPE,6BACC,KAACvB;4BACC+E,MAAK;4BACLtB,SAAQ;4BACRwB,OAAM;4BACNoB,MAAM9E;4BACN+E,QAAO;4BACPC,KAAI;4BACJ3B,IAAI;gCAAEwB,WAAW;4BAAa;sCAE7B;6BAED;;mCAGN,KAACjG;oBAAWsD,SAAQ;oBAAUwB,OAAM;8BACjC;;;;;AAMb;AACApE,qBAAqB2F,WAAW,GAAG;AAEnC,eAAe3F,qBAAoB"}
|