@voyantjs/checkout-ui 0.19.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.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @voyantjs/checkout-ui
2
+
3
+ Universal payment UI components built on the existing Voyant payments
4
+ stack:
5
+
6
+ - `@voyantjs/finance` — `payment_sessions`, `payments`, `payment_instruments`
7
+ - `@voyantjs/checkout` — collection plans + `paymentStarters` registration
8
+ - `@voyantjs/plugin-netopia` (and future plugins) — real processor adapters
9
+ - `@voyantjs/notifications` — payment-link emails
10
+
11
+ This package adds **pure UI** on top of that stack — no parallel state,
12
+ no parallel contracts. Verticals wrap `<PaymentStep>` and translate
13
+ `PaymentChoice` events into checkout calls.
14
+
15
+ ## Components
16
+
17
+ ### `<PaymentStep>`
18
+
19
+ Capability-driven payment picker. The parent passes a
20
+ `PaymentStepCapabilities` boolean record built from what's actually
21
+ wired (a configured `paymentStarter`, `bankTransferDetails` registered,
22
+ saved methods on file).
23
+
24
+ | Section | Visible when |
25
+ |---|---|
26
+ | Saved cards on file | `capabilities.chargeSavedCard` |
27
+ | Send payment link | `capabilities.sendLink` |
28
+ | Charge a new card now | `capabilities.newCard` |
29
+ | Bank transfer (manual) | `capabilities.bankTransfer` |
30
+ | Vertical-specific extras | always (e.g. "Issue on credit" for flights) |
31
+ | Hold seats — pay later | always (unless `hideHoldOption`) |
32
+
33
+ Saved methods are typed against `PublicBookingPaymentOptionsRecord["accounts"][number]`
34
+ from `@voyantjs/finance/public-validation` — single source of truth.
35
+
36
+ ### `<PaymentLinkLandingPage>`
37
+
38
+ The customer's view of a payment link. Takes a `PublicPaymentSession`
39
+ from `@voyantjs/finance/public-validation` and renders the order
40
+ summary, Pay-by-card button (redirects to `session.redirectUrl`), and
41
+ optional Bank-transfer block (template-supplied IBAN).
42
+
43
+ Terminal states (paid / failed / expired / cancelled / processing)
44
+ short-circuit the body to a status panel.
45
+
46
+ ## Architecture
47
+
48
+ See [`docs/architecture/payments-architecture.md`](../../docs/architecture/payments-architecture.md).
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Operator-side "Collect payment" dialog. Wraps `<PaymentStep>` and
3
+ * `useCollectPayment` so any vertical's booking detail page can drop it in:
4
+ *
5
+ * ```tsx
6
+ * <CollectPaymentDialog
7
+ * open={open}
8
+ * onOpenChange={setOpen}
9
+ * bookingId={booking.id}
10
+ * defaultCurrency={booking.sellCurrency}
11
+ * defaultAmountCents={booking.sellAmountCents}
12
+ * />
13
+ * ```
14
+ *
15
+ * Capabilities are pinned to `sendLink + bankTransfer` because the
16
+ * operator initiates the collection rather than the customer self-charging
17
+ * a saved card. Verticals that need additional flows (e.g. a "Charge to
18
+ * folio" extra) should compose `<PaymentStep>` directly.
19
+ */
20
+ export interface CollectPaymentDialogProps {
21
+ open: boolean;
22
+ onOpenChange: (next: boolean) => void;
23
+ bookingId: string;
24
+ defaultCurrency: string;
25
+ defaultAmountCents?: number | null;
26
+ defaultPayerEmail?: string | null;
27
+ defaultPayerName?: string | null;
28
+ /** Card processor id registered in checkout's `paymentStarters`. */
29
+ cardProvider?: string;
30
+ }
31
+ export declare function CollectPaymentDialog({ open, onOpenChange, bookingId, defaultCurrency, defaultAmountCents, defaultPayerEmail, defaultPayerName, cardProvider, }: CollectPaymentDialogProps): import("react/jsx-runtime").JSX.Element;
32
+ //# sourceMappingURL=collect-payment-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-payment-dialog.d.ts","sourceRoot":"","sources":["../../src/components/collect-payment-dialog.tsx"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AASD,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,GACb,EAAE,yBAAyB,2CA6H3B"}
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCollectPayment } from "@voyantjs/checkout-react";
4
+ import { Button } from "@voyantjs/ui/components/button";
5
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@voyantjs/ui/components/dialog";
6
+ import { Input } from "@voyantjs/ui/components/input";
7
+ import { Label } from "@voyantjs/ui/components/label";
8
+ import { CheckCircle2, Copy, ExternalLink, Loader2 } from "lucide-react";
9
+ import { useMemo, useState } from "react";
10
+ import { toast } from "sonner";
11
+ import { PaymentStep } from "./payment-step.js";
12
+ const CAPABILITIES = {
13
+ chargeSavedCard: false,
14
+ newCard: false,
15
+ sendLink: true,
16
+ bankTransfer: true,
17
+ };
18
+ export function CollectPaymentDialog({ open, onOpenChange, bookingId, defaultCurrency, defaultAmountCents, defaultPayerEmail, defaultPayerName, cardProvider, }) {
19
+ const [amountCents, setAmountCents] = useState(defaultAmountCents ?? 0);
20
+ const [choice, setChoice] = useState(null);
21
+ const [result, setResult] = useState(null);
22
+ const collect = useCollectPayment(bookingId, {
23
+ cardProvider,
24
+ payerEmail: defaultPayerEmail,
25
+ payerName: defaultPayerName,
26
+ });
27
+ const amountInputValue = useMemo(() => (amountCents > 0 ? (amountCents / 100).toFixed(2) : ""), [amountCents]);
28
+ function reset() {
29
+ setAmountCents(defaultAmountCents ?? 0);
30
+ setChoice(null);
31
+ setResult(null);
32
+ collect.reset();
33
+ }
34
+ async function submit() {
35
+ if (!choice)
36
+ return;
37
+ if (choice.type !== "send_link" && choice.type !== "bank_transfer") {
38
+ toast.error("Pick 'Send payment link' or 'Bank transfer' to collect now.");
39
+ return;
40
+ }
41
+ if (amountCents <= 0) {
42
+ toast.error("Enter an amount above zero.");
43
+ return;
44
+ }
45
+ try {
46
+ const data = await collect.mutateAsync({ choice, amountCents });
47
+ setResult(data);
48
+ toast.success(choice.type === "send_link"
49
+ ? "Payment link sent — customer received the email."
50
+ : "Bank-transfer instructions ready below.");
51
+ }
52
+ catch (err) {
53
+ toast.error(err.message);
54
+ }
55
+ }
56
+ return (_jsx(Dialog, { open: open, onOpenChange: (next) => {
57
+ onOpenChange(next);
58
+ if (!next)
59
+ reset();
60
+ }, children: _jsxs(DialogContent, { className: "max-w-xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Collect payment" }), _jsx(DialogDescription, { children: "Pick how the customer will pay. Card flows redirect to the configured processor; bank-transfer surfaces IBAN instructions you can pass on." })] }), result ? (_jsx(ResultPanel, { result: result })) : (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs(Label, { htmlFor: "collect-amount", children: ["Amount (", defaultCurrency, ")"] }), _jsx(Input, { id: "collect-amount", type: "number", inputMode: "decimal", step: "0.01", min: "0", value: amountInputValue, onChange: (e) => {
61
+ const raw = Number.parseFloat(e.target.value);
62
+ setAmountCents(Number.isFinite(raw) ? Math.round(raw * 100) : 0);
63
+ } }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Defaults to the booking's sell amount. Override for a deposit or partial collection." })] }), _jsx(PaymentStep, { value: choice, onChange: setChoice, capabilities: CAPABILITIES, hideHoldOption: true })] })), _jsx(DialogFooter, { children: result ? (_jsx(Button, { onClick: () => onOpenChange(false), children: "Done" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => onOpenChange(false), disabled: collect.isPending, children: "Cancel" }), _jsxs(Button, { onClick: submit, disabled: collect.isPending ||
64
+ !choice ||
65
+ (choice.type !== "send_link" && choice.type !== "bank_transfer"), children: [collect.isPending && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), choice?.type === "bank_transfer" ? "Generate instructions" : "Send payment link"] })] })) })] }) }));
66
+ }
67
+ function ResultPanel({ result }) {
68
+ if (result.plan.method === "card") {
69
+ const url = result.providerStart?.redirectUrl ?? result.paymentSession?.redirectUrl ?? null;
70
+ return (_jsxs("div", { className: "flex flex-col gap-4 rounded-xl border bg-card p-5", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(CheckCircle2, { className: "h-5 w-5" }), _jsx("span", { className: "font-medium", children: "Payment link ready" })] }), _jsx("p", { className: "text-muted-foreground text-sm", children: "The customer received an email with the link. You can also share it directly:" }), url ? (_jsxs("div", { className: "flex items-center gap-2 rounded-md border bg-muted/30 p-3 font-mono text-xs", children: [_jsx("span", { className: "flex-1 break-all", children: url }), _jsx("button", { type: "button", "aria-label": "Copy link", className: "text-muted-foreground hover:text-foreground", onClick: () => {
71
+ navigator.clipboard?.writeText(url).catch(() => undefined);
72
+ }, children: _jsx(Copy, { className: "h-3.5 w-3.5" }) }), _jsx("a", { href: url, target: "_blank", rel: "noreferrer", "aria-label": "Open link", className: "text-muted-foreground hover:text-foreground", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5" }) })] })) : (_jsxs("p", { className: "text-muted-foreground text-xs", children: ["The processor didn't return an immediate redirect URL. The session id is", " ", _jsx("span", { className: "font-mono", children: result.paymentSession?.id ?? "—" }), "."] }))] }));
73
+ }
74
+ const ins = result.bankTransferInstructions;
75
+ if (!ins) {
76
+ return (_jsxs("div", { className: "rounded-xl border border-amber-500/40 bg-amber-500/5 p-5 text-sm", children: ["Bank-transfer instructions are not configured for this template. Set", _jsx("span", { className: "font-mono", children: " BANK_TRANSFER_BENEFICIARY " }), "and friends in ", _jsx("span", { className: "font-mono", children: ".dev.vars" }), "."] }));
77
+ }
78
+ return (_jsxs("div", { className: "flex flex-col gap-4 rounded-xl border bg-card p-5", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(CheckCircle2, { className: "h-5 w-5" }), _jsx("span", { className: "font-medium", children: "Bank-transfer instructions" })] }), _jsxs("dl", { className: "grid grid-cols-1 gap-2 text-sm", children: [_jsx(Row, { label: "Beneficiary", children: ins.beneficiary }), _jsx(Row, { label: "IBAN", children: _jsx("span", { className: "font-mono", children: ins.iban }) }), ins.bankName && _jsx(Row, { label: "Bank", children: ins.bankName }), _jsx(Row, { label: "Reference", children: _jsx("span", { className: "font-mono", children: ins.invoiceNumber }) })] }), ins.notes && _jsx("p", { className: "text-muted-foreground text-xs", children: ins.notes })] }));
79
+ }
80
+ function Row({ label, children }) {
81
+ return (_jsxs("div", { className: "flex items-baseline justify-between gap-3 border-b py-1.5 last:border-b-0", children: [_jsx("dt", { className: "text-muted-foreground text-xs uppercase tracking-wider", children: label }), _jsx("dd", { children: children })] }));
82
+ }
@@ -0,0 +1,54 @@
1
+ import type { PublicPaymentSession } from "@voyantjs/finance/public-validation";
2
+ import { type ReactNode } from "react";
3
+ /**
4
+ * Universal landing page rendered at `/pay/:sessionId`. The customer lands
5
+ * here from a payment-link email (or after returning from the processor's
6
+ * hosted checkout). Vertical-agnostic — same component for a flight order,
7
+ * a hotel deposit, or a cruise balance.
8
+ *
9
+ * The parent owns:
10
+ * - fetching `PublicPaymentSession` from `/v1/public/payment-sessions/:id`
11
+ * (typically via `usePublicPaymentSession` from `@voyantjs/finance-react`)
12
+ * - re-fetching after the user returns from the processor (the processor
13
+ * redirects back to `session.returnUrl`; the page re-mounts and the
14
+ * latest status is shown)
15
+ * - sourcing the bank-transfer block from template config
16
+ *
17
+ * See `docs/architecture/payments-architecture.md` §Core Rule 4.
18
+ */
19
+ export interface PaymentLinkLandingPageProps {
20
+ session: PublicPaymentSession;
21
+ /** Bank-transfer instructions block — template-supplied per deployment. */
22
+ bankTransferInstructions?: BankTransferInstructions;
23
+ /** Header slot (logo, brand name, optional support contact). */
24
+ brandHeader?: ReactNode;
25
+ /** Footer slot (T&Cs link, support contact, privacy). */
26
+ brandFooter?: ReactNode;
27
+ /**
28
+ * Fired when the customer clicks "Pay by card" — the parent typically
29
+ * redirects to `session.redirectUrl` (the processor's hosted checkout).
30
+ */
31
+ onPayByCard?: () => void;
32
+ /**
33
+ * Optional human-readable description shown above the amount. Sourced
34
+ * from the booking / invoice / vertical context (the session record
35
+ * itself doesn't carry a description today).
36
+ */
37
+ description?: string;
38
+ }
39
+ export interface BankTransferInstructions {
40
+ beneficiaryName: string;
41
+ iban: string;
42
+ bic?: string;
43
+ bankName?: string;
44
+ /**
45
+ * Reference the customer must include in their wire so finance can
46
+ * reconcile the inbound transfer. Defaults to the session's
47
+ * `externalReference` / `clientReference` / `id` when not supplied.
48
+ */
49
+ reference?: string;
50
+ /** Free-text notes — surfaced under the IBAN block. */
51
+ notes?: string;
52
+ }
53
+ export declare function PaymentLinkLandingPage({ session, bankTransferInstructions, brandHeader, brandFooter, onPayByCard, description, }: PaymentLinkLandingPageProps): import("react/jsx-runtime").JSX.Element;
54
+ //# sourceMappingURL=payment-link-landing-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-link-landing-page.d.ts","sourceRoot":"","sources":["../../src/components/payment-link-landing-page.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAA;AAK/E,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,oBAAoB,CAAA;IAC7B,2EAA2E;IAC3E,wBAAwB,CAAC,EAAE,wBAAwB,CAAA;IACnD,gEAAgE;IAChE,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,yDAAyD;IACzD,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,MAAM,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,wBAAwB,EACxB,WAAW,EACX,WAAW,EACX,WAAW,EACX,WAAW,GACZ,EAAE,2BAA2B,2CAa7B"}
@@ -0,0 +1,147 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button } from "@voyantjs/ui/components/button";
4
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/components/tabs";
5
+ import { cn } from "@voyantjs/ui/lib/utils";
6
+ import { Building2, CheckCircle2, CircleAlert, Copy, CreditCard, ExternalLink, Loader2 } from "lucide-react";
7
+ import { useState } from "react";
8
+ export function PaymentLinkLandingPage({ session, bankTransferInstructions, brandHeader, brandFooter, onPayByCard, description, }) {
9
+ return (_jsxs("div", { className: "mx-auto flex min-h-screen w-full max-w-2xl flex-col gap-6 px-4 py-8", children: [brandHeader, _jsx(Header, { session: session, description: description }), _jsx(Body, { session: session, bankTransferInstructions: bankTransferInstructions, onPayByCard: onPayByCard }), brandFooter] }));
10
+ }
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ function Header({ session, description, }) {
13
+ return (_jsxs("header", { className: "flex flex-col gap-2 border-b pb-4", children: [_jsx("h1", { className: "font-semibold text-2xl", children: description ?? defaultDescription(session) }), _jsxs("div", { className: "flex items-baseline gap-3", children: [_jsx("span", { className: "font-semibold text-3xl tabular-nums", children: formatMoneyCents(session.amountCents, session.currency) }), session.expiresAt && session.status !== "paid" && (_jsxs("span", { className: "text-muted-foreground text-sm", children: ["Expires ", formatDateTime(session.expiresAt)] }))] })] }));
14
+ }
15
+ function Body({ session, bankTransferInstructions, onPayByCard, }) {
16
+ // Terminal states — short-circuit body to a status panel.
17
+ if (session.status === "paid")
18
+ return _jsx(TerminalState, { status: "paid", reason: null });
19
+ if (session.status === "failed") {
20
+ return (_jsx(TerminalState, { status: "failed", reason: session.failureMessage ?? null, retryUrl: session.redirectUrl ?? null }));
21
+ }
22
+ if (session.status === "expired")
23
+ return _jsx(TerminalState, { status: "expired", reason: null });
24
+ if (session.status === "cancelled")
25
+ return _jsx(TerminalState, { status: "cancelled", reason: null });
26
+ if (session.status === "processing" || session.status === "authorized") {
27
+ return _jsx(ProcessingState, {});
28
+ }
29
+ const cardAvailable = Boolean(session.redirectUrl) || session.status === "requires_redirect";
30
+ const bankAvailable = Boolean(bankTransferInstructions);
31
+ // No method available — soft fallback (rare; signals a misconfigured
32
+ // session that has neither a redirect URL nor a bank-transfer block).
33
+ if (!cardAvailable && !bankAvailable) {
34
+ return (_jsxs("div", { className: "rounded-xl border border-amber-500/40 bg-amber-500/5 p-5 text-sm", children: [_jsx("p", { className: "font-medium", children: "No payment method available" }), _jsx("p", { className: "mt-2 text-muted-foreground", children: "This payment link doesn't have any payment methods configured. Please contact your travel agent." })] }));
35
+ }
36
+ // Single method — render inline without tabs.
37
+ if (cardAvailable && !bankAvailable) {
38
+ return _jsx(CardPanel, { session: session, onPayByCard: onPayByCard });
39
+ }
40
+ if (bankAvailable && !cardAvailable && bankTransferInstructions) {
41
+ return _jsx(BankTransferPanel, { session: session, instructions: bankTransferInstructions });
42
+ }
43
+ // Both methods — tab strip.
44
+ return (_jsxs(Tabs, { defaultValue: "card", className: "w-full", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsxs(TabsTrigger, { value: "card", children: [_jsx(CreditCard, { className: "mr-2 h-4 w-4" }), "Pay by card"] }), _jsxs(TabsTrigger, { value: "bank", children: [_jsx(Building2, { className: "mr-2 h-4 w-4" }), "Bank transfer"] })] }), _jsx(TabsContent, { value: "card", className: "mt-4", children: _jsx(CardPanel, { session: session, onPayByCard: onPayByCard }) }), _jsx(TabsContent, { value: "bank", className: "mt-4", children: bankTransferInstructions && (_jsx(BankTransferPanel, { session: session, instructions: bankTransferInstructions })) })] }));
45
+ }
46
+ function CardPanel({ session, onPayByCard, }) {
47
+ return (_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("p", { className: "mb-4 text-muted-foreground text-sm", children: "You'll be redirected to the secure payment page hosted by the card processor." }), _jsxs(Button, { className: "w-full", onClick: () => {
48
+ if (onPayByCard)
49
+ onPayByCard();
50
+ else if (session.redirectUrl)
51
+ window.location.href = session.redirectUrl;
52
+ }, children: ["Pay ", formatMoneyCents(session.amountCents, session.currency), _jsx(ExternalLink, { className: "ml-2 h-4 w-4" })] })] }));
53
+ }
54
+ function BankTransferPanel({ session, instructions, }) {
55
+ const reference = instructions.reference ?? session.externalReference ?? session.clientReference ?? session.id;
56
+ return (_jsxs("div", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("p", { className: "mb-4 text-muted-foreground text-sm", children: ["Wire ", formatMoneyCents(session.amountCents, session.currency), " to the account below. Your booking is confirmed once payment is received (typically 1\u20133 business days)."] }), _jsxs("dl", { className: "grid grid-cols-1 gap-2 text-sm", children: [_jsx(Row, { label: "Beneficiary", children: instructions.beneficiaryName }), _jsx(Row, { label: "IBAN", copyValue: instructions.iban, children: _jsx("span", { className: "font-mono", children: instructions.iban }) }), instructions.bic && (_jsx(Row, { label: "BIC / SWIFT", copyValue: instructions.bic, children: _jsx("span", { className: "font-mono", children: instructions.bic }) })), instructions.bankName && _jsx(Row, { label: "Bank", children: instructions.bankName }), _jsx(Row, { label: "Reference", copyValue: reference, children: _jsx("span", { className: "font-mono", children: reference }) })] }), instructions.notes && (_jsx("p", { className: "mt-4 text-muted-foreground text-xs", children: instructions.notes }))] }));
57
+ }
58
+ function Row({ label, children, copyValue, }) {
59
+ return (_jsxs("div", { className: "flex items-baseline justify-between gap-3 border-b py-1.5 last:border-b-0", children: [_jsx("dt", { className: "text-muted-foreground text-xs uppercase tracking-wider", children: label }), _jsxs("dd", { className: "flex items-center gap-2", children: [children, copyValue && _jsx(CopyButton, { value: copyValue })] })] }));
60
+ }
61
+ function CopyButton({ value }) {
62
+ const [copied, setCopied] = useState(false);
63
+ return (_jsx("button", { type: "button", "aria-label": copied ? "Copied" : `Copy ${value}`, className: "text-muted-foreground transition-colors hover:text-foreground", onClick: async () => {
64
+ try {
65
+ await navigator.clipboard?.writeText(value);
66
+ setCopied(true);
67
+ setTimeout(() => setCopied(false), 1500);
68
+ }
69
+ catch {
70
+ // Older browsers without clipboard API — no-op; the value is still
71
+ // visible and selectable for manual copy.
72
+ }
73
+ }, children: copied ? (_jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-emerald-600" })) : (_jsx(Copy, { className: "h-3.5 w-3.5" })) }));
74
+ }
75
+ function TerminalState({ status, reason, retryUrl, }) {
76
+ const cfg = {
77
+ paid: {
78
+ icon: _jsx(CheckCircle2, { className: "h-10 w-10 text-emerald-600" }),
79
+ title: "Payment received",
80
+ body: "Thanks — your booking is confirmed. You'll receive a confirmation email shortly.",
81
+ tone: "border-emerald-500/40 bg-emerald-500/5",
82
+ },
83
+ failed: {
84
+ icon: _jsx(CircleAlert, { className: "h-10 w-10 text-destructive" }),
85
+ title: "Payment failed",
86
+ body: reason ?? "The payment couldn't be processed. Please try again or contact support.",
87
+ tone: "border-destructive/40 bg-destructive/5",
88
+ },
89
+ expired: {
90
+ icon: _jsx(CircleAlert, { className: "h-10 w-10 text-amber-600" }),
91
+ title: "Payment link expired",
92
+ body: "This payment link has expired. Please request a new one from your travel agent.",
93
+ tone: "border-amber-500/40 bg-amber-500/5",
94
+ },
95
+ cancelled: {
96
+ icon: _jsx(CircleAlert, { className: "h-10 w-10 text-muted-foreground" }),
97
+ title: "Payment cancelled",
98
+ body: "This payment was cancelled. Please contact your travel agent if this is unexpected.",
99
+ tone: "border-border bg-muted/20",
100
+ },
101
+ }[status];
102
+ return (_jsxs("div", { className: cn("flex flex-col items-center gap-3 rounded-xl border p-8 text-center", cfg.tone), children: [cfg.icon, _jsx("h2", { className: "font-semibold text-lg", children: cfg.title }), _jsx("p", { className: "max-w-md text-muted-foreground text-sm", children: cfg.body }), status === "failed" && retryUrl && (_jsxs(Button, { className: "mt-2", onClick: () => {
103
+ window.location.href = retryUrl;
104
+ }, children: ["Try again", _jsx(ExternalLink, { className: "ml-2 h-4 w-4" })] }))] }));
105
+ }
106
+ function ProcessingState() {
107
+ return (_jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border bg-card p-8 text-center", children: [_jsx(Loader2, { className: "h-10 w-10 animate-spin text-muted-foreground" }), _jsx("h2", { className: "font-semibold text-lg", children: "Processing payment\u2026" }), _jsx("p", { className: "max-w-md text-muted-foreground text-sm", children: "We're confirming the payment with the processor. This usually takes a few seconds." })] }));
108
+ }
109
+ function defaultDescription(session) {
110
+ switch (session.targetType) {
111
+ case "booking":
112
+ return "Booking payment";
113
+ case "booking_payment_schedule":
114
+ return "Booking deposit";
115
+ case "booking_guarantee":
116
+ return "Booking guarantee";
117
+ case "invoice":
118
+ return "Invoice payment";
119
+ case "order":
120
+ return "Order payment";
121
+ case "flight_order":
122
+ return "Flight payment";
123
+ default:
124
+ return "Payment";
125
+ }
126
+ }
127
+ function formatMoneyCents(cents, currency) {
128
+ const amount = cents / 100;
129
+ if (!Number.isFinite(amount))
130
+ return `${cents} ${currency}`;
131
+ return new Intl.NumberFormat(undefined, {
132
+ style: "currency",
133
+ currency,
134
+ maximumFractionDigits: 2,
135
+ }).format(amount);
136
+ }
137
+ function formatDateTime(iso) {
138
+ const d = new Date(iso);
139
+ if (Number.isNaN(d.getTime()))
140
+ return iso;
141
+ return new Intl.DateTimeFormat(undefined, {
142
+ day: "numeric",
143
+ month: "short",
144
+ hour: "2-digit",
145
+ minute: "2-digit",
146
+ }).format(d);
147
+ }
@@ -0,0 +1,49 @@
1
+ import type { PaymentChoice, PaymentStepCapabilities, PaymentStepExtraOption as CheckoutPaymentStepExtraOption, SavedPaymentAccount } from "@voyantjs/checkout-react";
2
+ import { type ReactNode } from "react";
3
+ /**
4
+ * UI-side extension of the `PaymentStepExtraOption` from checkout-react —
5
+ * adds an optional `icon` slot since the icon is a presentation concern.
6
+ */
7
+ export interface PaymentStepExtraOption extends CheckoutPaymentStepExtraOption {
8
+ icon?: ReactNode;
9
+ }
10
+ export interface PaymentStepProps {
11
+ value: PaymentChoice | null;
12
+ onChange: (next: PaymentChoice | null) => void;
13
+ /**
14
+ * What the active processor / template actually offers. Each `false`
15
+ * value hides its section. Honest — no invented capability strings.
16
+ */
17
+ capabilities: PaymentStepCapabilities;
18
+ /** Saved methods on file — surfaced when `capabilities.chargeSavedCard` is true. */
19
+ savedMethods?: SavedPaymentAccount[];
20
+ loadingSavedMethods?: boolean;
21
+ /**
22
+ * Vertical-specific always-available options. Rendered after the
23
+ * capability-gated sections, before the universal Hold option.
24
+ */
25
+ extraOptions?: ReadonlyArray<PaymentStepExtraOption>;
26
+ /** Hide the universal "Hold — pay later" option (some verticals don't support it). */
27
+ hideHoldOption?: boolean;
28
+ /**
29
+ * Optional callback fired when the operator picks "Send payment link".
30
+ * The parent wires it to `useInitiateCheckoutCollection` (or whichever
31
+ * checkout endpoint applies for the vertical).
32
+ */
33
+ onRequestPaymentLink?: () => void;
34
+ }
35
+ /**
36
+ * Universal capability-gated payment picker. Renders sections based on
37
+ * what the parent declares is actually wired, plus optional vertical-
38
+ * specific extras and the universal Hold option.
39
+ *
40
+ * Sections, in order:
41
+ * - Saved cards on file ← capabilities.chargeSavedCard
42
+ * - Send payment link ← capabilities.sendLink
43
+ * - Charge a new card now ← capabilities.newCard
44
+ * - Bank transfer ← capabilities.bankTransfer
45
+ * - Vertical-specific extras ← always rendered (e.g. "Issue on credit")
46
+ * - Hold seats — pay later ← always rendered (unless hideHoldOption)
47
+ */
48
+ export declare function PaymentStep({ value, onChange, capabilities, savedMethods, loadingSavedMethods, extraOptions, hideHoldOption, onRequestPaymentLink, }: PaymentStepProps): import("react/jsx-runtime").JSX.Element;
49
+ //# sourceMappingURL=payment-step.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-step.d.ts","sourceRoot":"","sources":["../../src/components/payment-step.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EACvB,sBAAsB,IAAI,8BAA8B,EACxD,mBAAmB,EACpB,MAAM,0BAA0B,CAAA;AAMjC,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,8BAA8B;IAC5E,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,KAAK,IAAI,CAAA;IAE9C;;;OAGG;IACH,YAAY,EAAE,uBAAuB,CAAA;IAErC,oFAAoF;IACpF,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAA;IACpC,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAE7B;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAA;IAEpD,sFAAsF;IACtF,cAAc,CAAC,EAAE,OAAO,CAAA;IAExB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,oBAAoB,GACrB,EAAE,gBAAgB,2CA+BlB"}
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Input } from "@voyantjs/ui/components/input";
4
+ import { Label } from "@voyantjs/ui/components/label";
5
+ import { RadioGroup, RadioGroupItem } from "@voyantjs/ui/components/radio-group";
6
+ import { cn } from "@voyantjs/ui/lib/utils";
7
+ import { Banknote, CreditCard, Link2, Wallet } from "lucide-react";
8
+ import { useState } from "react";
9
+ /**
10
+ * Universal capability-gated payment picker. Renders sections based on
11
+ * what the parent declares is actually wired, plus optional vertical-
12
+ * specific extras and the universal Hold option.
13
+ *
14
+ * Sections, in order:
15
+ * - Saved cards on file ← capabilities.chargeSavedCard
16
+ * - Send payment link ← capabilities.sendLink
17
+ * - Charge a new card now ← capabilities.newCard
18
+ * - Bank transfer ← capabilities.bankTransfer
19
+ * - Vertical-specific extras ← always rendered (e.g. "Issue on credit")
20
+ * - Hold seats — pay later ← always rendered (unless hideHoldOption)
21
+ */
22
+ export function PaymentStep({ value, onChange, capabilities, savedMethods, loadingSavedMethods, extraOptions, hideHoldOption, onRequestPaymentLink, }) {
23
+ return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { children: [_jsx("h2", { className: "font-semibold text-base", children: "Payment" }), _jsx("p", { className: "text-muted-foreground text-sm", children: "Pick a saved method or use a different payment option." })] }), capabilities.chargeSavedCard && (_jsx(SavedMethodsSection, { loading: loadingSavedMethods, methods: savedMethods ?? [], selectedId: value?.type === "saved_method" ? value.method.id : null, onSelect: (method) => onChange({ type: "saved_method", method }) })), _jsx(AltMethodsSection, { value: value, onChange: onChange, showNewCard: !!capabilities.newCard, showPaymentLink: !!capabilities.sendLink, showBankTransfer: !!capabilities.bankTransfer, extraOptions: extraOptions ?? [], hideHoldOption: hideHoldOption, onRequestPaymentLink: onRequestPaymentLink })] }));
24
+ }
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ function SavedMethodsSection({ loading, methods, selectedId, onSelect, }) {
27
+ return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsxs("header", { className: "mb-3 flex items-center justify-between", children: [_jsx("h3", { className: "font-medium text-sm", children: "Saved payment methods" }), methods.length > 0 && (_jsxs("span", { className: "text-muted-foreground text-xs", children: [methods.length, " on file"] }))] }), loading ? (_jsx("div", { className: "h-16 animate-pulse rounded-md bg-muted/40" })) : methods.length === 0 ? (_jsx("div", { className: "rounded-md border border-dashed p-4 text-center text-muted-foreground text-xs", children: "No saved methods on file for this contact." })) : (_jsx("ul", { className: "flex flex-col gap-2", children: methods.map((m) => {
28
+ const selected = selectedId === m.id;
29
+ return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelect(m), className: cn("flex w-full items-center gap-3 rounded-md border bg-background p-3 text-left transition-colors", selected
30
+ ? "border-primary ring-2 ring-primary/20"
31
+ : "hover:border-primary/40 hover:bg-accent/30"), children: [_jsx(BrandTile, { brand: m.brand }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [_jsxs("span", { className: "font-medium text-sm", children: [m.label, m.last4 && (_jsxs(_Fragment, { children: [" ···· ", _jsx("span", { className: "font-mono", children: m.last4 })] })), m.isDefault && (_jsx("span", { className: "ml-2 rounded-full bg-primary/10 px-1.5 py-0.5 font-medium text-[9px] text-primary uppercase tracking-wider", children: "Default" }))] }), m.expiryMonth && m.expiryYear && (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["Expires ", String(m.expiryMonth).padStart(2, "0"), "/", m.expiryYear] }))] }), selected && (_jsx("span", { className: "font-medium text-primary text-xs", children: "Selected" }))] }) }, m.id));
32
+ }) }))] }));
33
+ }
34
+ function AltMethodsSection({ value, onChange, showNewCard, showPaymentLink, showBankTransfer, extraOptions, hideHoldOption, onRequestPaymentLink, }) {
35
+ const [newCardName, setNewCardName] = useState("");
36
+ const [newCardNumber, setNewCardNumber] = useState("");
37
+ const [newCardExp, setNewCardExp] = useState("");
38
+ const activeId = (() => {
39
+ if (value == null)
40
+ return null;
41
+ if (value.type === "saved_method")
42
+ return null;
43
+ if (value.type === "new_card")
44
+ return "new_card";
45
+ if (value.type === "send_link")
46
+ return "send_link";
47
+ if (value.type === "bank_transfer")
48
+ return "bank_transfer";
49
+ if (value.type === "hold")
50
+ return "hold";
51
+ if (value.type === "extra")
52
+ return `extra:${value.optionId}`;
53
+ return null;
54
+ })();
55
+ const setAlt = (id) => {
56
+ if (id === "hold") {
57
+ onChange({ type: "hold" });
58
+ }
59
+ else if (id === "new_card") {
60
+ onChange({
61
+ type: "new_card",
62
+ cardToken: tokenizeNewCard(newCardNumber),
63
+ ...(newCardName ? { cardholderName: newCardName } : {}),
64
+ ...(newCardExp ? { expiry: newCardExp } : {}),
65
+ });
66
+ }
67
+ else if (id === "send_link") {
68
+ onChange({ type: "send_link" });
69
+ onRequestPaymentLink?.();
70
+ }
71
+ else if (id === "bank_transfer") {
72
+ onChange({ type: "bank_transfer" });
73
+ }
74
+ else if (id.startsWith("extra:")) {
75
+ onChange({ type: "extra", optionId: id.slice("extra:".length) });
76
+ }
77
+ };
78
+ return (_jsxs("section", { className: "rounded-xl border bg-card p-5 shadow-sm", children: [_jsx("header", { className: "mb-3 flex items-center justify-between", children: _jsx("h3", { className: "font-medium text-sm", children: "Other payment options" }) }), _jsxs(RadioGroup, { value: activeId ?? "__none", onValueChange: (v) => {
79
+ if (!v)
80
+ return;
81
+ if (v === "send_link" ||
82
+ v === "new_card" ||
83
+ v === "bank_transfer" ||
84
+ v === "hold" ||
85
+ v.startsWith("extra:")) {
86
+ setAlt(v);
87
+ }
88
+ }, className: "flex flex-col gap-2", children: [showPaymentLink && (_jsx(AltRow, { id: "send_link", icon: _jsx(Link2, { className: "h-4 w-4 text-muted-foreground" }), title: "Send payment link to customer", body: "Hold the order, generate a payment link, and email it for the customer to pay.", active: activeId === "send_link" })), showNewCard && (_jsx(AltRow, { id: "new_card", icon: _jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" }), title: "New credit / debit card", body: "Charge a one-off card now.", active: activeId === "new_card", children: activeId === "new_card" && (_jsxs("div", { className: "mt-3 grid grid-cols-1 gap-3 md:grid-cols-3", children: [_jsx(Field, { label: "Cardholder name", children: _jsx(Input, { value: newCardName, onChange: (e) => {
89
+ setNewCardName(e.target.value);
90
+ if (value?.type === "new_card") {
91
+ onChange({ ...value, cardholderName: e.target.value });
92
+ }
93
+ } }) }), _jsx(Field, { label: "Card number", children: _jsx(Input, { inputMode: "numeric", placeholder: "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022", value: newCardNumber, onChange: (e) => {
94
+ setNewCardNumber(e.target.value);
95
+ if (value?.type === "new_card") {
96
+ onChange({ ...value, cardToken: tokenizeNewCard(e.target.value) });
97
+ }
98
+ } }) }), _jsx(Field, { label: "MM/YY", children: _jsx(Input, { value: newCardExp, onChange: (e) => {
99
+ setNewCardExp(e.target.value);
100
+ if (value?.type === "new_card") {
101
+ onChange({ ...value, expiry: e.target.value });
102
+ }
103
+ }, placeholder: "08/29" }) })] })) })), showBankTransfer && (_jsx(AltRow, { id: "bank_transfer", icon: _jsx(Banknote, { className: "h-4 w-4 text-muted-foreground" }), title: "Bank transfer", body: "Hold the order; customer pays by wire to the IBAN shown on the payment page.", active: activeId === "bank_transfer" })), extraOptions.map((opt) => (_jsx(AltRow, { id: `extra:${opt.id}`, icon: opt.icon, title: opt.label, body: opt.description, active: activeId === `extra:${opt.id}` }, opt.id))), !hideHoldOption && (_jsx(AltRow, { id: "hold", icon: _jsx(Wallet, { className: "h-4 w-4 text-muted-foreground" }), title: "Hold \u2014 pay later", body: "Lock the order; pay before the deadline to confirm.", active: activeId === "hold" }))] }), showNewCard && (_jsxs("p", { className: "mt-4 flex items-start gap-1.5 text-[11px] text-muted-foreground", children: [_jsx(Banknote, { className: "mt-0.5 h-3 w-3" }), "Card numbers entered here are tokenized in production via the processor's hosted form \u2014 never sent through this UI."] }))] }));
104
+ }
105
+ function AltRow({ id, icon, title, body, active, children, }) {
106
+ return (_jsxs("label", { className: cn("flex cursor-pointer items-start gap-3 rounded-md border bg-background p-3 transition-colors", active ? "border-primary bg-primary/5" : "hover:border-primary/40"), children: [_jsx(RadioGroupItem, { value: id, className: "mt-0.5" }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("span", { className: "flex items-center gap-2 font-medium text-sm", children: [icon, title] }), _jsx("span", { className: "text-muted-foreground text-xs", children: body }), children] })] }));
107
+ }
108
+ function Field({ label, children, }) {
109
+ return (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { className: "text-[11px] uppercase tracking-wider text-muted-foreground", children: label }), children] }));
110
+ }
111
+ function BrandTile({ brand }) {
112
+ return (_jsx("span", { className: "flex h-9 w-12 shrink-0 items-center justify-center rounded-md border bg-muted/30 font-mono text-[10px] uppercase tracking-wider", children: (brand ?? "card").slice(0, 4) }));
113
+ }
114
+ function tokenizeNewCard(number) {
115
+ // Demo "tokenization" — in production the processor's hosted form
116
+ // returns an opaque token; we never see the PAN here.
117
+ const last4 = number.replace(/\D/g, "").slice(-4) || "0000";
118
+ return `tok_demo_new_${last4}`;
119
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@voyantjs/checkout-ui` — universal checkout/payment UI components on top
3
+ * of `@voyantjs/checkout` + `@voyantjs/checkout-react`.
4
+ *
5
+ * - <PaymentStep> capability-driven payment picker
6
+ * - <PaymentLinkLandingPage> customer view of a payment link
7
+ * - <CollectPaymentDialog> operator-side "collect now" dialog
8
+ *
9
+ * Pure presentation. State lives in `@voyantjs/finance`, orchestration in
10
+ * `@voyantjs/checkout`, hooks in `@voyantjs/checkout-react`. Re-exports the
11
+ * universal types (`PaymentChoice`, capabilities) from checkout-react so
12
+ * consumers can `import { PaymentStep, type PaymentChoice } from
13
+ * "@voyantjs/checkout-ui"` without a second import.
14
+ *
15
+ * See `docs/architecture/payments-architecture.md`.
16
+ */
17
+ export { type PaymentChoice, type PaymentStepCapabilities, type SavedPaymentAccount, } from "@voyantjs/checkout-react";
18
+ export { type CollectPaymentDialogProps, CollectPaymentDialog, } from "./components/collect-payment-dialog.js";
19
+ export { type BankTransferInstructions, PaymentLinkLandingPage, type PaymentLinkLandingPageProps, } from "./components/payment-link-landing-page.js";
20
+ export { PaymentStep, type PaymentStepExtraOption, type PaymentStepProps, } from "./components/payment-step.js";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,KAAK,yBAAyB,EAC9B,oBAAoB,GACrB,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EACL,KAAK,wBAAwB,EAC7B,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,2CAA2C,CAAA;AAClD,OAAO,EACL,WAAW,EACX,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,GACtB,MAAM,8BAA8B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@voyantjs/checkout-ui` — universal checkout/payment UI components on top
3
+ * of `@voyantjs/checkout` + `@voyantjs/checkout-react`.
4
+ *
5
+ * - <PaymentStep> capability-driven payment picker
6
+ * - <PaymentLinkLandingPage> customer view of a payment link
7
+ * - <CollectPaymentDialog> operator-side "collect now" dialog
8
+ *
9
+ * Pure presentation. State lives in `@voyantjs/finance`, orchestration in
10
+ * `@voyantjs/checkout`, hooks in `@voyantjs/checkout-react`. Re-exports the
11
+ * universal types (`PaymentChoice`, capabilities) from checkout-react so
12
+ * consumers can `import { PaymentStep, type PaymentChoice } from
13
+ * "@voyantjs/checkout-ui"` without a second import.
14
+ *
15
+ * See `docs/architecture/payments-architecture.md`.
16
+ */
17
+ export { CollectPaymentDialog, } from "./components/collect-payment-dialog.js";
18
+ export { PaymentLinkLandingPage, } from "./components/payment-link-landing-page.js";
19
+ export { PaymentStep, } from "./components/payment-step.js";
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@voyantjs/checkout-ui",
3
+ "version": "0.19.0",
4
+ "license": "Apache-2.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/voyantjs/voyant.git",
8
+ "directory": "packages/checkout-ui"
9
+ },
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "exports": {
13
+ ".": "./src/index.ts",
14
+ "./styles.css": "./src/styles.css",
15
+ "./components/*": "./src/components/*.tsx"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "clean": "rm -rf dist",
20
+ "prepack": "pnpm run build",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "biome check src/",
23
+ "test": "vitest run --passWithNoTests"
24
+ },
25
+ "peerDependencies": {
26
+ "@voyantjs/checkout": "workspace:*",
27
+ "@voyantjs/checkout-react": "workspace:*",
28
+ "@voyantjs/finance": "workspace:*",
29
+ "@voyantjs/finance-react": "workspace:*",
30
+ "@voyantjs/ui": "workspace:*",
31
+ "@tanstack/react-query": "^5.0.0",
32
+ "react": "^19.0.0",
33
+ "react-dom": "^19.0.0",
34
+ "sonner": "^2.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@tanstack/react-query": "^5.96.2",
38
+ "@types/react": "^19.2.14",
39
+ "@types/react-dom": "^19.2.3",
40
+ "@voyantjs/checkout": "workspace:*",
41
+ "@voyantjs/checkout-react": "workspace:*",
42
+ "@voyantjs/finance": "workspace:*",
43
+ "@voyantjs/finance-react": "workspace:*",
44
+ "@voyantjs/ui": "workspace:*",
45
+ "@voyantjs/voyant-typescript-config": "workspace:*",
46
+ "lucide-react": "^0.475.0",
47
+ "react": "^19.2.4",
48
+ "react-dom": "^19.2.4",
49
+ "sonner": "^2.0.7",
50
+ "typescript": "^6.0.2",
51
+ "vitest": "^4.1.2"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "src/styles.css"
56
+ ],
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "exports": {
60
+ ".": {
61
+ "types": "./dist/index.d.ts",
62
+ "import": "./dist/index.js",
63
+ "default": "./dist/index.js"
64
+ },
65
+ "./styles.css": {
66
+ "default": "./src/styles.css"
67
+ },
68
+ "./components/*": {
69
+ "types": "./dist/components/*.d.ts",
70
+ "import": "./dist/components/*.js",
71
+ "default": "./dist/components/*.js"
72
+ }
73
+ },
74
+ "main": "./dist/index.js",
75
+ "types": "./dist/index.d.ts"
76
+ }
77
+ }
package/src/styles.css ADDED
@@ -0,0 +1 @@
1
+ /* @voyantjs/payments-ui — placeholder for any payments-domain CSS. */