@12-apps/payments-frontend 1.12.0 → 1.14.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 +4 -4
- package/src/components/checkout/checkout-steps.tsx +29 -24
- package/src/components/checkout/providers/capability-default.tsx +67 -0
- package/src/components/checkout/providers/hosted-link.tsx +52 -0
- package/src/components/checkout/providers/pix-and-card.tsx +50 -0
- package/src/components/checkout/providers/registry.ts +78 -0
- package/src/components/checkout/providers/types.ts +65 -0
- package/src/components/checkout/types.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/payments-frontend",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browser half of the vendor-agnostic payments platform: plug-and-play MUI components for the per-provider settings page (credential form from each provider's schema, masked hints, verify/enable) and the checkout page (PIX QR + polling, card tokenization, hosted-checkout redirect), plus the headless hooks and fetch clients they build on. Talks only to the host's payments HTTP surface — never to a provider directly. Microfrontend-ready: no app coupling, host injects theme and auth.",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"storybook:build": "storybook build"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@12-apps/payments-backend": "^1.
|
|
20
|
+
"@12-apps/payments-backend": "^1.14.0",
|
|
21
21
|
"react-qr-code": "^2.2.0"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"react-dom": ">=19.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@12-apps/eslint-config": "^1.
|
|
32
|
-
"@12-apps/typescript-config": "^1.
|
|
31
|
+
"@12-apps/eslint-config": "^1.16.0",
|
|
32
|
+
"@12-apps/typescript-config": "^1.16.0",
|
|
33
33
|
"@emotion/react": "^11.14.0",
|
|
34
34
|
"@emotion/styled": "^11.14.0",
|
|
35
35
|
"@mui/material": "^6.5.0",
|
|
@@ -2,12 +2,9 @@ import { Box } from "@mui/material";
|
|
|
2
2
|
import { useEffect, useRef, type JSX, type ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import { BuyerInfoForm } from "./buyer-info-form";
|
|
5
|
-
import { CardView } from "./card-view";
|
|
6
5
|
import { LockOutlinedIcon } from "./icons";
|
|
7
6
|
import {
|
|
8
|
-
cardChain,
|
|
9
7
|
cardPathAvailable,
|
|
10
|
-
cardTokenization,
|
|
11
8
|
offeredMethods,
|
|
12
9
|
selectableMethods,
|
|
13
10
|
usePreselectSoleMethod,
|
|
@@ -15,7 +12,7 @@ import {
|
|
|
15
12
|
import { MethodPicker } from "./method-picker";
|
|
16
13
|
import { PaymentErrorPanel } from "./payment-error-panel";
|
|
17
14
|
import { PayerSummary } from "./payer-summary";
|
|
18
|
-
import {
|
|
15
|
+
import { resolveCheckoutScreen } from "./providers/registry";
|
|
19
16
|
import type {
|
|
20
17
|
BuyerField,
|
|
21
18
|
BuyerInfo,
|
|
@@ -53,11 +50,24 @@ function useAutoRaiseOrder(
|
|
|
53
50
|
}, [method, order, creating, createError, onGenerate]);
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The payment body — whichever screen the store's provider declares (FUT-596).
|
|
55
|
+
*
|
|
56
|
+
* This used to switch on `order.method` and compose the PIX and card panes
|
|
57
|
+
* itself. It now resolves a screen from the id the adapter published on the
|
|
58
|
+
* chain's head and renders it; the panes moved to `providers/`, unchanged.
|
|
59
|
+
* The shell keeps everything shared — picker, payer, totals, errors, the
|
|
60
|
+
* polling cadence — so a provider's flow differs only where it genuinely does.
|
|
61
|
+
*
|
|
62
|
+
* `resolveCheckoutScreen` always returns a component, so there is no branch
|
|
63
|
+
* here for "no screen": an undeclared or unrecognised id lands on the
|
|
64
|
+
* capability default.
|
|
65
|
+
*/
|
|
57
66
|
function PaymentBody({
|
|
58
67
|
order,
|
|
59
68
|
buyer,
|
|
60
69
|
providerConfig,
|
|
70
|
+
method,
|
|
61
71
|
tenantSlug,
|
|
62
72
|
onResolved,
|
|
63
73
|
pollIntervalMs,
|
|
@@ -65,29 +75,23 @@ function PaymentBody({
|
|
|
65
75
|
order: CheckoutOrder | null;
|
|
66
76
|
buyer: BuyerInfo;
|
|
67
77
|
providerConfig: CheckoutProviderConfig | null;
|
|
78
|
+
method: PaymentMethod | null;
|
|
68
79
|
tenantSlug?: string;
|
|
69
80
|
onResolved: (status: OrderStatus) => void;
|
|
70
81
|
pollIntervalMs?: number;
|
|
71
82
|
}): JSX.Element | null {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
tenantSlug={tenantSlug}
|
|
85
|
-
onResolved={onResolved}
|
|
86
|
-
pollIntervalMs={pollIntervalMs}
|
|
87
|
-
/>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
return null;
|
|
83
|
+
const Screen = resolveCheckoutScreen(providerConfig?.chain?.[0]?.checkoutScreen);
|
|
84
|
+
return (
|
|
85
|
+
<Screen
|
|
86
|
+
order={order}
|
|
87
|
+
buyer={buyer}
|
|
88
|
+
config={providerConfig}
|
|
89
|
+
method={method}
|
|
90
|
+
tenantSlug={tenantSlug}
|
|
91
|
+
onResolved={onResolved}
|
|
92
|
+
pollIntervalMs={pollIntervalMs}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/** Empty-cart state shown when there's nothing to check out. */
|
|
@@ -334,6 +338,7 @@ export function PaymentStep({
|
|
|
334
338
|
order={order}
|
|
335
339
|
buyer={buyer}
|
|
336
340
|
providerConfig={providerConfig ?? null}
|
|
341
|
+
method={method}
|
|
337
342
|
tenantSlug={tenantSlug}
|
|
338
343
|
onResolved={onResolved}
|
|
339
344
|
pollIntervalMs={pollIntervalMs}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen for a provider that declares none (FUT-596, AC3).
|
|
3
|
+
*
|
|
4
|
+
* This is the guarantee that adding a vendor never leaves a blank pane. It is
|
|
5
|
+
* reached three ways, and all three are normal rather than exceptional:
|
|
6
|
+
*
|
|
7
|
+
* 1. An adapter that has not declared `checkoutScreen` at all — Stone and
|
|
8
|
+
* Stripe today. They check out on the day they are written.
|
|
9
|
+
* 2. A host serving an older `/checkout/config` with no chain, so there is
|
|
10
|
+
* no id to read.
|
|
11
|
+
* 3. An id THIS bundle has never heard of. The backend and frontend packages
|
|
12
|
+
* version independently, so a host running a newer server than bundle is
|
|
13
|
+
* an ordinary deployment state — and the buyer must not pay for it.
|
|
14
|
+
*
|
|
15
|
+
* It does not reimplement a pane. It picks between the same two screens a
|
|
16
|
+
* declaration would have chosen, from the capabilities every adapter already
|
|
17
|
+
* publishes — so the default and the declared path cannot drift apart, and
|
|
18
|
+
* there is exactly one implementation of each shape.
|
|
19
|
+
*/
|
|
20
|
+
import type { JSX } from "react";
|
|
21
|
+
|
|
22
|
+
import type { CheckoutProviderConfig } from "../types";
|
|
23
|
+
|
|
24
|
+
import { HostedLinkScreen } from "./hosted-link";
|
|
25
|
+
import { PixAndCardScreen } from "./pix-and-card";
|
|
26
|
+
import type { ProviderCheckoutScreenProps } from "./types";
|
|
27
|
+
|
|
28
|
+
/** Schemes that give the BROWSER a card form of its own. */
|
|
29
|
+
const IN_BROWSER_TOKENIZATION: ReadonlySet<string> = new Set(["PUBLIC_KEY", "SDK"]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether this store hands the buyer over instead of collecting here — the
|
|
33
|
+
* frontend twin of the server's `usesHostedCheckout`, deliberately written to
|
|
34
|
+
* the same three rules so the pane and the walk cannot disagree.
|
|
35
|
+
*
|
|
36
|
+
* Note this is NOT `!cardPathAvailable(config)`. That helper answers a
|
|
37
|
+
* different question — "is a card offerable at all" — and it answers TRUE for
|
|
38
|
+
* a hand-off store, because typing the card on the provider's page is still a
|
|
39
|
+
* card path. Inverting it therefore sends the hosted store to the on-page
|
|
40
|
+
* screen and the on-page store to the hand-off, which is exactly backwards.
|
|
41
|
+
*
|
|
42
|
+
* The rules, in order:
|
|
43
|
+
* - Only CARD can be answered in advance. TOKENIZATION IS A CARD FACT: it
|
|
44
|
+
* says how the browser turns a PAN into an instrument, and a PIX charge
|
|
45
|
+
* has no instrument to mint. A store with no card-capable entry is not
|
|
46
|
+
* hosted — this is the FUT-747 correction, and getting it wrong routed the
|
|
47
|
+
* simplest store there is (one PIX-only provider honestly declaring
|
|
48
|
+
* `NONE`) into a hand-off it had no link for.
|
|
49
|
+
* - Hosted only when NOBODY who takes a card takes it here.
|
|
50
|
+
* - No chain served (an older host, a still-loading config) ⇒ not hosted,
|
|
51
|
+
* which is what this checkout did before there was a chain to read.
|
|
52
|
+
*/
|
|
53
|
+
function handsBuyerOver(config: CheckoutProviderConfig | null): boolean {
|
|
54
|
+
const chain = config?.chain;
|
|
55
|
+
if (!chain || chain.length === 0) return false;
|
|
56
|
+
const cardCapable = chain.filter((link) => link.methods.includes("CARD"));
|
|
57
|
+
if (cardCapable.length === 0) return false;
|
|
58
|
+
return !cardCapable.some((link) => IN_BROWSER_TOKENIZATION.has(link.tokenization));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function CapabilityDefaultScreen(props: ProviderCheckoutScreenProps): JSX.Element | null {
|
|
62
|
+
return handsBuyerOver(props.config) ? (
|
|
63
|
+
<HostedLinkScreen {...props} />
|
|
64
|
+
) : (
|
|
65
|
+
<PixAndCardScreen {...props} />
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen for a store whose buyer finishes on the PROVIDER's page (FUT-596).
|
|
3
|
+
*
|
|
4
|
+
* Declared as `hosted-link` by any adapter of that shape — today InfinitePay.
|
|
5
|
+
* There is no card form and no PIX pane here on purpose: this provider takes
|
|
6
|
+
* neither on our page, and rendering either would offer a buyer something the
|
|
7
|
+
* charge cannot honour.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this screen renders no payable
|
|
10
|
+
*
|
|
11
|
+
* For a hand-off provider the pane never receives an `order`, and that is by
|
|
12
|
+
* design rather than an omission. The controller raises the charge, sees a
|
|
13
|
+
* `hostedCheckoutUrl` on the response and navigates — `handOverToProvider`
|
|
14
|
+
* returns before `setOrder` (`use-checkout-controller.ts`), because the buyer
|
|
15
|
+
* is leaving this page and parking the payable for the return leg matters more
|
|
16
|
+
* than painting a pane they will not see. `hosted-return.ts` picks it back up.
|
|
17
|
+
*
|
|
18
|
+
* So the whole job here is the moment BEFORE that: the buyer has chosen a
|
|
19
|
+
* method and the redirect is being prepared. Previously the pane rendered
|
|
20
|
+
* `null` through that moment, which reads as a checkout that has stalled — the
|
|
21
|
+
* screen tells them where they are going instead. Nothing here touches the
|
|
22
|
+
* money path; the navigation and the parking are unchanged.
|
|
23
|
+
*/
|
|
24
|
+
import { Box } from "@mui/material";
|
|
25
|
+
import type { JSX } from "react";
|
|
26
|
+
|
|
27
|
+
import { useCheckoutComponents } from "../ui";
|
|
28
|
+
|
|
29
|
+
import type { ProviderCheckoutScreenProps } from "./types";
|
|
30
|
+
|
|
31
|
+
export function HostedLinkScreen({ method }: ProviderCheckoutScreenProps): JSX.Element | null {
|
|
32
|
+
const { Text, LoadingState } = useCheckoutComponents();
|
|
33
|
+
|
|
34
|
+
// Nothing chosen yet ⇒ the shell is still showing the picker. Same as every
|
|
35
|
+
// other screen: the pane stays out of the way until there is something to say.
|
|
36
|
+
if (!method) return null;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Box
|
|
40
|
+
data-testid="checkout-handoff-pending"
|
|
41
|
+
sx={{ display: "flex", flexDirection: "column", gap: 2, alignItems: "center", py: 4 }}
|
|
42
|
+
>
|
|
43
|
+
<LoadingState variant="spinner" message="Preparando o pagamento" size="md" />
|
|
44
|
+
<Text variant="body" size="md" as="p">
|
|
45
|
+
Você será levado à página segura do provedor para concluir o pagamento.
|
|
46
|
+
</Text>
|
|
47
|
+
<Text variant="caption" size="sm" as="p" color="secondary">
|
|
48
|
+
Assim que terminar, você volta para cá e nós confirmamos o pedido.
|
|
49
|
+
</Text>
|
|
50
|
+
</Box>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The screen for a store that collects payment ON OUR PAGE (FUT-596).
|
|
3
|
+
*
|
|
4
|
+
* A PIX charge renders the provider's code here; a card charge renders our own
|
|
5
|
+
* form and mints the instrument in this browser. Declared by any adapter whose
|
|
6
|
+
* flow is that shape — `pix-and-card` — which today is PagBank and tomorrow is
|
|
7
|
+
* Stone, from one declaration each and no second component.
|
|
8
|
+
*
|
|
9
|
+
* It carries NO hand-off branch. A provider that finishes on its own page
|
|
10
|
+
* declares `hosted-link` and renders `./hosted-link.tsx` instead; the two never
|
|
11
|
+
* test for each other, which is the property FUT-596 exists to buy.
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX } from "react";
|
|
14
|
+
|
|
15
|
+
import { CardView } from "../card-view";
|
|
16
|
+
import { cardChain, cardTokenization } from "../method-capability";
|
|
17
|
+
import { PixView } from "../pix-view";
|
|
18
|
+
|
|
19
|
+
import type { ProviderCheckoutScreenProps } from "./types";
|
|
20
|
+
|
|
21
|
+
export function PixAndCardScreen({
|
|
22
|
+
order,
|
|
23
|
+
buyer,
|
|
24
|
+
config,
|
|
25
|
+
tenantSlug,
|
|
26
|
+
onResolved,
|
|
27
|
+
pollIntervalMs,
|
|
28
|
+
}: ProviderCheckoutScreenProps): JSX.Element | null {
|
|
29
|
+
if (order?.method === "PIX") {
|
|
30
|
+
return <PixView order={order} onResolved={onResolved} pollIntervalMs={pollIntervalMs} />;
|
|
31
|
+
}
|
|
32
|
+
if (order?.method === "CARD") {
|
|
33
|
+
return (
|
|
34
|
+
<CardView
|
|
35
|
+
order={order}
|
|
36
|
+
buyer={buyer}
|
|
37
|
+
providerConfig={cardTokenization(config)}
|
|
38
|
+
// The whole chain (FUT-563): one instrument is minted per provider so
|
|
39
|
+
// the charge survives the first one failing, with nothing re-typed.
|
|
40
|
+
providerChain={cardChain(config)}
|
|
41
|
+
tenantSlug={tenantSlug}
|
|
42
|
+
onResolved={onResolved}
|
|
43
|
+
pollIntervalMs={pollIntervalMs}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
// No order yet — the shell is still showing the picker, and raises one as
|
|
48
|
+
// soon as a method is chosen.
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen id → screen (FUT-596).
|
|
3
|
+
*
|
|
4
|
+
* The one place the buyer's pane is chosen. An adapter declares
|
|
5
|
+
* `checkoutScreen` (see `PaymentProviderAdapter`), the gateway publishes it on
|
|
6
|
+
* every chain entry, and this table turns it into a component.
|
|
7
|
+
*
|
|
8
|
+
* ## Why the key is a screen id and not a provider name
|
|
9
|
+
*
|
|
10
|
+
* A provider-name switch is the shape this ticket exists to remove. It cannot
|
|
11
|
+
* express reuse — Stone's flow is PagBank's flow, and under a name key that is
|
|
12
|
+
* a duplicated entry rather than one declaration — and it puts vendor
|
|
13
|
+
* knowledge in the frontend, where nothing declares it and no adapter outside
|
|
14
|
+
* this package can extend it. A screen id inverts that: the adapter says what
|
|
15
|
+
* shape its flow is, and any number of providers share one screen by saying
|
|
16
|
+
* the same word. It is the contract `SetupStep.action` already uses one layer
|
|
17
|
+
* up, for the same reason.
|
|
18
|
+
*
|
|
19
|
+
* ## Unknown ids resolve, they do not fail
|
|
20
|
+
*
|
|
21
|
+
* {@link screenFor} returns `null` for an id it does not know, and the caller
|
|
22
|
+
* renders {@link CapabilityDefaultScreen}. This is load-bearing: the backend
|
|
23
|
+
* and frontend packages version independently, so a host running a newer
|
|
24
|
+
* server than bundle will publish ids this table has never seen. That is an
|
|
25
|
+
* ordinary deployment state and it must degrade to a working checkout, never
|
|
26
|
+
* to an empty pane.
|
|
27
|
+
*/
|
|
28
|
+
import { CapabilityDefaultScreen } from "./capability-default";
|
|
29
|
+
import { HostedLinkScreen } from "./hosted-link";
|
|
30
|
+
import { PixAndCardScreen } from "./pix-and-card";
|
|
31
|
+
import type { ProviderCheckoutScreen } from "./types";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Every screen this bundle can render, by the id an adapter declares.
|
|
35
|
+
*
|
|
36
|
+
* NULL-PROTOTYPE on purpose. The key is a string that arrives from the server,
|
|
37
|
+
* so a plain object literal would answer `SCREENS['constructor']` with
|
|
38
|
+
* `Object` and `SCREENS['toString']` with a function — both truthy, neither a
|
|
39
|
+
* component, and the pane would throw mid-render for a store whose declared id
|
|
40
|
+
* happened to collide with `Object.prototype`. `Object.create(null)` has no
|
|
41
|
+
* prototype to inherit from, so the only keys are the ones written here.
|
|
42
|
+
*/
|
|
43
|
+
const SCREENS: Readonly<Record<string, ProviderCheckoutScreen>> = Object.assign(
|
|
44
|
+
Object.create(null) as Record<string, ProviderCheckoutScreen>,
|
|
45
|
+
{
|
|
46
|
+
/** Collected on our page: a PIX code, or a card typed here. */
|
|
47
|
+
"pix-and-card": PixAndCardScreen,
|
|
48
|
+
/** Finished on the provider's own page. */
|
|
49
|
+
"hosted-link": HostedLinkScreen,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The screen for a declared id, or `null` when nothing is declared or the id
|
|
55
|
+
* is unknown to this bundle. A `null` answer is not an error — see the module
|
|
56
|
+
* comment.
|
|
57
|
+
*/
|
|
58
|
+
export function screenFor(id: string | null | undefined): ProviderCheckoutScreen | null {
|
|
59
|
+
if (!id) return null;
|
|
60
|
+
return SCREENS[id] ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* THE resolution the pane uses: the declared screen, else the capability
|
|
65
|
+
* default. Always returns something renderable, which is AC3.
|
|
66
|
+
*
|
|
67
|
+
* The id is read from the chain's HEAD, because the head is the provider the
|
|
68
|
+
* walk tries first and therefore the flow the buyer is about to enter. A
|
|
69
|
+
* failover to a tail provider happens server-side, after the buyer has already
|
|
70
|
+
* given us everything the charge needs — the card is typed once and minted for
|
|
71
|
+
* every chain entry (FUT-563) — so the pane does not change under them
|
|
72
|
+
* mid-charge, and must not.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveCheckoutScreen(
|
|
75
|
+
chainHeadScreen: string | null | undefined,
|
|
76
|
+
): ProviderCheckoutScreen {
|
|
77
|
+
return screenFor(chainHeadScreen) ?? CapabilityDefaultScreen;
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buyer checkout SCREEN contract (FUT-596).
|
|
3
|
+
*
|
|
4
|
+
* A screen owns the pane where the buyer actually pays — the PIX code, the card
|
|
5
|
+
* form, the hand-off notice — and nothing else. Everything shared stays in the
|
|
6
|
+
* shell: the stepper, the payer summary, the method picker, the totals, the
|
|
7
|
+
* error surfaces, the polling cadence and the terminal `onResolved` hop to
|
|
8
|
+
* Confirmação. That split is what lets a vendor's flow differ in the one place
|
|
9
|
+
* it genuinely differs without forking a checkout.
|
|
10
|
+
*
|
|
11
|
+
* Which screen renders is resolved from an id the ADAPTER declares
|
|
12
|
+
* (`PaymentProviderAdapter.checkoutScreen`, published per chain entry), so
|
|
13
|
+
* neither the shell nor any host names a provider to get here. See
|
|
14
|
+
* `./registry.ts`.
|
|
15
|
+
*/
|
|
16
|
+
import type { JSX } from "react";
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
BuyerInfo,
|
|
20
|
+
CheckoutOrder,
|
|
21
|
+
CheckoutProviderConfig,
|
|
22
|
+
OrderStatus,
|
|
23
|
+
PaymentMethod,
|
|
24
|
+
} from "../types";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What every screen is handed. Every field is already a prop of `PaymentStep`,
|
|
28
|
+
* so resolving a screen threads nothing new through the shell.
|
|
29
|
+
*/
|
|
30
|
+
export interface ProviderCheckoutScreenProps {
|
|
31
|
+
/**
|
|
32
|
+
* The raised payable, or `null` before the buyer has chosen a method.
|
|
33
|
+
*
|
|
34
|
+
* `null` is a real state a screen must render, not a loading artefact: it is
|
|
35
|
+
* the moment a hand-off screen says where the buyer is about to be sent.
|
|
36
|
+
*/
|
|
37
|
+
order: CheckoutOrder | null;
|
|
38
|
+
buyer: BuyerInfo;
|
|
39
|
+
/**
|
|
40
|
+
* The store's protocol, RAW — not pre-narrowed to the card path's view.
|
|
41
|
+
*
|
|
42
|
+
* The shell used to hand the pane `cardTokenization(config)` and
|
|
43
|
+
* `cardChain(config)` directly. Both are card-shaped readings, and a screen
|
|
44
|
+
* whose provider takes no card here must not be given one: it would describe
|
|
45
|
+
* a store that mints instruments when this one does not. Screens that need
|
|
46
|
+
* those readings call the helpers themselves.
|
|
47
|
+
*/
|
|
48
|
+
config: CheckoutProviderConfig | null;
|
|
49
|
+
/** What the shell's picker currently has selected; `null` before a choice. */
|
|
50
|
+
method: PaymentMethod | null;
|
|
51
|
+
/** Scopes the saved-card list to the store being paid. */
|
|
52
|
+
tenantSlug?: string;
|
|
53
|
+
/** The shell's polling cadence, passed through so tests can shorten it. */
|
|
54
|
+
pollIntervalMs?: number;
|
|
55
|
+
/** A terminal status — the shell moves to Confirmação. */
|
|
56
|
+
onResolved: (status: OrderStatus) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A screen is a plain component. `null` is a legal render (nothing to show
|
|
61
|
+
* yet), which is what the pane did for every provider before FUT-596.
|
|
62
|
+
*/
|
|
63
|
+
export type ProviderCheckoutScreen = (
|
|
64
|
+
props: ProviderCheckoutScreenProps,
|
|
65
|
+
) => JSX.Element | null;
|
|
@@ -236,6 +236,22 @@ export interface CheckoutChainLink {
|
|
|
236
236
|
* never shown a field for.
|
|
237
237
|
*/
|
|
238
238
|
customerSchema?: CheckoutCustomerField[];
|
|
239
|
+
/**
|
|
240
|
+
* The buyer screen THIS provider's flow needs (FUT-596) — an opaque id the
|
|
241
|
+
* adapter declares and `providers/registry.ts` resolves to a component.
|
|
242
|
+
*
|
|
243
|
+
* Optional, and the DEGRADE DIRECTION IS THE OPPOSITE of `customerSchema`'s
|
|
244
|
+
* above — spelled out because a reader will otherwise assume symmetry from
|
|
245
|
+
* the neighbouring comment. Absent, `null`, or an id THIS bundle has never
|
|
246
|
+
* heard of all mean the CAPABILITY DEFAULT: the pane composed from
|
|
247
|
+
* `tokenization` + `methods`, exactly as it was before this field existed.
|
|
248
|
+
*
|
|
249
|
+
* That is the safe direction here. Treating an unknown id as "render
|
|
250
|
+
* nothing" would blank the pane for every buyer of a store whose backend is
|
|
251
|
+
* one release ahead of this bundle — and the two packages version
|
|
252
|
+
* independently, so that skew is normal, not exceptional.
|
|
253
|
+
*/
|
|
254
|
+
checkoutScreen?: string | null;
|
|
239
255
|
}
|
|
240
256
|
|
|
241
257
|
/**
|