@12-apps/payments-frontend 3.43.1 → 3.45.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/package.json +2 -2
  2. package/src/components/checkout/apple-pay-button.tsx +84 -36
  3. package/src/components/checkout/checkout-flow.tsx +3 -2
  4. package/src/components/checkout/google-pay-button.tsx +6 -0
  5. package/src/components/checkout/list/buyer-inline.tsx +22 -14
  6. package/src/components/checkout/list/charge-runners.tsx +47 -23
  7. package/src/components/checkout/list/choice-list.tsx +25 -7
  8. package/src/components/checkout/list/choice.ts +4 -6
  9. package/src/components/checkout/list/cpf-top.tsx +98 -0
  10. package/src/components/checkout/list/en-US.ts +8 -0
  11. package/src/components/checkout/list/list-bar.tsx +29 -44
  12. package/src/components/checkout/list/list-face.tsx +110 -104
  13. package/src/components/checkout/list/list-top.tsx +99 -0
  14. package/src/components/checkout/list/online-content.tsx +4 -0
  15. package/src/components/checkout/list/option-row.tsx +55 -32
  16. package/src/components/checkout/list/payer-line.tsx +39 -0
  17. package/src/components/checkout/list/payment-list-step.tsx +50 -5
  18. package/src/components/checkout/list/pt-BR.ts +8 -0
  19. package/src/components/checkout/list/summary-column.tsx +120 -0
  20. package/src/components/checkout/list/top-cpf.ts +94 -0
  21. package/src/components/checkout/list/types.ts +89 -13
  22. package/src/components/checkout/list/use-capability.ts +113 -35
  23. package/src/components/checkout/list/use-list-charge.ts +11 -9
  24. package/src/components/checkout/list/use-list-state.ts +37 -4
  25. package/src/components/checkout/list/wallet-block.tsx +235 -0
  26. package/src/components/checkout/list/wallet-press.ts +129 -0
  27. package/src/components/checkout/payment-phase.tsx +1 -0
  28. package/src/components/checkout/payment-summary.tsx +2 -2
  29. package/src/components/checkout/screens-copy.ts +6 -0
  30. package/src/components/checkout/screens-en-US.ts +1 -0
  31. package/src/components/checkout/screens-pt-BR.ts +2 -0
  32. package/src/flows/create-payment-flows.tsx +11 -2
  33. package/src/flows/types.ts +7 -2
  34. package/src/index.ts +1 -1
