@openrails/billing-ui 0.250.0 → 0.252.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +19 -1
  3. package/dist/admin/messages.d.ts +8 -0
  4. package/dist/{admin-catalog-3pPEAuDY.js → admin-catalog-Bsxo2gyS.js} +2 -2
  5. package/dist/{admin-catalog-3pPEAuDY.js.map → admin-catalog-Bsxo2gyS.js.map} +1 -1
  6. package/dist/{admin-price-detail-BeGDPxKX.js → admin-price-detail-ByCmrx_W.js} +2 -3
  7. package/dist/admin-price-detail-ByCmrx_W.js.map +1 -0
  8. package/dist/{admin-price-wizard-ferZSTfO.js → admin-price-wizard-m7Qjhq6W.js} +2 -2
  9. package/dist/admin-price-wizard-m7Qjhq6W.js.map +1 -0
  10. package/dist/{admin-settings-C6eWFkDn.js → admin-settings-BIUgvclv.js} +99 -17
  11. package/dist/admin-settings-BIUgvclv.js.map +1 -0
  12. package/dist/admin.js +13 -5
  13. package/dist/admin.js.map +1 -1
  14. package/dist/client/generated/error-codes.d.ts +1 -1
  15. package/dist/client/generated/routes.d.ts +4 -4
  16. package/dist/client/generated/wire.d.ts +7 -6
  17. package/dist/client/types.d.ts +6 -0
  18. package/dist/{client-HWr09qpX.js → client-imFR1UCe.js} +82 -81
  19. package/dist/client-imFR1UCe.js.map +1 -0
  20. package/dist/client.js +2 -2
  21. package/dist/hooks-C9JkHcRt.js +553 -0
  22. package/dist/hooks-C9JkHcRt.js.map +1 -0
  23. package/dist/index.js +778 -745
  24. package/dist/index.js.map +1 -1
  25. package/dist/react/config.d.ts +12 -3
  26. package/dist/react/provider.d.ts +14 -1
  27. package/dist/react.js +1 -1
  28. package/package.json +1 -2
  29. package/dist/admin-price-detail-BeGDPxKX.js.map +0 -1
  30. package/dist/admin-price-wizard-ferZSTfO.js.map +0 -1
  31. package/dist/admin-settings-C6eWFkDn.js.map +0 -1
  32. package/dist/client-HWr09qpX.js.map +0 -1
  33. package/dist/hooks-D9MR20BY.js +0 -537
  34. package/dist/hooks-D9MR20BY.js.map +0 -1
@@ -7,7 +7,7 @@ import { a as d, n as f, o as p, r as m, s as h, t as g } from "./admin-table-CW
7
7
  import { t as _ } from "./admin-badge-VCGbnfiN.js";
8
8
  import { n as v, t as y } from "./admin-price-format-DM1qORWI.js";
9
9
  import { a as b, i as x, o as S, r as C, t as w } from "./admin-card-DGbi6ZHZ.js";
10
- import { t as T } from "./admin-price-wizard-ferZSTfO.js";
10
+ import { t as T } from "./admin-price-wizard-m7Qjhq6W.js";
11
11
  import { t as E } from "./admin-fact-card-D6mZgxpN.js";
12
12
  import * as D from "react";
13
13
  import { useMutation as O, useQuery as k, useQueryClient as A } from "@tanstack/react-query";
