@12-apps/payments-frontend 1.0.0 → 1.2.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/package.json +7 -4
- package/src/__tests__/provider-priority-list.test.tsx +2 -2
- package/src/__tests__/slugged-provider.test.tsx +108 -0
- package/src/card/cpf.ts +42 -0
- package/src/card/fields.tsx +254 -0
- package/src/card/format.ts +103 -0
- package/src/card/index.ts +42 -0
- package/src/card/stripe-token.ts +81 -0
- package/src/card/tokenize.test.ts +194 -0
- package/src/card/tokenize.ts +327 -0
- package/src/card/types.ts +54 -0
- package/src/components/PaymentProviderSettings.tsx +30 -4
- package/src/components/checkout/__tests__/card-3ds-handover.test.tsx +147 -0
- package/src/components/checkout/__tests__/clear-cart-on-paid.test.tsx +64 -0
- package/src/components/checkout/__tests__/hosted-return.test.ts +109 -0
- package/src/components/checkout/__tests__/method-capability.test.tsx +120 -0
- package/src/components/checkout/__tests__/payments-unavailable.test.tsx +53 -0
- package/src/components/checkout/__tests__/save-on-continue.test.tsx +165 -0
- package/src/components/checkout/__tests__/second-host.test.tsx +86 -0
- package/src/components/checkout/buyer-info-form.tsx +138 -0
- package/src/components/checkout/card-view.tsx +128 -0
- package/src/components/checkout/checkout-flow.tsx +201 -0
- package/src/components/checkout/checkout-steps.tsx +366 -0
- package/src/components/checkout/client.ts +157 -0
- package/src/components/checkout/hosted-return.ts +92 -0
- package/src/components/checkout/icons.tsx +61 -0
- package/src/components/checkout/method-capability.ts +69 -0
- package/src/components/checkout/method-picker.tsx +153 -0
- package/src/components/checkout/mui-defaults.tsx +218 -0
- package/src/components/checkout/payer-summary.tsx +81 -0
- package/src/components/checkout/payment-status.tsx +256 -0
- package/src/components/checkout/payments-unavailable.tsx +79 -0
- package/src/components/checkout/pix-view.tsx +179 -0
- package/src/components/checkout/types.ts +223 -0
- package/src/components/checkout/ui.tsx +171 -0
- package/src/components/checkout/use-card-checkout.ts +346 -0
- package/src/components/checkout/use-checkout-controller.ts +252 -0
- package/src/components/checkout/use-payment-polling.ts +93 -0
- package/src/components/settings-state.ts +45 -2
- package/src/index.ts +74 -1
- package/src/result.ts +11 -0
- package/src/components/CheckoutFlow.tsx +0 -169
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buyer checkout's payment vocabulary (FUT-7 / FUT-44 / FUT-58, moved into
|
|
3
|
+
* the library by FUT-564).
|
|
4
|
+
*
|
|
5
|
+
* Pure types with no runtime imports. Everything here is PAYMENT: what a
|
|
6
|
+
* charge, an order-in-payment, a buyer identity or a provider protocol looks
|
|
7
|
+
* like to the screens in this folder. The host-side halves — the cart, the
|
|
8
|
+
* catalog, order CREATION — stay in the host app and reach the flow through
|
|
9
|
+
* the ports declared on `CheckoutFlowProps`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Payment methods offered at checkout. Mirrors `Payment.method` (FUT-42). */
|
|
13
|
+
export type PaymentMethod = "PIX" | "CARD";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Order lifecycle, aligned with the FUT-43 backend:
|
|
17
|
+
* - `AWAITING_PAYMENT` — order created, charge raised, not yet reconciled (the
|
|
18
|
+
* "pending" UI state). Payment confirmation is **async** (provider webhook),
|
|
19
|
+
* so the client polls until a terminal state.
|
|
20
|
+
* - `PAID` — webhook confirmed; stock decremented in the same DB transaction.
|
|
21
|
+
* - `FAILED` — charge declined or the paid-transaction rolled back.
|
|
22
|
+
* - `EXPIRED` — the PIX charge (or checkout window) lapsed before payment.
|
|
23
|
+
*/
|
|
24
|
+
export type OrderStatus = "AWAITING_PAYMENT" | "PAID" | "FAILED" | "EXPIRED";
|
|
25
|
+
|
|
26
|
+
/** Terminal states — polling stops once the order reaches one of these. */
|
|
27
|
+
export const TERMINAL_STATUSES: readonly OrderStatus[] = ["PAID", "FAILED", "EXPIRED"];
|
|
28
|
+
|
|
29
|
+
/** Buyer contact captured at checkout. */
|
|
30
|
+
export interface BuyerInfo {
|
|
31
|
+
name?: string;
|
|
32
|
+
email?: string;
|
|
33
|
+
phone?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Buyer CPF (Brazilian taxpayer id) — REQUIRED by PagBank for every PIX/card
|
|
36
|
+
* charge (`customer.tax_id`).
|
|
37
|
+
*
|
|
38
|
+
* Persisted when the buyer opts in via "salvar meus dados" (LGPD consent),
|
|
39
|
+
* through the host's `saveBuyerContact` port at the end of the "Dados" step.
|
|
40
|
+
* It is never returned by any read — the host's profile API answers with
|
|
41
|
+
* `hasTaxId` only — so a returning buyer re-enters it. Order creation
|
|
42
|
+
* deliberately does NOT write it: that surface is MCP-exposed, and CPF must
|
|
43
|
+
* stay unwritable through the agent surface.
|
|
44
|
+
*/
|
|
45
|
+
taxId?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A raised PIX charge. `copyPaste` is the EMV "copia e cola" payload — it is both
|
|
50
|
+
* what the QR code encodes and what the copy button copies. The real backend also
|
|
51
|
+
* returns a provider QR image; the client renders its own QR from `copyPaste`, so
|
|
52
|
+
* no image URL is needed here.
|
|
53
|
+
*/
|
|
54
|
+
export interface PixCharge {
|
|
55
|
+
copyPaste: string;
|
|
56
|
+
/** ISO-8601 instant when the charge expires. */
|
|
57
|
+
expiresAt: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One discount as it was applied to an order (FUT-235), frozen at order time so
|
|
62
|
+
* it still reads correctly after the promotion is renamed, repriced or deleted.
|
|
63
|
+
*/
|
|
64
|
+
export interface CheckoutDiscount {
|
|
65
|
+
/** The discount's id, or null once the discount itself has been deleted. */
|
|
66
|
+
discountId: string | null;
|
|
67
|
+
/** The merchant's name for the promotion, as it was when it applied. */
|
|
68
|
+
name: string;
|
|
69
|
+
/** The coupon the buyer typed to get it, when it was a coupon. */
|
|
70
|
+
code: string | null;
|
|
71
|
+
/** Cents this discount removed from the order. Always > 0. */
|
|
72
|
+
amountCents: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** An order as seen by the checkout client after creation. */
|
|
76
|
+
export interface CheckoutOrder {
|
|
77
|
+
orderId: string;
|
|
78
|
+
status: OrderStatus;
|
|
79
|
+
method: PaymentMethod;
|
|
80
|
+
/**
|
|
81
|
+
* NET grand total in integer cents — the amount actually charged, and the
|
|
82
|
+
* source of truth for math. Discounts are already deducted.
|
|
83
|
+
*/
|
|
84
|
+
totalCents: number;
|
|
85
|
+
/**
|
|
86
|
+
* GROSS total before discounts, or null when the discount engine never ran
|
|
87
|
+
* for this order (every order predating FUT-235, and the comanda path): the
|
|
88
|
+
* subtotal then simply IS {@link totalCents}.
|
|
89
|
+
*/
|
|
90
|
+
subtotalCents: number | null;
|
|
91
|
+
/** Σ of every applied discount, in integer cents. 0 when none applied. */
|
|
92
|
+
discountTotalCents: number;
|
|
93
|
+
/** The discounts that fired, so the client can itemize the saving. */
|
|
94
|
+
appliedDiscounts: CheckoutDiscount[];
|
|
95
|
+
/** Pre-formatted BRL grand total (e.g. `R$ 42,90`). */
|
|
96
|
+
totalLabel: string;
|
|
97
|
+
/** Present only when {@link method} is `"PIX"`. */
|
|
98
|
+
pix?: PixCharge;
|
|
99
|
+
/**
|
|
100
|
+
* Where to send the buyer when the store's provider settles on its own page
|
|
101
|
+
* (FUT-556) — InfinitePay mints a checkout link rather than a PIX payload or
|
|
102
|
+
* a tokenizable card form. Present for a redirect provider only, and for
|
|
103
|
+
* either method: the provider's page offers its own.
|
|
104
|
+
*
|
|
105
|
+
* Not persisted. The link belongs to one raised charge, so it is answered
|
|
106
|
+
* from that charge's snapshot and never read back from the order row.
|
|
107
|
+
*/
|
|
108
|
+
hostedCheckoutUrl?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Comanda settlement context for the checkout (FUT-comandas): the scope plus
|
|
113
|
+
* the host-resolved totals shown in place of the cart total when settling a
|
|
114
|
+
* comanda. HOW a comanda is resolved is the host's business; the flow only
|
|
115
|
+
* renders the answer.
|
|
116
|
+
*/
|
|
117
|
+
export interface ComandaCheckout {
|
|
118
|
+
scope: "MINE" | "TABLE";
|
|
119
|
+
totalLabel: string;
|
|
120
|
+
totalItems: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A buyer input field the checkout can highlight on a validation failure. */
|
|
124
|
+
export type BuyerField = "cpf" | "email" | "name" | "phone";
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Structured, machine-readable checkout failure. The backend always returns
|
|
128
|
+
* these three fields (never a bare string) so the UI can react on `code`/`field`
|
|
129
|
+
* — highlight the offending input, disable payment when the gateway is down —
|
|
130
|
+
* without parsing a human message.
|
|
131
|
+
*/
|
|
132
|
+
export interface CheckoutError {
|
|
133
|
+
/** Stable machine code, e.g. `EMAIL_EQUALS_MERCHANT`, `GATEWAY_UNAVAILABLE`. */
|
|
134
|
+
code: string;
|
|
135
|
+
/** User-safe PT-BR message for display. */
|
|
136
|
+
message: string;
|
|
137
|
+
/** Which buyer field to highlight, or `null` when the failure isn't field-scoped. */
|
|
138
|
+
field: BuyerField | null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Result of the host's `createOrder` port: the order, or a structured failure. */
|
|
142
|
+
export type CreateOrderResult =
|
|
143
|
+
| { ok: true; data: CheckoutOrder }
|
|
144
|
+
| { ok: false; error: CheckoutError };
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* What the flow hands the host's `createOrder` port. Everything else an order
|
|
148
|
+
* needs — WHICH cart, WHICH comanda scope, WHICH tenant — is the host's own
|
|
149
|
+
* context, closed over by its port implementation.
|
|
150
|
+
*/
|
|
151
|
+
export interface CreateOrderRequest {
|
|
152
|
+
method: PaymentMethod;
|
|
153
|
+
buyer: BuyerInfo;
|
|
154
|
+
/** Opt-in: save buyer name/phone (not CPF — never persisted here) for next-checkout pre-fill. */
|
|
155
|
+
saveProfile: boolean;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** What the flow hands the host's `saveBuyerContact` port on "Continuar". */
|
|
159
|
+
export interface BuyerContact {
|
|
160
|
+
name?: string;
|
|
161
|
+
phone?: string;
|
|
162
|
+
taxId?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Display metadata needed to persist a card for reuse (never includes the PAN). */
|
|
166
|
+
export interface SavedCardMeta {
|
|
167
|
+
brand: string;
|
|
168
|
+
last4: string;
|
|
169
|
+
expMonth: number;
|
|
170
|
+
expYear: number;
|
|
171
|
+
holder: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Input to charge a tokenized card against an order. */
|
|
175
|
+
export interface ChargeCardInput {
|
|
176
|
+
orderId: string;
|
|
177
|
+
/** A fresh token, or a saved card's id for reuse. */
|
|
178
|
+
token: string;
|
|
179
|
+
/** Persist the token for future purchases (backend saves it against the buyer). */
|
|
180
|
+
saveCard: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Display metadata for the card being saved. Sent only when {@link saveCard} is
|
|
183
|
+
* true and a fresh card is used — the server persists it alongside the encrypted
|
|
184
|
+
* reusable token. Derived in the browser (the PAN never leaves it).
|
|
185
|
+
*/
|
|
186
|
+
cardMeta?: SavedCardMeta;
|
|
187
|
+
/** Buyer CPF for the provider charge (`customer.tax_id`); never persisted. */
|
|
188
|
+
taxId?: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The store's active payment protocol, as `GET /api/checkout/config` answers
|
|
193
|
+
* it (FUT-697). Extends the tokenization triple the card path consumes with
|
|
194
|
+
* `tokenization`, which the method picker reads (`REDIRECT` ⇒ the provider's
|
|
195
|
+
* hosted page takes the card, so no in-browser card form is needed).
|
|
196
|
+
*/
|
|
197
|
+
export interface CheckoutProviderConfig {
|
|
198
|
+
/** Active provider's name, or `null` when the store has none connected. */
|
|
199
|
+
provider: string | null;
|
|
200
|
+
/** How the browser produces a card instrument (adapter vocabulary). */
|
|
201
|
+
tokenization: "NONE" | "PUBLIC_KEY" | "SDK" | "REDIRECT" | null;
|
|
202
|
+
/** The provider's PUBLIC browser key, when it has one. */
|
|
203
|
+
publicKey: string | null;
|
|
204
|
+
/** Server-granted stub-mode permission — the ONLY license to mock-tokenize. */
|
|
205
|
+
mockTokenization: boolean;
|
|
206
|
+
/**
|
|
207
|
+
* The methods the store's provider chain declares it can charge (FUT-698) —
|
|
208
|
+
* the picker derives its options from this, so a provider that cannot PIX
|
|
209
|
+
* never offers PIX. Empty when no provider is connected.
|
|
210
|
+
*/
|
|
211
|
+
methods: ("PIX" | "CARD" | "BOLETO")[];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* What `POST /api/checkout/charge` answers (FUT-698). `hostedCheckoutUrl` is
|
|
216
|
+
* present when the provider demands the buyer finish the charge on ITS page —
|
|
217
|
+
* Stripe's redirect-based 3-D Secure — and the client then hands the buyer
|
|
218
|
+
* over exactly as it does for a redirect provider's link (FUT-556).
|
|
219
|
+
*/
|
|
220
|
+
export interface ChargeOutcome {
|
|
221
|
+
status: OrderStatus;
|
|
222
|
+
hostedCheckoutUrl?: string;
|
|
223
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The checkout's design-system seam (FUT-564, option 3: slot injection).
|
|
3
|
+
*
|
|
4
|
+
* The buyer screens in this folder ship BEHAVIOR and STRUCTURE — steps,
|
|
5
|
+
* validation, polling, tokenization — but render their pixels through this
|
|
6
|
+
* contract: a small set of primitive slots the host fills with its own design
|
|
7
|
+
* system. Unfilled slots fall back to raw MUI (`defaultCheckoutComponents`),
|
|
8
|
+
* which is why the package's peers are `@mui/material` + `@emotion/*` and
|
|
9
|
+
* nothing else — a host with no component library still gets a working, plain
|
|
10
|
+
* checkout, and a host with one (this repo fills these with `@12-apps/ui`) gets
|
|
11
|
+
* its own look without the package ever importing it.
|
|
12
|
+
*
|
|
13
|
+
* Each slot's props are the SUBSET the checkout actually uses — deliberately
|
|
14
|
+
* narrow so any design system can satisfy them with a thin (often identity)
|
|
15
|
+
* mapping. The contract is documented for adopters in
|
|
16
|
+
* `packages/payments/ADOPTING.md` §3.
|
|
17
|
+
*/
|
|
18
|
+
import { createContext, useContext, useMemo, type ChangeEvent, type ComponentType, type CSSProperties, type JSX, type ReactNode } from 'react';
|
|
19
|
+
|
|
20
|
+
import { defaultCheckoutComponents } from './mui-defaults';
|
|
21
|
+
|
|
22
|
+
/** Typography. `variant`/`size`/`weight`/`color` are semantic, never raw CSS. */
|
|
23
|
+
export interface CheckoutTextProps {
|
|
24
|
+
variant?: 'body' | 'heading' | 'caption' | 'code';
|
|
25
|
+
size?: 'xs' | 'sm' | 'md';
|
|
26
|
+
weight?: 'semibold' | 'bold';
|
|
27
|
+
color?: 'primary' | 'secondary' | 'success' | 'danger';
|
|
28
|
+
as?: 'p' | 'span' | 'h2';
|
|
29
|
+
style?: CSSProperties;
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
'data-testid'?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The checkout's actions — `dataTestId` must land on the button element. */
|
|
35
|
+
export interface CheckoutButtonProps {
|
|
36
|
+
variant?: 'solid' | 'outline' | 'text';
|
|
37
|
+
color?: 'primary' | 'neutral';
|
|
38
|
+
size?: 'sm' | 'md' | 'lg';
|
|
39
|
+
fullWidth?: boolean;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
loading?: boolean;
|
|
42
|
+
icon?: ReactNode;
|
|
43
|
+
iconPosition?: 'left' | 'right';
|
|
44
|
+
onClick?: () => void;
|
|
45
|
+
dataTestId?: string;
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Text field. `data-testid` must land on the `<input>` itself (e2e fills it). */
|
|
50
|
+
export interface CheckoutInputProps {
|
|
51
|
+
label?: string;
|
|
52
|
+
type?: 'text' | 'email' | 'tel';
|
|
53
|
+
inputMode?: 'numeric';
|
|
54
|
+
variant?: 'outlined';
|
|
55
|
+
size?: 'md';
|
|
56
|
+
fullWidth?: boolean;
|
|
57
|
+
required?: boolean;
|
|
58
|
+
autoComplete?: string;
|
|
59
|
+
placeholder?: string;
|
|
60
|
+
maxLength?: number;
|
|
61
|
+
value: string;
|
|
62
|
+
error?: boolean;
|
|
63
|
+
helperText?: string;
|
|
64
|
+
endAdornment?: ReactNode;
|
|
65
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
66
|
+
onBlur?: () => void;
|
|
67
|
+
'data-testid'?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CheckoutCheckboxProps {
|
|
71
|
+
checked: boolean;
|
|
72
|
+
onChange?: (event: ChangeEvent<HTMLElement>, checked: boolean) => void;
|
|
73
|
+
label?: string;
|
|
74
|
+
'data-testid'?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CheckoutAlertProps {
|
|
78
|
+
variant?: 'info' | 'warning' | 'danger';
|
|
79
|
+
title?: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
showIcon?: boolean;
|
|
82
|
+
'data-testid'?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface CheckoutLoadingStateProps {
|
|
86
|
+
variant?: 'spinner';
|
|
87
|
+
message?: string;
|
|
88
|
+
size?: 'md';
|
|
89
|
+
dataTestId?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CheckoutStepperStep {
|
|
93
|
+
id: string;
|
|
94
|
+
label: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface CheckoutStepperProps {
|
|
98
|
+
steps: CheckoutStepperStep[];
|
|
99
|
+
activeId: string;
|
|
100
|
+
completed?: Set<string>;
|
|
101
|
+
orientation?: 'horizontal';
|
|
102
|
+
size?: 'sm';
|
|
103
|
+
'data-testid'?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface CheckoutRadioOption {
|
|
107
|
+
value: string;
|
|
108
|
+
label: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CheckoutRadioGroupProps {
|
|
113
|
+
label?: string;
|
|
114
|
+
value: string;
|
|
115
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, value: string) => void;
|
|
116
|
+
options: CheckoutRadioOption[];
|
|
117
|
+
dataTestId?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The bar that pins the primary CTA to the bottom of the viewport. In this
|
|
122
|
+
* repo's storefront the host fills it with its `StickyActionBar` chrome; the
|
|
123
|
+
* default is a plain sticky footer.
|
|
124
|
+
*/
|
|
125
|
+
export interface CheckoutActionBarProps {
|
|
126
|
+
children: ReactNode;
|
|
127
|
+
dataTestId?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Every primitive the buyer checkout renders through. */
|
|
131
|
+
export interface CheckoutComponents {
|
|
132
|
+
Text: ComponentType<CheckoutTextProps>;
|
|
133
|
+
Button: ComponentType<CheckoutButtonProps>;
|
|
134
|
+
Input: ComponentType<CheckoutInputProps>;
|
|
135
|
+
Checkbox: ComponentType<CheckoutCheckboxProps>;
|
|
136
|
+
Alert: ComponentType<CheckoutAlertProps>;
|
|
137
|
+
LoadingState: ComponentType<CheckoutLoadingStateProps>;
|
|
138
|
+
Stepper: ComponentType<CheckoutStepperProps>;
|
|
139
|
+
RadioGroup: ComponentType<CheckoutRadioGroupProps>;
|
|
140
|
+
ActionBar: ComponentType<CheckoutActionBarProps>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const CheckoutComponentsContext = createContext<CheckoutComponents>(defaultCheckoutComponents);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Fills the checkout's component slots for everything rendered beneath it.
|
|
147
|
+
* Partial on purpose: a host overrides only the slots its design system
|
|
148
|
+
* covers and inherits the raw-MUI default for the rest.
|
|
149
|
+
*/
|
|
150
|
+
export function CheckoutComponentsProvider({
|
|
151
|
+
components,
|
|
152
|
+
children,
|
|
153
|
+
}: {
|
|
154
|
+
components?: Partial<CheckoutComponents>;
|
|
155
|
+
children: ReactNode;
|
|
156
|
+
}): JSX.Element {
|
|
157
|
+
const value = useMemo<CheckoutComponents>(
|
|
158
|
+
() => ({ ...defaultCheckoutComponents, ...components }),
|
|
159
|
+
[components],
|
|
160
|
+
);
|
|
161
|
+
return (
|
|
162
|
+
<CheckoutComponentsContext.Provider value={value}>
|
|
163
|
+
{children}
|
|
164
|
+
</CheckoutComponentsContext.Provider>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The filled slot set — raw MUI unless a provider above overrode a slot. */
|
|
169
|
+
export function useCheckoutComponents(): CheckoutComponents {
|
|
170
|
+
return useContext(CheckoutComponentsContext);
|
|
171
|
+
}
|