@@ -0,0 +1,235 @@
1
+ import { Box, Divider } from "@mui/material";
2
+ import { useTheme, type Theme } from "@mui/material/styles";
3
+ import { useEffect, useId, useRef, type JSX } from "react";
4
+
5
+ import { ChargeFailure } from "../card-view";
6
+ import { useCheckoutCopy } from "../copy-context";
7
+ import { UNRESOLVED_CODE } from "../failure-codes";
8
+ import type { GooglePaymentsClient } from "../google-pay-button";
9
+ import { useCheckoutComponents } from "../ui";
10
+
11
+ import type { ListChargeFailure } from "./charge-runners";
12
+ import { GroupHeading } from "./option-row";
13
+ import type { PaymentListCopy } from "./types";
14
+ import type { ListCapability, ListWallet } from "./use-capability";
15
+
16
+ /** The prototype's 44px wallet button (L3), on the theme's spacing scale. */
17
+ const WALLET_HEIGHT = 5.5;
18
+
19
+ /** Why the buttons are held, as each button carries it: disabled, and described by the reason. */
20
+ interface Held {
21
+ /** The reason's element id, or `null` when nothing holds them. */
22
+ describedBy: string | null;
23
+ }
24
+
25
+ /**
26
+ * The pill's edge: none on a light page (the black pill is its own edge); a
27
+ * hairline at night, where black on a dark surface has none; and a visible
28
+ * one while held, when the pill is dimmed (FUT-3685 review).
29
+ */
30
+ const pillEdge = (held: boolean) => (t: Theme): string => {
31
+ if (held) return `1px solid ${t.palette.text.secondary}`;
32
+ return t.palette.mode === "dark" ? `1px solid ${t.palette.divider}` : "1px solid transparent";
33
+ };
34
+
35
+ /**
36
+ * The look every wallet pill shares — the prototype's 44px black pill (L3).
37
+ * The edge is an OUTLINE drawn just inside the pill: it paints over Google's
38
+ * own element and moves nothing, so both pills keep one size and their labels
39
+ * one baseline.
40
+ */
41
+ const pill = (held: boolean) => ({
42
+ height: (t: Theme) => t.spacing(WALLET_HEIGHT),
43
+ width: "100%",
44
+ border: 0,
45
+ outline: pillEdge(held),
46
+ outlineOffset: "-1px",
47
+ borderRadius: (t: Theme) => t.spacing(WALLET_HEIGHT / 2),
48
+ overflow: "hidden",
49
+ } as const);
50
+
51
+ /**
52
+ * Google Pay's own button (FUT-471's brand rule: the buyer presses Google's
53
+ * button, drawn by `createButton`, never a look-alike) — black, "pay", a pill.
54
+ * Its press opens the sheet SYNCHRONOUSLY, inside the tap, through `onPress`.
55
+ * Held, Google's element is marked disabled and described by the reason —
56
+ * it stays in the accessibility tree — and a press does nothing.
57
+ */
58
+ function GoogleWalletButton({ client, onPress, held }: { client: GooglePaymentsClient; onPress: () => void; held: Held }): JSX.Element {
59
+ const mount = useRef<HTMLDivElement | null>(null);
60
+ const latest = useRef(onPress);
61
+ latest.current = onPress;
62
+ const locale = useCheckoutCopy().screens.wallet.googlePay.buttonLocale;
63
+ const theme = useTheme();
64
+ // Google's own parameter, in CSS pixels: half the button's height is a pill.
65
+ const radius = Number.parseFloat(theme.spacing(WALLET_HEIGHT / 2));
66
+ useEffect(() => {
67
+ const element = mount.current;
68
+ if (!element) return undefined;
69
+ element.replaceChildren(
70
+ client.createButton({
71
+ onClick: () => latest.current(),
72
+ buttonSizeMode: "fill",
73
+ buttonLocale: locale,
74
+ buttonColor: "black",
75
+ buttonType: "pay",
76
+ buttonRadius: radius,
77
+ }),
78
+ );
79
+ return () => element.replaceChildren();
80
+ }, [client, locale, radius]);
81
+ useEffect(() => {
82
+ const button = mount.current?.querySelector("button");
83
+ if (!button) return;
84
+ if (held.describedBy) {
85
+ button.setAttribute("aria-disabled", "true");
86
+ button.setAttribute("aria-describedby", held.describedBy);
87
+ } else {
88
+ button.removeAttribute("aria-disabled");
89
+ button.removeAttribute("aria-describedby");
90
+ }
91
+ });
92
+ const isHeld = held.describedBy !== null;
93
+ return (
94
+ <Box
95
+ ref={mount}
96
+ data-testid="list-google-pay"
97
+ data-held={isHeld ? "true" : undefined}
98
+ sx={{ ...pill(isHeld), pointerEvents: isHeld ? "none" : undefined, "& > *": { width: "100%", height: "100%" } }}
99
+ />
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Apple's button: the `-apple-pay-button` appearance Safari draws in the
105
+ * buyer's own language (Apple's Human Interface Guidelines), black, "pay".
106
+ * Only a browser that has an `ApplePaySession` ever renders it; one that does
107
+ * not know the appearance (a test browser with a stubbed session) gets the
108
+ * accessible name drawn as text on the same black pill instead.
109
+ */
110
+ function AppleWalletButton({ label, onPress, held }: { label: string; onPress: () => void; held: Held }): JSX.Element {
111
+ const isHeld = held.describedBy !== null;
112
+ return (
113
+ <Box
114
+ component="button"
115
+ type="button"
116
+ aria-label={label}
117
+ disabled={isHeld}
118
+ aria-describedby={held.describedBy ?? undefined}
119
+ onClick={onPress}
120
+ data-testid="list-apple-pay"
121
+ sx={{
122
+ WebkitAppearance: "-apple-pay-button",
123
+ "-apple-pay-button-type": "pay",
124
+ "-apple-pay-button-style": "black",
125
+ ...pill(isHeld),
126
+ cursor: isHeld ? "default" : "pointer",
127
+ bgcolor: "common.black",
128
+ color: "common.white",
129
+ typography: "body1",
130
+ fontWeight: 600,
131
+ "&:focus-visible": { outline: (t) => `${t.spacing(0.25)} solid ${t.palette.primary.main}`, outlineOffset: (t) => t.spacing(0.25) },
132
+ "@supports (-webkit-appearance: -apple-pay-button)": { "& > span": { display: "none" } },
133
+ }}
134
+ >
135
+ <span aria-hidden>{label}</span>
136
+ </Box>
137
+ );
138
+ }
139
+
140
+ /** One wallet's button, or a held pill while the host re-prices it. */
141
+ function WalletButton({ wallet, cap, copy, onGoogle, onApple, held }: {
142
+ wallet: ListWallet;
143
+ cap: ListCapability;
144
+ copy: PaymentListCopy;
145
+ onGoogle: () => void;
146
+ onApple: () => void;
147
+ held: Held;
148
+ }): JSX.Element {
149
+ if (wallet === "GOOGLE_PAY" && cap.googlePay) return <GoogleWalletButton client={cap.googlePay.client} onPress={onGoogle} held={held} />;
150
+ if (wallet === "APPLE_PAY") return <AppleWalletButton label={copy.wallets.applePay} onPress={onApple} held={held} />;
151
+ // Google while the host re-prices: no sheet can open, so no Google button either — a plain held pill.
152
+ return <HeldWalletButton label={copy.wallets.googlePay} held={held} />;
153
+ }
154
+
155
+ /** A held wallet while the host re-prices: the pill, disabled, named, described by the reason. */
156
+ function HeldWalletButton({ label, held }: { label: string; held: Held }): JSX.Element {
157
+ return (
158
+ <Box
159
+ component="button"
160
+ type="button"
161
+ disabled
162
+ aria-describedby={held.describedBy ?? undefined}
163
+ data-testid="list-wallet-held"
164
+ sx={{ ...pill(true), bgcolor: "common.black", color: "common.white", typography: "body1", fontWeight: 600 }}
165
+ >
166
+ {label}
167
+ </Box>
168
+ );
169
+ }
170
+
171
+ /**
172
+ * "PAGAMENTO RÁPIDO" (FUT-3685, C5, C6, E4, E5, L3, L5): the ready wallets'
173
+ * buttons above the list, then "ou escolha como pagar". Nothing at all — no
174
+ * heading, no buttons, no divider — when no wallet is ready (E4); one wallet
175
+ * takes the full width (E5); two sit side by side from the theme's `sm`
176
+ * (L5) and stack below it (L3).
177
+ *
178
+ * The buttons are not part of the list's radio group (R1): pressing one opens
179
+ * its sheet and never changes the chosen row. `held` is why they cannot act
180
+ * yet — the CPF the provider needs (E3), the host re-pricing — said under them.
181
+ */
182
+ export function WalletBlock({ cap, copy, held, failure, onGoogle, onApple }: {
183
+ cap: ListCapability;
184
+ copy: PaymentListCopy;
185
+ held: string | null;
186
+ /** The last wallet charge's refusal; an UNRESOLVED one removes the buttons (FUT-563). */
187
+ failure: ListChargeFailure | null;
188
+ onGoogle: () => void;
189
+ onApple: () => void;
190
+ }): JSX.Element | null {
191
+ const { Text } = useCheckoutComponents();
192
+ const id = useId();
193
+ const reasonId = `${id}-held`;
194
+ if (cap.wallets.length === 0) return null;
195
+ const two = cap.wallets.length > 1;
196
+ const unresolved = failure?.code === UNRESOLVED_CODE;
197
+ return (
198
+ <Box data-testid="list-wallets" sx={{ display: "flex", flexDirection: "column", gap: 1.25 }}>
199
+ <Box role="group" aria-labelledby={id} sx={{ display: "flex", flexDirection: "column", gap: 1.25 }}>
200
+ <GroupHeading id={id}>{copy.wallets.heading}</GroupHeading>
201
+ {unresolved ? null : (
202
+ <Box
203
+ data-testid="list-wallet-buttons"
204
+ data-held={held ? "true" : undefined}
205
+ sx={{
206
+ display: "grid",
207
+ gap: 1.25,
208
+ gridTemplateColumns: { xs: "minmax(0, 1fr)", sm: two ? "repeat(2, minmax(0, 1fr))" : "minmax(0, 1fr)" },
209
+ opacity: (t) => (held ? t.palette.action.disabledOpacity : 1),
210
+ }}
211
+ >
212
+ {cap.wallets.map((wallet) => (
213
+ <WalletButton key={wallet} wallet={wallet} cap={cap} copy={copy} onGoogle={onGoogle} onApple={onApple} held={{ describedBy: held ? reasonId : null }} />
214
+ ))}
215
+ </Box>
216
+ )}
217
+ {held ? (
218
+ // The Text slot takes no id; the reason's own box carries the one the buttons point at.
219
+ <Box id={reasonId} data-testid="list-wallets-held">
220
+ <Text variant="body" size="sm" color="secondary" as="p" style={{ margin: 0 }}>
221
+ {held}
222
+ </Text>
223
+ </Box>
224
+ ) : null}
225
+ {failure ? <ChargeFailure message={failure.message} unresolved={unresolved} /> : null}
226
+ </Box>
227
+ <Divider data-testid="list-wallets-or">
228
+ {/* The body style the hints use: a caption tone fell under 4.5:1 on the surface. */}
229
+ <Text variant="body" size="xs" color="secondary" as="span">
230
+ {copy.wallets.or}
231
+ </Text>
232
+ </Divider>
233
+ </Box>
234
+ );
235
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * A wallet button's press (FUT-3685, C5, C6): the wallet's sheet opens HERE,
3
+ * synchronously, inside the buyer's tap — the browser opens Google's sheet and
4
+ * Safari starts an Apple Pay session only from inside one — priced at the
5
+ * host's amount DUE (`totalCents`, K3). The order is raised in parallel by the
6
+ * list's charge, and the runner charges the sheet's key only against an order
7
+ * that asks the provider for the same amount (R4).
8
+ */
9
+ import { useEffect, type MutableRefObject } from "react";
10
+
11
+ import { startApplePaySession } from "../apple-pay-button";
12
+ import { useCheckoutCopy } from "../copy-context";
13
+ import { paymentDataRequest, sheetDismissed } from "../google-pay-button";
14
+
15
+ import type { ListChargeFailure, WalletSheet } from "./charge-runners";
16
+ import type { ListCapability } from "./use-capability";
17
+ import type { ListCharge } from "./use-list-charge";
18
+
19
+ /** What a press needs from the list face. */
20
+ export interface WalletPressInput {
21
+ cap: ListCapability;
22
+ charge: ListCharge;
23
+ /** The host's amount due, in cents — what the sheet shows. */
24
+ cents: number | undefined;
25
+ /** The amount the bar names for an online payment, frozen at the press. */
26
+ total: string;
27
+ /** Something stops a press: a reason the block shows, or a charge already under way. */
28
+ blocked: boolean;
29
+ /** Before the sheet opens: clear the last refusal, keep the asked details. */
30
+ before: () => void;
31
+ /** A sheet that could not open, in the wallet's own words. */
32
+ onFailed: (failure: ListChargeFailure) => void;
33
+ /**
34
+ * Set synchronously by a press, cleared when the charge is back to idle: a
35
+ * second tap before React re-renders (a double tap) opens no second sheet.
36
+ */
37
+ pressed: MutableRefObject<boolean>;
38
+ }
39
+
40
+ /**
41
+ * Open a wallet's sheet, synchronously. A sheet that throws instead — Safari's
42
+ * `InvalidAccessError` for a session it will not begin, a pay.js that throws
43
+ * outright — starts no charge, so nothing would ever clear the press guard:
44
+ * both buttons would be dead (FUT-3685 review). So the guard is released, and
45
+ * the buyer is told the wallet could not start; the next press tries again.
46
+ */
47
+ function openSheet<T>(input: WalletPressInput, open: () => T, cannotStart: string): T | null {
48
+ try {
49
+ return open();
50
+ } catch {
51
+ input.pressed.current = false;
52
+ input.onFailed({ message: cannotStart, code: null });
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /** Google Pay's press: `loadPaymentData` inside the tap, then the charge. */
58
+ export function useGooglePayPress(input: WalletPressInput, sheet: MutableRefObject<WalletSheet | null>): () => void {
59
+ const cannotStart = useCheckoutCopy().screens.wallet.googlePay.cannotComplete;
60
+ return () => {
61
+ const google = input.cap.googlePay;
62
+ const cents = input.cents;
63
+ if (!google || cents === undefined || input.blocked || input.pressed.current) return;
64
+ input.pressed.current = true;
65
+ input.before();
66
+ const loading = openSheet(input, () => google.client.loadPaymentData(paymentDataRequest(google.params, { totalCents: cents })), cannotStart);
67
+ if (!loading) return;
68
+ // A sheet that rejects later reaches the runner, which says so and settles the charge.
69
+ const answer = loading.then(
70
+ (data) => data.paymentMethodData.tokenizationData.token,
71
+ (error: unknown) => {
72
+ if (sheetDismissed(error)) return null;
73
+ throw error;
74
+ },
75
+ );
76
+ sheet.current = { type: "GOOGLE_PAY", answer, cents };
77
+ input.charge.start("GOOGLE_PAY", input.total);
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Apple Pay's press: the session is CREATED and BEGUN synchronously, in the
83
+ * tap — Safari's rule — and the order is raised while the buyer looks at the
84
+ * sheet. A merchant validation that cannot run says why, beside the list.
85
+ */
86
+ export function useApplePayPress(input: WalletPressInput, sheet: MutableRefObject<WalletSheet | null>): () => void {
87
+ const copy = useCheckoutCopy().screens.wallet.applePay;
88
+ return () => {
89
+ const apple = input.cap.applePay;
90
+ const cents = input.cents;
91
+ if (!apple || cents === undefined || input.blocked || input.pressed.current) return;
92
+ input.pressed.current = true;
93
+ input.before();
94
+ const started = openSheet(
95
+ input,
96
+ () =>
97
+ startApplePaySession({
98
+ Session: apple.Session,
99
+ cents,
100
+ copy,
101
+ validateMerchant: apple.validateMerchant,
102
+ onError: (message) => input.onFailed({ message, code: null }),
103
+ }),
104
+ copy.cannotComplete,
105
+ );
106
+ if (!started) return;
107
+ sheet.current = { type: "APPLE_PAY", answer: started.answer, cents, complete: started.complete };
108
+ input.charge.start("APPLE_PAY", input.total);
109
+ };
110
+ }
111
+
112
+ /**
113
+ * A sheet left open with nobody to charge it: the raise behind it was refused,
114
+ * and the list is back to its rows. Apple's sheet waits for an outcome, so it
115
+ * is told this one failed; nothing is charged.
116
+ */
117
+ export function useCloseOrphanSheet(phase: ListCharge["phase"], sheet: MutableRefObject<WalletSheet | null>, pressed: MutableRefObject<boolean>): void {
118
+ useEffect(() => {
119
+ if (phase !== "idle") return;
120
+ // Back to idle: a new press may open a sheet again.
121
+ pressed.current = false;
122
+ const open = sheet.current;
123
+ sheet.current = null;
124
+ if (!open || open.claimed) return;
125
+ void open.answer.then((key) => {
126
+ if (key !== null) open.complete?.(false);
127
+ }, () => undefined);
128
+ }, [phase, sheet, pressed]);
129
+ }
@@ -101,6 +101,7 @@ export function PaymentPhase({
101
101
  defaultBuyer={props.defaultBuyer}
102
102
  saveBuyerContact={props.saveBuyerContact}
103
103
  consentLabel={props.copy.dados.saveProfile}
104
+ validateApplePayMerchant={validateApplePayMerchant}
104
105
  />
105
106
  );
106
107
  }
@@ -212,7 +212,7 @@ function AddressRow({
212
212
  const DIVIDER = <Box sx={{ height: "1px", bgcolor: "divider" }} />;
213
213
 
214
214
  /** The cart's lines, "2× X-Burger … R$ 36,00", when the host passed them. */
215
- function SummaryLines({ lines }: { lines: CheckoutSummaryLine[] }): JSX.Element {
215
+ export function SummaryLines({ lines }: { lines: CheckoutSummaryLine[] }): JSX.Element {
216
216
  const { Text } = useCheckoutComponents();
217
217
  const copy = useCheckoutCopy().screens.layout;
218
218
  return (
@@ -232,7 +232,7 @@ function SummaryLines({ lines }: { lines: CheckoutSummaryLine[] }): JSX.Element
232
232
  }
233
233
 
234
234
  /** "Total" and the amount on one line, the host's money rows under it. */
235
- function SummaryTotal({ totalLabel, discountLines }: { totalLabel: string; discountLines?: ReactNode }): JSX.Element {
235
+ export function SummaryTotal({ totalLabel, discountLines }: { totalLabel: string; discountLines?: ReactNode }): JSX.Element {
236
236
  const { Text } = useCheckoutComponents();
237
237
  const copy = useCheckoutCopy().screens.layout;
238
238
  return (
@@ -234,6 +234,12 @@ export interface WalletCopy {
234
234
  cannotStart: string;
235
235
  /** It started and failed. */
236
236
  cannotComplete: string;
237
+ /**
238
+ * The sheet was authorized and the payment did not go through — the
239
+ * amount moved, or the charge was refused (FUT-3685). Absent ⇒
240
+ * `cannotComplete`. Both packs carry it.
241
+ */
242
+ paymentFailed?: string;
237
243
  payAction: string;
238
244
  };
239
245
  googlePay: {
@@ -124,6 +124,7 @@ export const EN_US_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
124
124
  orderTotal: 'Order total',
125
125
  cannotStart: 'Could not start Apple Pay in this store. Pay by card instead.',
126
126
  cannotComplete: 'Could not start Apple Pay. Try again, or pay by card.',
127
+ paymentFailed: 'Could not complete the payment with Apple Pay. Try again, or pay by card.',
127
128
  payAction: 'Pay with Apple Pay',
128
129
  },
129
130
  googlePay: {
@@ -113,6 +113,8 @@ export const PT_BR_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
113
113
  cannotStart: 'Não foi possível iniciar o Apple Pay nesta loja. Pague com cartão.',
114
114
  cannotComplete:
115
115
  'Não foi possível iniciar o Apple Pay. Tente novamente ou pague com cartão.',
116
+ paymentFailed:
117
+ 'Não foi possível concluir o pagamento com o Apple Pay. Tente novamente ou pague com cartão.',
116
118
  payAction: 'Pagar com Apple Pay',
117
119
  },
118
120
  googlePay: {
@@ -68,6 +68,16 @@ function buildRuntime(config: PaymentFlowsConfig): FlowsRuntime {
68
68
  };
69
69
  }
70
70
 
71
+ /** The host's confirmation slots, as `CheckoutFlow` takes them. */
72
+ function confirmationProps(confirmation: PaymentFlowsConfig["confirmation"]) {
73
+ return {
74
+ confirmationExtra: confirmation?.extra,
75
+ confirmationHero: confirmation?.hero,
76
+ confirmationFooter: confirmation?.footer,
77
+ backActionEmphasis: confirmation?.backActionEmphasis,
78
+ };
79
+ }
80
+
71
81
  /** The host's one-list Pagamento (FUT-3591), when it opted in — a hook, read every render. */
72
82
  function useHostPaymentList(runtime: FlowsRuntime) {
73
83
  return runtime.config.usePaymentList?.();
@@ -111,8 +121,7 @@ function buildCheckout(
111
121
  settlement={settlement ?? hostSettlement}
112
122
  providerConfig={config}
113
123
  tenantSlug={tenantSlug}
114
- confirmationExtra={runtime.config.confirmation?.extra}
115
- confirmationHero={runtime.config.confirmation?.hero}
124
+ {...confirmationProps(runtime.config.confirmation)}
116
125
  validateApplePayMerchant={ports.validateApplePayMerchant}
117
126
  paymentList={paymentList}
118
127
  />
@@ -20,6 +20,7 @@
20
20
  * a `tokensByProvider` key. A host never types one.
21
21
  */
22
22
  import type { CheckoutPaymentList } from "../components/checkout/list/types";
23
+ import type { BackActionEmphasis } from "../components/checkout/payment-status-parts";
23
24
  import type { ComponentType, ReactNode } from "react";
24
25
 
25
26
  import type { SavedCard } from "../card";
@@ -153,9 +154,13 @@ export interface PaymentFlowsConfig extends CheckoutPipelineConfig {
153
154
  copy: CheckoutCopyFE;
154
155
  /**
155
156
  * Host content under the paid receipt (the storefront's PWA install invite),
156
- * and the host's own illustration in place of the paid icon (`hero`).
157
+ * the host's own illustration in place of the paid icon (`hero`), content
158
+ * AFTER the confirmation's actions (`footer`), and whether the way out leads
159
+ * the paid screen (`backActionEmphasis`) — `CheckoutFlowProps`'
160
+ * `confirmationExtra`/`Hero`/`Footer` and `backActionEmphasis`, so a host on
161
+ * the factory composes the same confirmation as one mounting the flow.
157
162
  */
158
- confirmation?: { extra?: ReactNode; hero?: ReactNode };
163
+ confirmation?: { extra?: ReactNode; hero?: ReactNode; footer?: ReactNode; backActionEmphasis?: BackActionEmphasis };
159
164
  /**
160
165
  * Warnings the host reports (Sentry). Default SILENT — never `console`: a
161
166
  * library that writes to a buyer's console tells them nothing and tells the
package/src/index.ts CHANGED
@@ -56,7 +56,7 @@ export { PT_BR_CHECKOUT_PAYMENT_COPY } from './components/checkout-payment-pt-BR
56
56
  // ---------------------------------------------------------------------------
57
57
  export { CheckoutFlow, type CheckoutCartView, type CheckoutFlowProps } from './components/checkout/checkout-flow';
58
58
  // The Pagamento step as ONE list (FUT-3591/3592) — opt-in via `CheckoutFlowProps.paymentList`.
59
- export type { CheckoutDoorOption, CheckoutListChoice, CheckoutListHold, CheckoutOnlineChoice, CheckoutPaymentList, CheckoutPixBank, PaymentListCopy, PixBankCopy, PixBankId, PixBankName } from './components/checkout/list/types';
59
+ export type { CheckoutDoorOption, CheckoutDoorRowControls, CheckoutListChoice, CheckoutListHold, CheckoutOnlineChoice, CheckoutPaymentList, CheckoutPixBank, PaymentListCopy, PixBankCopy, PixBankId, PixBankName } from './components/checkout/list/types';
60
60
  export { PT_BR_PAYMENT_LIST_COPY } from './components/checkout/list/pt-BR';
61
61
  export type { BackActionEmphasis } from './components/checkout/payment-status-parts';
62
62
  export type { CheckoutDeliverySummary, CheckoutSummaryLine } from './components/checkout/payment-summary';