@@ -60,7 +60,6 @@ var G = [
60
60
  "price_id",
61
61
  "product_id",
62
62
  "plan_id",
63
- "form_name",
64
63
  "flex_id",
65
64
  "recurring_billing_option_id",
66
65
  "lookup_key",
@@ -479,4 +478,4 @@ function Z({ migrationId: e }) {
479
478
  //#endregion
480
479
  export { X as PriceDetailPage };
481
480
 
482
- //# sourceMappingURL=admin-price-detail-BeGDPxKX.js.map
481
+ //# sourceMappingURL=admin-price-detail-ByCmrx_W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-price-detail-ByCmrx_W.js","names":[],"sources":["../src/admin/pages/catalog/psp-links.tsx","../src/admin/pages/catalog/price-detail.tsx"],"sourcesContent":["import { AdminMessage } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\nimport { useQuery } from \"@tanstack/react-query\"\n\nimport { Badge } from \"#orck/admin/components/ui/badge\"\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from \"#orck/admin/components/ui/card\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"#orck/admin/components/ui/table\"\nimport type { PSPRoutingSkip } from \"#orck/admin/lib/api/types\"\nimport type { PSPLinkState, Price } from \"#orck/client/generated/wire\"\n\n// The catalog is declarative: the provider adapter pushes prices and the\n// read-back reconciliation is alert-only, so this surface displays a price's\n// psps and never edits them. It answers two different questions:\n// - Link state (`price.providers`): does OpenRails hold a link for this PSP,\n// and does the remote object still match?\n// - Purchase readiness (the routing preview): would a real purchase land\n// here now? A linked price can still be unroutable (PSP unarmed,\n// credentials missing).\n\n// SKIP_LABELS renders the routing skip vocabulary for operators. Keep the keys\n// in lockstep with internal/db/models/order_attempt.go.\nconst SKIP_LABELS: Record<PSPRoutingSkip, string> = {\n unknown_selector: \"That name matches no provider you have set up.\",\n ambiguous_selector:\n \"More than one provider on this rail is set up. Name the provider rather than the rail.\",\n not_armed: \"This provider is not set up here, or it was archived.\",\n credentials_missing:\n \"The provider is set up, but the credentials it needs are missing.\",\n link_missing: \"This price has not been sent to the provider yet.\",\n mode_unsupported:\n \"This provider cannot take this kind of payment (one-off or recurring).\",\n posture_disarmed:\n \"The provider's credentials failed their test-mode check, so it is switched off.\",\n service_unavailable: \"The connection to this provider is not running.\",\n resolve_failed: \"The provider could not be worked out at the time of asking.\",\n}\n\n// The engine names each outcome for its own logs. These are the same outcomes\n// as a sentence you can act on.\nconst SKIP_HEADLINES: Record<PSPRoutingSkip, string> = {\n unknown_selector: \"unknown provider\",\n ambiguous_selector: \"more than one match\",\n not_armed: \"not set up\",\n credentials_missing: \"credentials missing\",\n link_missing: \"not sent to provider\",\n mode_unsupported: \"cannot take this payment\",\n posture_disarmed: \"credentials failed check\",\n service_unavailable: \"connection not running\",\n resolve_failed: \"could not be worked out\",\n}\n\nfunction skipHeadline(skip?: string | null): string {\n if (!skip) return \"ready\"\n return SKIP_HEADLINES[skip as PSPRoutingSkip] ?? skip\n}\n\nfunction skipLabel(skip?: string | null) {\n if (!skip) return \"\"\n return SKIP_LABELS[skip as PSPRoutingSkip] ?? skip\n}\n\nconst OK_BADGE = \"bg-settled-surface text-settled\"\nconst WARN_BADGE = \"bg-held-surface text-held\"\nconst BAD_BADGE = \"bg-failed-surface text-failed\"\n\nfunction linkStatusClass(status: PSPLinkState[\"status\"]) {\n if (status === \"linked\") return OK_BADGE\n if (status === \"error\") return BAD_BADGE\n return WARN_BADGE\n}\n\n// syncStatusClass leaves \"unknown\" neutral, not a warning: nobody has checked\n// yet.\nfunction syncStatusClass(sync?: string) {\n switch (sync) {\n case \"in_sync\":\n return OK_BADGE\n case \"drifted\":\n case \"missing\":\n return BAD_BADGE\n case \"never_synced\":\n case \"sync_disabled\":\n return WARN_BADGE\n default:\n return \"\"\n }\n}\n\n// LINK_ID_ORDER puts the identifying fields first; anything else the adapter\n// stored follows in sorted order. `rail` is pulled out into its own column.\nconst LINK_ID_ORDER = [\n \"price_id\",\n \"product_id\",\n \"plan_id\",\n \"flex_id\",\n \"recurring_billing_option_id\",\n \"lookup_key\",\n \"provider\",\n]\n\nfunction linkEntries(ids?: Record<string, string> | null) {\n const rest = Object.entries(ids ?? {}).filter(\n ([k, v]) => k !== \"rail\" && v !== \"\"\n )\n rest.sort(([a], [b]) => {\n const ia = LINK_ID_ORDER.indexOf(a)\n const ib = LINK_ID_ORDER.indexOf(b)\n if (ia !== ib)\n return (\n (ia < 0 ? LINK_ID_ORDER.length : ia) -\n (ib < 0 ? LINK_ID_ORDER.length : ib)\n )\n return a.localeCompare(b)\n })\n return rest\n}\n\nexport function PSPLinksCard({\n price,\n verifying,\n verified,\n onVerify,\n}: {\n price: Price\n verifying: boolean\n verified: boolean\n onVerify: () => void\n}) {\n const links = Object.entries(price.psps ?? {}).sort(([a], [b]) =>\n a.localeCompare(b)\n )\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-start justify-between gap-2\">\n <div className=\"grid gap-1\">\n <CardTitle className=\"text-sm\">\n <AdminMessage id={\"Payment providers\"} />\n </CardTitle>\n <CardDescription>\n <AdminMessage\n id={\n \"Which providers this price has been sent to, and whether their copy still matches yours. Publishing the catalog is what sends it; this page only reports. Verify asks each provider what it currently holds, and changes nothing.\"\n }\n />\n </CardDescription>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={verifying || !links.length}\n onClick={onVerify}\n >\n {verifying ? \"Verifying…\" : \"Verify\"}\n </Button>\n </CardHeader>\n <CardContent className=\"flex flex-col gap-3\">\n {!links.length ? (\n <p className=\"text-sm text-muted-foreground\">\n <AdminMessage\n id={\n \"This price has not been sent to any provider, so nobody can be charged for it yet.\"\n }\n />\n </p>\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Provider\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Rail\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Sent\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Still matches\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Their reference\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {links.map(([psp, state]) => (\n <TableRow key={psp}>\n <TableCell className=\"font-mono text-xs\">{psp}</TableCell>\n <TableCell className=\"text-xs\">\n {state.ids?.rail ?? \"—\"}\n </TableCell>\n <TableCell>\n <Badge\n variant=\"secondary\"\n className={linkStatusClass(state.status)}\n >\n {state.status}\n </Badge>\n {state.message && (\n <p className=\"mt-1 max-w-xs text-xs text-muted-foreground\">\n {state.message}\n </p>\n )}\n </TableCell>\n <TableCell>\n <Badge\n variant=\"secondary\"\n className={syncStatusClass(state.sync_status)}\n >\n {state.sync_status ?? \"unknown\"}\n </Badge>\n {state.drift?.map((d) => (\n <p\n key={d.field}\n className=\"mt-1 text-xs text-muted-foreground\"\n >\n {d.field}\n <AdminMessage id={\": yours \"} />\n {d.openrails_value}\n <AdminMessage id={\" · theirs\"} /> {d.remote_value}\n </p>\n ))}\n </TableCell>\n <TableCell className=\"font-mono text-xs\">\n {linkEntries(state.ids).length ? (\n <div className=\"grid gap-0.5\">\n {linkEntries(state.ids).map(([k, v]) => (\n <span key={k}>\n {k}={v}\n </span>\n ))}\n </div>\n ) : (\n \"—\"\n )}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n {!!price.pending_manual_actions?.length && (\n <div className=\"border-held/40 rounded-md border p-3 text-xs\">\n {price.pending_manual_actions.map((a) => (\n <p key={`${a.psp}-${a.action}`}>\n <span className=\"font-mono\">{a.psp}</span>: {a.hint || a.action}\n </p>\n ))}\n </div>\n )}\n {!verified && !!links.length && (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"Nothing is claimed about the providers' copies until you press Verify.\"\n }\n />\n </p>\n )}\n </CardContent>\n </Card>\n )\n}\n\n// PSPReadinessCard answers \"can someone buy this right now, and if not,\n// why?\" with the routing preview, so the console and purchase never disagree.\nexport function PSPReadinessCard({ price }: { price: Price }) {\n const { adminQueries } = useAdminServices()\n\n const {\n data: decision,\n isFetching: busy,\n refetch: reload,\n } = useQuery(adminQueries.pspRouting(price.id))\n\n const eligible = decision?.candidates.filter((c) => !c.skip) ?? []\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-start justify-between gap-2\">\n <div className=\"grid gap-1\">\n <CardTitle className=\"text-sm\">\n <AdminMessage id={\"Can this be bought?\"} />\n </CardTitle>\n <CardDescription>\n <AdminMessage\n id={\n \"Which provider would take the money if someone bought this right now, and why the others were passed over. This asks the same question a real purchase asks, without starting one.\"\n }\n />\n </CardDescription>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={busy}\n onClick={() => void reload()}\n >\n {busy ? \"Checking…\" : \"Re-check\"}\n </Button>\n </CardHeader>\n <CardContent className=\"flex flex-col gap-3\">\n {!decision ? (\n <p className=\"text-sm text-muted-foreground\">\n {busy ? \"Checking…\" : \"This check could not be run.\"}\n </p>\n ) : (\n <>\n <p className=\"text-sm\">\n {decision.psp ? (\n <>\n <AdminMessage id={\"Money for this price would go to\"} />{\" \"}\n <span className=\"font-medium\">{decision.psp}</span>\n {decision.rail && (\n <>\n <AdminMessage id={\" on \"} />\n {decision.rail}\n </>\n )}\n .\n </>\n ) : (\n <span className=\"text-held\">\n <AdminMessage\n id={\n \"No provider can take this payment, so nobody can buy this price yet.\"\n }\n />\n </span>\n )}\n </p>\n {!decision.candidates.length ? (\n <p className=\"text-sm text-muted-foreground\">\n <AdminMessage id={\"There were no providers to consider.\"} />\n </p>\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Provider\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Rail\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Outcome\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Why\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {decision.candidates.map((c) => (\n <TableRow key={c.psp}>\n <TableCell className=\"font-mono text-xs\">\n {c.psp}\n {c.psp === decision.psp && (\n <Badge\n variant=\"secondary\"\n className={`ml-2 ${OK_BADGE}`}\n >\n <AdminMessage id={\"selected\"} />\n </Badge>\n )}\n </TableCell>\n <TableCell className=\"text-xs\">\n {c.rail || \"—\"}\n </TableCell>\n <TableCell>\n <Badge\n variant=\"secondary\"\n className={c.skip ? WARN_BADGE : OK_BADGE}\n >\n {skipHeadline(c.skip)}\n </Badge>\n </TableCell>\n <TableCell className=\"text-xs text-muted-foreground\">\n {skipLabel(c.skip)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n {!!decision.candidates.length && eligible.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n '\"Not sent to provider\" is fixed by publishing the catalog. Everything else is fixed under Settings, Payment providers.'\n }\n />\n </p>\n )}\n </>\n )}\n </CardContent>\n </Card>\n )\n}\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\nimport { HugeiconsIcon } from \"@hugeicons/react\"\nimport { ArrowLeft01Icon } from \"@hugeicons/core-free-icons\"\nimport * as React from \"react\"\nimport { Link, useLocation, useNavigate } from \"#orck/admin/router\"\n\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\"\n\nimport { Fact } from \"#orck/admin/components/fact-card\"\nimport { Badge } from \"#orck/admin/components/ui/badge\"\nimport { useConfirm } from \"#orck/admin/lib/confirm\"\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n} from \"#orck/admin/components/ui/card\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"#orck/admin/components/ui/table\"\nimport { formatDate, shortId } from \"#orck/admin/lib/format\"\n\nimport {\n priceAmountLabel,\n priceIntervalLabel,\n} from \"#orck/admin/pages/catalog/price-format\"\nimport { PriceChangeWizard } from \"#orck/admin/pages/catalog/price-wizard\"\nimport {\n PSPReadinessCard,\n PSPLinksCard,\n} from \"#orck/admin/pages/catalog/psp-links\"\nimport { useCatalogWrites } from \"#orck/admin/lib/capabilities\"\n\n// A price ID can contain its own percent-encoded key bytes. Router params\n// decode an inner %2F again; decode the original URL segment exactly once.\nfunction priceIDFromPath(pathname: string): string {\n const segment = pathname.replace(/\\/+$/, \"\").split(\"/\").at(-1) ?? \"\"\n try {\n return decodeURIComponent(segment)\n } catch {\n return \"\"\n }\n}\n\n// PriceDetailPage (#777): the version chain (from the #774 pointer-movement\n// log, with dates) and any pending scheduled migration (progress + cancel)\n// for one price key — the wizard's Step 1 entry point (\"Change price\") also\n// lives here alongside the catalog list row.\nexport function PriceDetailPage() {\n const { text: adminText } = useAdminUI()\n\n const { adminQueries } = useAdminServices()\n\n const id = priceIDFromPath(useLocation().pathname)\n const writesAllowed = useCatalogWrites()\n const navigate = useNavigate()\n // verify (or#812) is opt-in: it makes GET price perform a live retrieve\n // against every attached provider, which is a network round trip per PSP.\n const [verify, setVerify] = React.useState(false)\n const {\n data: price,\n isPending: loading,\n isFetching,\n refetch: reload,\n } = useQuery(adminQueries.price(id, { verify, errorAction: \"Load price\" }))\n const { data: product } = useQuery(adminQueries.product(price?.product_id))\n const { data: history } = useQuery(adminQueries.priceHistory(price?.id))\n const { data: migrations } = useQuery(\n adminQueries.priceMigrations(product?.key, price?.key)\n )\n const latestBatch = migrations?.data?.[0]\n\n if (!id)\n return (\n <p role=\"alert\" className=\"text-sm text-muted-foreground\">\n Invalid price URL.\n </p>\n )\n\n // Only the FIRST load blanks the page; a verify refetch keeps the rendered\n // price in place so the button can show its own in-flight state.\n if (!price)\n return (\n <p className=\"text-sm text-muted-foreground\">\n {loading ? \"Loading…\" : \"Price not found.\"}\n </p>\n )\n\n const isPending = !!latestBatch && latestBatch.scheduled > 0\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex items-center gap-3\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => navigate(-1)}\n aria-label={adminText(\"Back\")}\n >\n <HugeiconsIcon icon={ArrowLeft01Icon} className=\"size-4\" />\n </Button>\n <div>\n <h2 className=\"flex items-center gap-2 text-sm\">\n {price.id}\n {price.archived && (\n <Badge variant=\"secondary\">\n <AdminMessage id={\"archived\"} />\n </Badge>\n )}\n </h2>\n <p className=\"text-xs text-muted-foreground\">\n {product?.display_name}\n </p>\n </div>\n <div className=\"ml-auto\">\n {writesAllowed && (\n <PriceChangeWizard\n price={price}\n productName={product?.display_name ?? \"…\"}\n productKey={product?.key}\n />\n )}\n </div>\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n <Fact label={adminText(\"Product\")}>\n <Link className=\"underline-offset-2 hover:underline\" to={`/catalog`}>\n {product?.display_name ?? shortId(price.product_id, 13)}\n </Link>\n </Fact>\n <Fact label={adminText(\"Amount\")}>{priceAmountLabel(price)}</Fact>\n <Fact label={adminText(\"Renews\")}>\n {price.currency.toUpperCase()} · {priceIntervalLabel(price)}\n </Fact>\n <Fact label={adminText(\"Created\")}>{formatDate(price.created_at)}</Fact>\n </div>\n\n <PSPLinksCard\n price={price}\n verifying={verify && isFetching}\n verified={verify}\n onVerify={() => (verify ? void reload() : setVerify(true))}\n />\n\n <PSPReadinessCard price={price} />\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-sm\">\n <AdminMessage id={\"Price history\"} />\n </CardTitle>\n </CardHeader>\n <CardContent>\n {!history?.data?.length ? (\n <p className=\"text-sm text-muted-foreground\">\n <AdminMessage id={\"No history yet.\"} />\n </p>\n ) : (\n <Table>\n <TableHeader>\n <TableRow className=\"hover:bg-transparent\">\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Amount\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Since\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"State\"} />\n </TableHead>\n <TableHead className=\"text-muted-foreground\">\n <AdminMessage id={\"Price\"} />\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {history.data.map((entry) => (\n <TableRow key={`${entry.price.id}-${entry.effective_at}`}>\n <TableCell>{priceAmountLabel(entry.price)}</TableCell>\n <TableCell>{formatDate(entry.effective_at)}</TableCell>\n <TableCell>\n {entry.archived ? (\n <Badge variant=\"secondary\">\n <AdminMessage id={\"retired\"} />\n </Badge>\n ) : (\n <Badge className=\"bg-settled-surface text-settled\">\n <AdminMessage id={\"offered\"} />\n </Badge>\n )}\n </TableCell>\n <TableCell>\n <Link\n className=\"text-xs underline-offset-2 hover:underline\"\n to={`/catalog/prices/${encodeURIComponent(entry.price.id)}`}\n title={entry.price.id}\n >\n {entry.price.id}\n </Link>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </CardContent>\n </Card>\n\n {latestBatch && (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-2\">\n <CardTitle className=\"text-sm\">\n {isPending\n ? \"Moving customers to the new price\"\n : \"Last move to a new price\"}\n </CardTitle>\n {isPending && (\n <CancelMigrationButton migrationId={latestBatch.id} />\n )}\n </CardHeader>\n <CardContent className=\"flex flex-col gap-2 text-sm\">\n <p>\n {latestBatch.applied}\n <AdminMessage id={\" of\"} />{\" \"}\n {latestBatch.applied +\n latestBatch.scheduled +\n latestBatch.canceled}{\" \"}\n <AdminMessage id={\"moved\"} />\n {latestBatch.scheduled > 0 &&\n ` · ${latestBatch.scheduled} still scheduled`}\n {\" · effective\"}\n {formatDate(latestBatch.effective_at)}\n </p>\n {latestBatch.skipped > 0 && (\n <p className=\"text-xs text-muted-foreground\">\n {latestBatch.skipped}\n <AdminMessage id={\" subscription\"} />\n {latestBatch.skipped === 1 ? \" was\" : \"s were\"}\n <AdminMessage\n id={\n \" left alone because another change was already scheduled for them.\"\n }\n />\n </p>\n )}\n </CardContent>\n </Card>\n )}\n </div>\n )\n}\n\nfunction CancelMigrationButton({ migrationId }: { migrationId: string }) {\n const { toast, toastApiError, adminMutations } = useAdminServices()\n\n const queryClient = useQueryClient()\n const cancelBatch = useMutation(\n adminMutations.cancelPriceMigration(queryClient)\n )\n const confirm = useConfirm()\n return (\n <Button\n variant=\"destructive\"\n size=\"sm\"\n disabled={cancelBatch.isPending}\n onClick={async () => {\n const canceling = await confirm({\n title: \"Cancel this migration?\",\n description:\n \"Subscribers not yet moved stay on their current price. Those already moved stay moved.\",\n confirmLabel: \"Cancel migration\",\n cancelLabel: \"Keep migrating\",\n tone: \"destructive\",\n })\n if (!canceling) return\n try {\n const result = await cancelBatch.mutateAsync(migrationId)\n toast.success(\n `Canceled ${result.canceled} pending move${result.canceled === 1 ? \"\" : \"s\"}. Already-migrated subscribers stay migrated.`\n )\n const release = result.rail_release_required.length\n if (release > 0)\n toast.warning(\n `${release} Stripe subscription${release === 1 ? \" still carries\" : \"s still carry\"} the move as a Stripe subscription schedule: release it in Stripe, or the price changes at period end.`\n )\n } catch (err) {\n toastApiError(err, \"Cancel migration\")\n }\n }}\n >\n {cancelBatch.isPending ? \"Canceling…\" : \"Cancel migration\"}\n </Button>\n )\n}\n"],"mappings":"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"}
@@ -70,7 +70,7 @@ function P({ price: o, productName: A, productKey: P, draft: F, onDone: re }) {
70
70
  trial_unit_amount: o.trial_unit_amount ?? void 0,
71
71
  trial_duration_hours: o.trial_duration_hours ?? void 0,
72
72
  key: o.key,
73
- psps: Object.keys(o.psps ?? {}),
73
+ psps: Object.fromEntries(Object.keys(o.psps ?? {}).map((e) => [e, {}])),
74
74
  expected_revision: o.revision
75
75
  },
76
76
  migration: e.mode === "migrate" ? {
@@ -354,4 +354,4 @@ function P({ price: o, productName: A, productKey: P, draft: F, onDone: re }) {
354
354
  //#endregion
355
355
  export { P as t };
356
356
 
357
- //# sourceMappingURL=admin-price-wizard-ferZSTfO.js.map
357
+ //# sourceMappingURL=admin-price-wizard-m7Qjhq6W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-price-wizard-m7Qjhq6W.js","names":[],"sources":["../src/admin/pages/catalog/price-wizard-logic.ts","../src/admin/pages/catalog/price-wizard.tsx"],"sourcesContent":["// Pure logic for the price-change wizard, kept apart from React for unit tests.\nimport { formatNativeAmount } from \"#orck/admin/lib/format\"\n\nexport type PriceDirection = \"increase\" | \"decrease\" | \"unchanged\"\n\nexport function detectDirection(\n newAmount: string,\n currentAmount: string\n): PriceDirection {\n const next = BigInt(newAmount)\n const current = BigInt(currentAmount)\n if (next > current) return \"increase\"\n if (next < current) return \"decrease\"\n return \"unchanged\"\n}\n\nexport type MigrationMode = \"grandfather\" | \"migrate\"\n\n// DEFAULT_NOTICE_WINDOW_DAYS is the fallback until the merchant's\n// reprice_notice_window_days loads; it mirrors\n// subscriptions.DefaultPriceIncreaseNoticeDays so the UI never under-gates.\n// The server enforces the window too (price_increase_notice_too_short).\nexport const DEFAULT_NOTICE_WINDOW_DAYS = 30\n\n// defaultMigrationMode is the direction-aware Step 2 default: an increase\n// grandfathers (no new action), a decrease migrates now (never grandfathered).\nexport function defaultMigrationMode(direction: PriceDirection): MigrationMode {\n return direction === \"decrease\" ? \"migrate\" : \"grandfather\"\n}\n\n// minEffectiveDate is the earliest allowed migration date, or null when there\n// is no minimum (a decrease needs no notice).\nexport function minEffectiveDate(\n direction: PriceDirection,\n now: Date,\n noticeWindowDays: number\n): Date | null {\n if (direction !== \"increase\") return null\n const min = new Date(now)\n min.setUTCDate(min.getUTCDate() + noticeWindowDays)\n return min\n}\n\n// defaultEffectiveDate is Step 2's pre-filled migration date: the notice\n// window's floor for an increase, \"now\" for a decrease.\nexport function defaultEffectiveDate(\n direction: PriceDirection,\n now: Date,\n noticeWindowDays: number\n): Date {\n return minEffectiveDate(direction, now, noticeWindowDays) ?? now\n}\n\n// isEffectiveDateValid enforces the console-side notice-window gate: an\n// increase+migrate plan may not pick a date inside the notice window.\nexport function isEffectiveDateValid(\n direction: PriceDirection,\n effectiveAt: Date,\n now: Date,\n noticeWindowDays: number\n): boolean {\n const min = minEffectiveDate(direction, now, noticeWindowDays)\n return !min || effectiveAt.getTime() >= min.getTime()\n}\n\nexport interface MigrationPlan {\n mode: MigrationMode\n // ISO instant; only meaningful when mode === \"migrate\".\n effectiveAt: string\n}\n\nconst dateLabel = (iso: string) =>\n new Date(iso).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n })\n\n// buildReviewText renders Step 3's plan in words, e.g. \"New subscribers pay $12\n// immediately. 1,204 existing subscribers keep $10 until Sep 1, then move to\n// $12 at their next renewal. Notices go out on confirm.\"\nexport function buildReviewText(params: {\n newAmount: string\n currentAmount: string\n currency: string\n affectedCount: number\n plan: MigrationPlan\n now: Date\n}): string {\n const { newAmount, currentAmount, currency, affectedCount, plan, now } =\n params\n const newLabel = formatNativeAmount(newAmount, currency)\n const oldLabel = formatNativeAmount(currentAmount, currency)\n const lead = `New subscribers pay ${newLabel} immediately.`\n\n if (affectedCount === 0) {\n return `${lead} No existing subscribers are on a prior version of this price.`\n }\n const subj = `${affectedCount.toLocaleString()} existing subscriber${affectedCount === 1 ? \"\" : \"s\"}`\n\n if (plan.mode === \"grandfather\") {\n return `${lead} ${subj} keep ${oldLabel} forever (grandfathered).`\n }\n\n // The engine notifies each affected subscriber at schedule time, however\n // far out effective_at is: always say so.\n const effective = new Date(plan.effectiveAt)\n const immediate = effective.getTime() <= now.getTime()\n if (immediate) {\n return `${lead} ${subj} move to ${newLabel} at their next renewal. Notices go out on confirm.`\n }\n return `${lead} ${subj} keep ${oldLabel} until ${dateLabel(plan.effectiveAt)}, then move to ${newLabel} at their next renewal. Notices go out on confirm.`\n}\n","import { AdminMessage, useAdminUI } from \"#orck/admin/ui-context\"\nimport { useAdminServices } from \"#orck/admin/context\"\nimport * as React from \"react\"\n\nimport { useForm } from \"@tanstack/react-form\"\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\"\n\nimport { FormFieldErrors } from \"#orck/admin/components/form-field-errors\"\nimport { Badge } from \"#orck/admin/components/ui/badge\"\nimport { Button } from \"#orck/admin/components/ui/button\"\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"#orck/admin/components/ui/dialog\"\nimport { Input } from \"#orck/admin/components/ui/input\"\nimport { Label } from \"#orck/admin/components/ui/label\"\nimport type { PriceChangeDraft } from \"#orck/admin/lib/api/copilot\"\nimport { DIALOG_FORM } from \"#orck/admin/lib/dialog-width\"\nimport type { Price } from \"#orck/client/generated/wire\"\nimport {\n formatNativeAmount,\n nativeAmountFromInput,\n nativeAmountToInput,\n} from \"#orck/admin/lib/format\"\n\nimport { priceIntervalLabel } from \"#orck/admin/pages/catalog/price-format\"\nimport {\n buildReviewText,\n defaultEffectiveDate,\n defaultMigrationMode,\n DEFAULT_NOTICE_WINDOW_DAYS,\n detectDirection,\n isEffectiveDateValid,\n minEffectiveDate,\n type MigrationMode,\n} from \"#orck/admin/pages/catalog/price-wizard-logic\"\n\nconst toDateInputValue = (d: Date) => d.toISOString().slice(0, 10)\n\nconst priceChangeFormValues = (price: Price, draft?: PriceChangeDraft) => ({\n amountInput: nativeAmountToInput(\n draft?.new_amount ?? price.unit_amount,\n price.currency\n ),\n mode: (draft?.migration ? \"migrate\" : \"grandfather\") as MigrationMode,\n effectiveAt: draft?.migration?.effective_at\n ? toDateInputValue(new Date(draft.migration.effective_at))\n : \"\",\n})\n\n// PriceChangeWizard is the #777 console price-change wizard — the ONLY\n// amount-edit affordance on a price. Three steps: new amount -> migration\n// plan (direction-aware defaults, live affected-count preview, notice-window\n// gate on increase+migrate) -> review in words, then confirm applies the\n// catalog price update followed (if migrating) by the price migration.\n//\n// draft (#779): when the catalog copilot proposed this change, the dialog\n// opens PRE-FILLED straight at Step 3 (review) — the affected count already\n// rode in the draft, so no extra preview call is needed. Confirm behaves\n// EXACTLY as a hand-typed change (the same create-price and price-migration\n// calls).\nexport function PriceChangeWizard({\n price,\n productName,\n productKey,\n draft,\n onDone,\n}: {\n price: Price\n productName: string\n productKey: string | undefined\n draft?: PriceChangeDraft\n onDone?: () => void\n}) {\n const { toast, toastApiError, adminMutations, adminQueries } =\n useAdminServices()\n\n const { id: adminId } = useAdminUI()\n\n const queryClient = useQueryClient()\n const [open, setOpen] = React.useState(() => !!draft)\n const [step, setStep] = React.useState<1 | 2 | 3>(() => (draft ? 3 : 1))\n const previewPriceChange = useMutation(adminMutations.previewPriceChange())\n const changePrice = useMutation(adminMutations.changePrice(queryClient))\n const affectedCount =\n previewPriceChange.data?.matched ?? draft?.affected_count ?? null\n const { data: configuration } = useQuery({\n ...adminQueries.merchantConfiguration(),\n enabled: open,\n })\n const noticeWindowDays =\n configuration?.settings.reprice_notice_window_days ??\n DEFAULT_NOTICE_WINDOW_DAYS\n\n const [now] = React.useState(() => new Date())\n const form = useForm({\n defaultValues: priceChangeFormValues(price, draft),\n onSubmit: async ({ value }) => {\n if (!productKey || price.customer_amount) return\n const newAmount = nativeAmountFromInput(value.amountInput, price.currency)\n if (newAmount === null || BigInt(newAmount) <= 0n) return\n\n try {\n const created = await changePrice.mutateAsync({\n price: {\n product_id: price.product_id,\n unit_amount: newAmount,\n currency: price.currency,\n access_duration_hours: price.access_duration_hours ?? undefined,\n billing_interval_hours: price.billing_interval_hours ?? undefined,\n trial_unit_amount: price.trial_unit_amount ?? undefined,\n trial_duration_hours: price.trial_duration_hours ?? undefined,\n key: price.key,\n psps: Object.fromEntries(\n Object.keys(price.psps ?? {}).map((psp) => [psp, {}])\n ),\n expected_revision: price.revision,\n },\n migration:\n value.mode === \"migrate\"\n ? {\n productKey,\n priceKey: price.key,\n effectiveAt: new Date(value.effectiveAt).toISOString(),\n }\n : undefined,\n })\n if (created.pending_manual_actions?.length) {\n toast.warning(\n `Price updated. One step is left for you: ${created.pending_manual_actions.map((action) => `${action.psp}: ${action.hint}`).join(\". \")}`\n )\n } else {\n toast.success(\n value.mode === \"migrate\"\n ? \"Price updated and migration scheduled\"\n : \"Price updated\"\n )\n }\n handleOpenChange(false)\n onDone?.()\n } catch (err) {\n toastApiError(err, \"Change price\")\n }\n },\n })\n\n const reset = () => {\n setStep(1)\n form.reset(priceChangeFormValues(price))\n previewPriceChange.reset()\n }\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n reset()\n }\n setOpen(next)\n }\n\n const enterStep2 = () => {\n if (!productKey || price.customer_amount) return\n const amount = nativeAmountFromInput(\n form.state.values.amountInput,\n price.currency\n )\n const direction = detectDirection(\n amount ?? price.unit_amount,\n price.unit_amount\n )\n form.setFieldValue(\"mode\", defaultMigrationMode(direction))\n form.setFieldValue(\n \"effectiveAt\",\n toDateInputValue(defaultEffectiveDate(direction, now, noticeWindowDays))\n )\n setStep(2)\n previewPriceChange.reset()\n previewPriceChange.mutate(\n { productKey, priceKey: price.key },\n { onError: (err) => toastApiError(err, \"Preview affected subscribers\") }\n )\n }\n\n // Archived (prior-version) rows are history, not the editable current\n // price. Each product/key pair has exactly one live edit target.\n // Wait for the product before allowing previews or scheduling a migration.\n if (price.archived || !productKey || price.customer_amount) {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n title={\n price.customer_amount\n ? \"This price lets customers choose an amount. Change its limits through a catalog application or the client.\"\n : price.archived\n ? \"This is an archived version. Change the current price for this key instead.\"\n : \"Load the product before changing its price.\"\n }\n >\n <AdminMessage id={\"Change price\"} />\n </Button>\n )\n }\n\n return (\n <Dialog open={open} onOpenChange={handleOpenChange}>\n {!draft && (\n <DialogTrigger\n render={\n <Button variant=\"outline\" size=\"sm\">\n <AdminMessage id={\"Change price\"} />\n </Button>\n }\n />\n )}\n <DialogContent className={DIALOG_FORM}>\n <form\n className=\"grid gap-4\"\n onSubmit={(event) => {\n event.preventDefault()\n event.stopPropagation()\n if (step !== 3) return\n void form.handleSubmit()\n }}\n >\n <form.Subscribe\n selector={(state) => [state.values, state.isSubmitting] as const}\n >\n {([values, isSubmitting]) => {\n const newAmount =\n nativeAmountFromInput(values.amountInput, price.currency) ??\n price.unit_amount\n const direction = detectDirection(newAmount, price.unit_amount)\n const minDate = minEffectiveDate(direction, now, noticeWindowDays)\n const dateValid =\n values.mode !== \"migrate\" ||\n (!!values.effectiveAt &&\n isEffectiveDateValid(\n direction,\n new Date(values.effectiveAt),\n now,\n noticeWindowDays\n ))\n\n return (\n <>\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2\">\n <AdminMessage id={\"Change price for \"} />\n {productName}\n {draft && (\n <Badge variant=\"secondary\">\n <AdminMessage id={\"drafted by copilot\"} />\n </Badge>\n )}\n </DialogTitle>\n <DialogDescription>\n <AdminMessage id={\"Step \"} />\n {step}\n <AdminMessage id={\" of 3:\"} />{\" \"}\n {step === 1\n ? \"new amount\"\n : step === 2\n ? \"migration plan\"\n : \"review\"}\n </DialogDescription>\n </DialogHeader>\n\n {step === 1 && (\n <div className=\"grid gap-3\">\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <p className=\"text-xs text-muted-foreground uppercase\">\n <AdminMessage id={\"Current amount\"} />\n </p>\n <p className=\"font-medium\">\n {formatNativeAmount(\n price.unit_amount,\n price.currency\n )}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground uppercase\">\n <AdminMessage id={\"Currency · interval\"} />\n </p>\n <p className=\"font-medium\">\n {price.currency.toUpperCase()} ·{\" \"}\n {priceIntervalLabel(price)}\n </p>\n </div>\n </div>\n <form.Field\n name=\"amountInput\"\n validators={{\n onBlur: ({ value }) => {\n const amount = nativeAmountFromInput(\n value,\n price.currency\n )\n if (amount === null || BigInt(amount) <= 0n) {\n return \"Enter an amount greater than zero.\"\n }\n return undefined\n },\n }}\n >\n {(field) => (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={adminId(\"wiz-amount\")}>\n <AdminMessage id={\"New amount (major units)\"} />\n </Label>\n <Input\n id={adminId(\"wiz-amount\")}\n type=\"number\"\n step=\"any\"\n min=\"0\"\n value={field.state.value}\n onBlur={field.handleBlur}\n onChange={(event) =>\n field.handleChange(event.target.value)\n }\n autoFocus\n />\n <FormFieldErrors errors={field.state.meta.errors} />\n </div>\n )}\n </form.Field>\n {direction !== \"unchanged\" && (\n <p className=\"text-sm text-muted-foreground\">\n <AdminMessage id={\"This is a price\"} />{\" \"}\n <span className=\"font-medium text-foreground\">\n {direction === \"increase\" ? \"increase\" : \"decrease\"}\n </span>\n <AdminMessage\n id={\n \". Currency and interval stay locked to the current price.\"\n }\n />\n </p>\n )}\n </div>\n )}\n\n {step === 2 && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm\">\n {affectedCount === null && previewPriceChange.isPending\n ? \"Checking affected subscribers…\"\n : `${(affectedCount ?? 0).toLocaleString()} active subscription${affectedCount === 1 ? \"\" : \"s\"} on prior versions of ${price.key}.`}\n </p>\n <form.Field name=\"mode\">\n {(field) => (\n <div className=\"grid gap-2\">\n <label className=\"flex items-start gap-2 rounded-md border p-3 text-sm\">\n <input\n type=\"radio\"\n name={field.name}\n className=\"mt-1\"\n checked={field.state.value === \"grandfather\"}\n onChange={() =>\n field.handleChange(\"grandfather\")\n }\n />\n <span>\n <span className=\"font-medium\">\n <AdminMessage id={\"Grandfather.\"} />\n </span>{\" \"}\n <AdminMessage\n id={\n \"Existing subscribers keep the current price.\"\n }\n />\n {direction === \"increase\" && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n <AdminMessage id={\"default\"} />\n </Badge>\n )}\n </span>\n </label>\n <label className=\"flex items-start gap-2 rounded-md border p-3 text-sm\">\n <input\n type=\"radio\"\n name={field.name}\n className=\"mt-1\"\n checked={field.state.value === \"migrate\"}\n onChange={() => field.handleChange(\"migrate\")}\n />\n <span>\n <span className=\"font-medium\">\n <AdminMessage id={\"Migrate.\"} />\n </span>{\" \"}\n <AdminMessage\n id={\n \"Move everyone to the new price at their first renewal on or after a date you choose.\"\n }\n />\n {direction === \"decrease\" && (\n <Badge variant=\"secondary\" className=\"ml-2\">\n <AdminMessage id={\"default\"} />\n </Badge>\n )}\n </span>\n </label>\n </div>\n )}\n </form.Field>\n {values.mode === \"migrate\" && (\n <form.Field name=\"effectiveAt\">\n {(field) => (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={adminId(\"wiz-effective\")}>\n <AdminMessage id={\"Effective date\"} />\n </Label>\n <Input\n id={adminId(\"wiz-effective\")}\n type=\"date\"\n value={field.state.value}\n min={\n minDate\n ? toDateInputValue(minDate)\n : undefined\n }\n onBlur={field.handleBlur}\n onChange={(event) =>\n field.handleChange(event.target.value)\n }\n />\n {direction === \"increase\" ? (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage id={\"Must be at least \"} />\n {noticeWindowDays}\n <AdminMessage\n id={\n \" days away. Card networks require that much notice before a price goes up. You can change the number in Settings.\"\n }\n />\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"A price going down needs no notice, so today is fine.\"\n }\n />\n </p>\n )}\n {!dateValid && (\n <p\n className=\"text-xs text-destructive\"\n role=\"alert\"\n >\n <AdminMessage id={\"Pick a later date.\"} />\n </p>\n )}\n <p className=\"text-xs text-muted-foreground\">\n <AdminMessage\n id={\n \"A reprice_scheduled notice is queued for every affected subscriber as soon as you confirm.\"\n }\n />\n </p>\n </div>\n )}\n </form.Field>\n )}\n </div>\n )}\n\n {step === 3 && (\n <div className=\"grid gap-3\">\n <p className=\"rounded-md border bg-muted/40 p-3 text-sm\">\n {buildReviewText({\n newAmount,\n currentAmount: price.unit_amount,\n currency: price.currency,\n affectedCount: affectedCount ?? 0,\n plan: {\n mode: values.mode,\n effectiveAt:\n values.mode === \"migrate\"\n ? new Date(values.effectiveAt).toISOString()\n : \"\",\n },\n now,\n })}\n </p>\n </div>\n )}\n\n <DialogFooter>\n {step > 1 && (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isSubmitting}\n onClick={() => setStep((value) => (value - 1) as 1 | 2)}\n >\n <AdminMessage id={\"Back\"} />\n </Button>\n )}\n {step === 1 && (\n <Button\n type=\"button\"\n disabled={\n !values.amountInput ||\n direction === \"unchanged\" ||\n BigInt(newAmount) <= 0n\n }\n onClick={enterStep2}\n >\n <AdminMessage id={\"Next\"} />\n </Button>\n )}\n {step === 2 && (\n <Button\n type=\"button\"\n disabled={!dateValid || previewPriceChange.isPending}\n onClick={() => setStep(3)}\n >\n <AdminMessage id={\"Next\"} />\n </Button>\n )}\n {step === 3 && (\n <Button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? \"Working…\" : \"Confirm\"}\n </Button>\n )}\n </DialogFooter>\n </>\n )\n }}\n </form.Subscribe>\n </form>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
@@ -537,7 +537,7 @@ function ge() {
537
537
  }),
538
538
  o && /* @__PURE__ */ W(R, {
539
539
  value: "customer-controls",
540
- children: /* @__PURE__ */ W(Ae, {})
540
+ children: /* @__PURE__ */ W(je, {})
541
541
  }),
542
542
  s.map((e) => /* @__PURE__ */ W(R, {
543
543
  value: e.value,
@@ -571,6 +571,8 @@ function ve({ configuration: t, edits: n }) {
571
571
  displayName: t.display_name,
572
572
  fromEmail: o?.from_email ?? "",
573
573
  supportURL: o?.support_url ?? "",
574
+ subscribeURL: o?.subscribe_url ?? "",
575
+ billingURL: o?.billing_url ?? "",
574
576
  logoURL: o?.logo_url ?? ""
575
577
  }), l = q(t), u = K({
576
578
  defaultValues: c(),
@@ -579,6 +581,8 @@ function ve({ configuration: t, edits: n }) {
579
581
  settings: { profile: {
580
582
  from_email: e.fromEmail || void 0,
581
583
  support_url: e.supportURL || void 0,
584
+ subscribe_url: e.subscribeURL || void 0,
585
+ billing_url: e.billingURL || void 0,
582
586
  logo_url: e.logoURL || void 0
583
587
  } }
584
588
  }, "Profile saved", "Save profile")
@@ -666,6 +670,36 @@ function ve({ configuration: t, edits: n }) {
666
670
  })
667
671
  })
668
672
  }),
673
+ /* @__PURE__ */ W(u.Field, {
674
+ name: "subscribeURL",
675
+ children: (e) => /* @__PURE__ */ W(Z, {
676
+ label: i("Subscribe URL"),
677
+ id: "s-subscribe",
678
+ children: /* @__PURE__ */ W(s, {
679
+ id: r("s-subscribe"),
680
+ type: "url",
681
+ value: e.state.value,
682
+ onBlur: e.handleBlur,
683
+ onChange: (t) => e.handleChange(t.target.value),
684
+ placeholder: i("https://example.com/subscribe")
685
+ })
686
+ })
687
+ }),
688
+ /* @__PURE__ */ W(u.Field, {
689
+ name: "billingURL",
690
+ children: (e) => /* @__PURE__ */ W(Z, {
691
+ label: i("Billing URL"),
692
+ id: "s-billing",
693
+ children: /* @__PURE__ */ W(s, {
694
+ id: r("s-billing"),
695
+ type: "url",
696
+ value: e.state.value,
697
+ onBlur: e.handleBlur,
698
+ onChange: (t) => e.handleChange(t.target.value),
699
+ placeholder: i("https://example.com/account")
700
+ })
701
+ })
702
+ }),
669
703
  /* @__PURE__ */ W(u.Field, {
670
704
  name: "logoURL",
671
705
  children: (e) => /* @__PURE__ */ W(Z, {
@@ -697,6 +731,14 @@ function ve({ configuration: t, edits: n }) {
697
731
  label: i("Support URL"),
698
732
  value: o?.support_url
699
733
  }),
734
+ /* @__PURE__ */ W(X, {
735
+ label: i("Subscribe URL"),
736
+ value: o?.subscribe_url
737
+ }),
738
+ /* @__PURE__ */ W(X, {
739
+ label: i("Billing URL"),
740
+ value: o?.billing_url
741
+ }),
700
742
  /* @__PURE__ */ W(X, {
701
743
  label: i("Logo URL"),
702
744
  value: o?.logo_url
@@ -794,7 +836,7 @@ function be() {
794
836
  className: "text-sm text-muted-foreground",
795
837
  children: /* @__PURE__ */ W(a, { id: "The accounts this merchant takes payments through, one or more per rail." })
796
838
  })]
797
- }), o && /* @__PURE__ */ W(ke, { railDefinitions: s }, e() ?? "")]
839
+ }), o && /* @__PURE__ */ W(Ae, { railDefinitions: s }, e() ?? "")]
798
840
  }), n.data?.data?.length ? /* @__PURE__ */ G(ne, {
799
841
  className: "min-w-[44rem]",
800
842
  children: [/* @__PURE__ */ W(te, { children: /* @__PURE__ */ G(E, {
@@ -1049,27 +1091,32 @@ function we(e) {
1049
1091
  }
1050
1092
  }
1051
1093
  var Te = {
1094
+ live: "Live: real money",
1095
+ test: "Test: the provider's own system, no money moves",
1096
+ sandbox: "Sandbox: the provider's separate sandbox"
1097
+ }, Ee = {
1052
1098
  nmi: "NMI",
1053
1099
  ccbill: "CCBill",
1054
1100
  stripe: "Stripe",
1055
1101
  solana: "Solana"
1056
1102
  };
1057
- function Ee(e, t) {
1058
- let n = Te[e] ?? e;
1103
+ function De(e, t) {
1104
+ let n = Ee[e] ?? e;
1059
1105
  return !t || t.toLowerCase() === n.toLowerCase() ? n : `${t} (${n})`;
1060
1106
  }
1061
- var De = {
1107
+ var Oe = {
1062
1108
  api: "API",
1063
1109
  id: "ID",
1064
1110
  url: "URL"
1065
1111
  };
1066
- function Oe(e) {
1067
- return e.split(/[_-]+/).filter(Boolean).map((e, t) => De[e.toLowerCase()] || (t > 0 ? e.toLowerCase() : e.charAt(0).toUpperCase() + e.slice(1).toLowerCase())).join(" ");
1112
+ function ke(e) {
1113
+ return e.split(/[_-]+/).filter(Boolean).map((e, t) => Oe[e.toLowerCase()] || (t > 0 ? e.toLowerCase() : e.charAt(0).toUpperCase() + e.slice(1).toLowerCase())).join(" ");
1068
1114
  }
1069
- function ke({ railDefinitions: t }) {
1115
+ function Ae({ railDefinitions: t }) {
1070
1116
  let { toast: n, toastApiError: r, adminMutations: c, selectedMerchant: l } = i(), { text: u, id: d } = e(), [f, p] = B.useState(!1), [m] = B.useState(() => l() ?? ""), h = U(), g = V(c.createPSP(h)), _ = K({
1071
1117
  defaultValues: {
1072
1118
  rail: "",
1119
+ environment: "",
1073
1120
  key: "",
1074
1121
  accountID: "",
1075
1122
  credentials: {}
@@ -1081,6 +1128,7 @@ function ke({ railDefinitions: t }) {
1081
1128
  await g.mutateAsync({
1082
1129
  key: e.key.trim().toLowerCase(),
1083
1130
  rail: e.rail,
1131
+ environment: e.environment,
1084
1132
  account_id: e.accountID.trim(),
1085
1133
  ...Object.keys(t).length ? { credentials: t } : {}
1086
1134
  }), _.reset(), n.success("PSP added"), p(!1);
@@ -1118,11 +1166,11 @@ function ke({ railDefinitions: t }) {
1118
1166
  children: [/* @__PURE__ */ G(C, {
1119
1167
  items: t.map((e) => ({
1120
1168
  value: e.rail,
1121
- label: Ee(e.rail, e.display_name)
1169
+ label: De(e.rail, e.display_name)
1122
1170
  })),
1123
1171
  value: e.state.value || null,
1124
1172
  onValueChange: (t) => {
1125
- e.handleChange(t ?? ""), _.setFieldValue("credentials", {});
1173
+ e.handleChange(t ?? ""), _.setFieldValue("credentials", {}), _.setFieldValue("environment", "");
1126
1174
  },
1127
1175
  children: [/* @__PURE__ */ W(b, {
1128
1176
  id: d("pv-rail"),
@@ -1131,11 +1179,44 @@ function ke({ railDefinitions: t }) {
1131
1179
  children: /* @__PURE__ */ W(y, { placeholder: u("Pick a rail…") })
1132
1180
  }), /* @__PURE__ */ W(x, { children: t.map((e) => /* @__PURE__ */ W(S, {
1133
1181
  value: e.rail,
1134
- children: Ee(e.rail, e.display_name)
1182
+ children: De(e.rail, e.display_name)
1135
1183
  }, e.rail)) })]
1136
1184
  }), /* @__PURE__ */ W(v, { errors: e.state.meta.errors })]
1137
1185
  })
1138
1186
  }),
1187
+ /* @__PURE__ */ W(_.Subscribe, {
1188
+ selector: (e) => e.values.rail,
1189
+ children: (e) => {
1190
+ let n = t.find((t) => t.rail === e)?.environments ?? [];
1191
+ return /* @__PURE__ */ W(_.Field, {
1192
+ name: "environment",
1193
+ validators: { onChange: ({ value: e }) => e ? void 0 : "Choose whether it moves real money" },
1194
+ children: (t) => /* @__PURE__ */ G($, {
1195
+ label: u("Environment"),
1196
+ id: "pv-environment",
1197
+ hint: "Whether this PSP moves real money. It is checked with the provider before the PSP is used.",
1198
+ children: [/* @__PURE__ */ G(C, {
1199
+ items: n.map((e) => ({
1200
+ value: e,
1201
+ label: u(Te[e] ?? e)
1202
+ })),
1203
+ value: t.state.value || null,
1204
+ onValueChange: (e) => t.handleChange(e ?? ""),
1205
+ children: [/* @__PURE__ */ W(b, {
1206
+ id: d("pv-environment"),
1207
+ className: "w-full",
1208
+ disabled: !e,
1209
+ "aria-invalid": t.state.meta.errors.length > 0,
1210
+ children: /* @__PURE__ */ W(y, { placeholder: u("Pick an environment…") })
1211
+ }), /* @__PURE__ */ W(x, { children: n.map((e) => /* @__PURE__ */ W(S, {
1212
+ value: e,
1213
+ children: u(Te[e] ?? e)
1214
+ }, e)) })]
1215
+ }), /* @__PURE__ */ W(v, { errors: t.state.meta.errors })]
1216
+ })
1217
+ });
1218
+ }
1219
+ }),
1139
1220
  /* @__PURE__ */ W(_.Field, {
1140
1221
  name: "key",
1141
1222
  validators: { onChange: ({ value: e }) => /^[a-z0-9][a-z0-9_-]{0,62}$/.test(e.trim().toLowerCase()) ? void 0 : "Lowercase letters, digits, - or _" },
@@ -1178,7 +1259,7 @@ function ke({ railDefinitions: t }) {
1178
1259
  return /* @__PURE__ */ W(_.Field, {
1179
1260
  name: "credentials",
1180
1261
  children: (e) => /* @__PURE__ */ W(oe, { children: n?.credential_keys.map((t) => /* @__PURE__ */ W($, {
1181
- label: Oe(t),
1262
+ label: ke(t),
1182
1263
  id: `pv-credential-${t}`,
1183
1264
  children: /* @__PURE__ */ W(s, {
1184
1265
  id: d(`pv-credential-${t}`),
@@ -1198,27 +1279,28 @@ function ke({ railDefinitions: t }) {
1198
1279
  }), /* @__PURE__ */ W(k, { children: /* @__PURE__ */ W(_.Subscribe, {
1199
1280
  selector: (e) => [
1200
1281
  e.values.rail,
1282
+ e.values.environment,
1201
1283
  e.values.key,
1202
1284
  e.values.accountID,
1203
1285
  e.canSubmit,
1204
1286
  e.isSubmitting
1205
1287
  ],
1206
- children: ([e, t, n, r, i]) => /* @__PURE__ */ G(oe, { children: [/* @__PURE__ */ W(o, {
1288
+ children: ([e, t, n, r, i, s]) => /* @__PURE__ */ G(oe, { children: [/* @__PURE__ */ W(o, {
1207
1289
  type: "button",
1208
1290
  variant: "outline",
1209
1291
  onClick: () => w(!1),
1210
1292
  children: /* @__PURE__ */ W(a, { id: "Cancel" })
1211
1293
  }), /* @__PURE__ */ W(o, {
1212
1294
  type: "submit",
1213
- disabled: !e || !t.trim() || !n.trim() || !r || i,
1214
- children: i ? "Saving…" : "Add PSP"
1295
+ disabled: !e || !t || !n.trim() || !r.trim() || !i || s,
1296
+ children: s ? "Saving…" : "Add PSP"
1215
1297
  })] })
1216
1298
  }) })]
1217
1299
  })]
1218
1300
  })]
1219
1301
  });
1220
1302
  }
1221
- function Ae() {
1303
+ function je() {
1222
1304
  let { toast: t, toastApiError: n, adminMutations: r } = i(), { text: l, id: u } = e(), [d, f] = B.useState(), m = V(r.lookupCustomerControls()), _ = V(r.setCreditLimit()), y = c(), b = K({
1223
1305
  defaultValues: { newLimit: "" },
1224
1306
  onSubmit: async ({ value: e }) => {
@@ -1431,4 +1513,4 @@ function $({ label: t, id: n, hint: r, children: i }) {
1431
1513
  //#endregion
1432
1514
  export { ge as SettingsPage };
1433
1515
 
1434
- //# sourceMappingURL=admin-settings-C6eWFkDn.js.map
1516
+ //# sourceMappingURL=admin-settings-BIUgvclv.js.map