@12-apps/payments-frontend 3.21.4 → 3.23.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 (59) hide show
  1. package/package.json +2 -2
  2. package/src/components/checkout/basket.ts +85 -0
  3. package/src/components/checkout/card-outcome.ts +81 -0
  4. package/src/components/checkout/card-view.tsx +62 -22
  5. package/src/components/checkout/checkout-actions.ts +387 -0
  6. package/src/components/checkout/checkout-flow.tsx +126 -24
  7. package/src/components/checkout/checkout-steps.tsx +149 -174
  8. package/src/components/checkout/checkout-totals.tsx +51 -0
  9. package/src/components/checkout/client-context.tsx +3 -0
  10. package/src/components/checkout/confirmation-wait.ts +97 -0
  11. package/src/components/checkout/dados-step.tsx +141 -0
  12. package/src/components/checkout/decline.ts +48 -0
  13. package/src/components/checkout/en-US.ts +41 -0
  14. package/src/components/checkout/failure-codes.ts +82 -0
  15. package/src/components/checkout/hosted-return.ts +190 -206
  16. package/src/components/checkout/hosted-store.ts +291 -0
  17. package/src/components/checkout/payment-error-panel.tsx +9 -3
  18. package/src/components/checkout/payment-status-parts.tsx +311 -0
  19. package/src/components/checkout/payment-status.tsx +69 -264
  20. package/src/components/checkout/pix-view.tsx +97 -8
  21. package/src/components/checkout/poll-loop.ts +5 -3
  22. package/src/components/checkout/providers/types.ts +20 -3
  23. package/src/components/checkout/pt-BR.ts +42 -0
  24. package/src/components/checkout/screens-copy.ts +14 -0
  25. package/src/components/checkout/screens-en-US.ts +1 -0
  26. package/src/components/checkout/screens-pt-BR.ts +3 -0
  27. package/src/components/checkout/transport.ts +21 -1
  28. package/src/components/checkout/types.ts +35 -0
  29. package/src/components/checkout/use-card-checkout.ts +34 -33
  30. package/src/components/checkout/use-checkout-controller.ts +68 -274
  31. package/src/components/checkout/use-hosted-resume.ts +326 -0
  32. package/src/components/checkout/use-payment-polling.ts +58 -7
  33. package/src/components/checkout/use-wallet-charge.ts +24 -1
  34. package/src/components/checkout/view-copy.ts +70 -0
  35. package/src/components/checkout/wallet-pane.tsx +9 -1
  36. package/src/flows/catalog-exit.ts +33 -0
  37. package/src/flows/create-payment-flows.tsx +19 -1
  38. package/src/flows/pipeline/actions.tsx +104 -0
  39. package/src/flows/pipeline/admission.ts +55 -0
  40. package/src/flows/pipeline/context.ts +140 -0
  41. package/src/flows/pipeline/derive-step.ts +234 -0
  42. package/src/flows/pipeline/engine-actions.ts +257 -0
  43. package/src/flows/pipeline/engine-chrome.tsx +123 -0
  44. package/src/flows/pipeline/engine-state.ts +107 -0
  45. package/src/flows/pipeline/engine.tsx +377 -0
  46. package/src/flows/pipeline/methods.ts +71 -0
  47. package/src/flows/pipeline/refusal-routing.ts +106 -0
  48. package/src/flows/pipeline/slices.ts +110 -0
  49. package/src/flows/pipeline/stable-plugins.ts +72 -0
  50. package/src/flows/pipeline/steps/buyer-steps.tsx +297 -0
  51. package/src/flows/pipeline/steps/index.ts +54 -0
  52. package/src/flows/pipeline/steps/pay-steps.tsx +182 -0
  53. package/src/flows/pipeline/steps/status-step.tsx +41 -0
  54. package/src/flows/pipeline/types.ts +232 -0
  55. package/src/flows/public.ts +78 -0
  56. package/src/flows/screens-hosted.tsx +55 -5
  57. package/src/flows/screens-pay.tsx +6 -1
  58. package/src/flows/types.ts +25 -2
  59. package/src/index.ts +29 -19
@@ -0,0 +1,232 @@
1
+ /**
2
+ * THE CHECKOUT PIPELINE'S VOCABULARY (FUT-1240, step 4 of FUT-1216).
3
+ *
4
+ * A checkout is a list of STEPS, a list of GATES and a list of SETTLEMENT
5
+ * METHODS. Each is a plain object in an array; registration is array
6
+ * membership and nothing else. The engine derives which step the shopper is
7
+ * on rather than remembering it, so a reload, a torn-down tab and a hand-off
8
+ * return all land where the server's own facts say they should.
9
+ *
10
+ * ## Two properties this file exists to keep
11
+ *
12
+ * 1. **Method syntax, never arrow properties.** Under `strictFunctionTypes` a
13
+ * method's parameters are BIVARIANT while a property holding a function is
14
+ * contravariant — so `CheckoutStep<S, F>` is assignable to
15
+ * {@link AnyCheckoutStep} with no `any` and no cast. Written as
16
+ * `applies: (ctx, facts) => boolean` instead, every registration would need
17
+ * a cast, and this repo forbids `any`.
18
+ * 2. **The package names no host.** Nothing here imports a host, a sibling
19
+ * workspace package or a domain word. A mode, a mesa, a comanda and a
20
+ * delivery address are all host concepts and reach the engine as a
21
+ * registered step or a registered method, never as a field.
22
+ */
23
+ import type { ComponentType, ReactNode } from "react";
24
+
25
+ import type { CheckoutCartView } from "../../components/checkout/checkout-flow";
26
+ import type {
27
+ BuyerInfo,
28
+ CheckoutError,
29
+ CheckoutOrder,
30
+ CheckoutProviderConfig,
31
+ CreateOrderRequest,
32
+ OrderStatus,
33
+ SettlementCheckout,
34
+ } from "../../components/checkout/types";
35
+ import type {
36
+ CheckoutStepperCopy,
37
+ CheckoutViewCopy,
38
+ } from "../../components/checkout/view-copy";
39
+
40
+ /**
41
+ * Where a step sits in the walk. The engine sorts by this first and by
42
+ * {@link CheckoutStep.order} second, so a host appends a step without knowing
43
+ * what else is registered.
44
+ *
45
+ * `pay` is the phase the no-charge rule is stated over: a settlement method
46
+ * that raises no charge mounts NO `pay` step, ever — see
47
+ * {@link SettlementMethodDescriptor.raisesCharge}.
48
+ */
49
+ export type CheckoutStepPhase = "details" | "before-pay" | "pay" | "after-pay";
50
+
51
+ /** Everything a step, a gate or a method may read. All of it server-owned or parked. */
52
+ export interface CheckoutContext {
53
+ tenantSlug?: string;
54
+ /** `GET /config`: chain, methods, tokenization. */
55
+ config: CheckoutProviderConfig | null;
56
+ configPending: boolean;
57
+ cart: CheckoutCartView;
58
+ /** Host-resolved balance, opaque scope. */
59
+ settlement: SettlementCheckout | null;
60
+ buyer: BuyerInfo;
61
+ taxIdOnFile: boolean;
62
+ /** A {@link SettlementMethodDescriptor.id}, or `null` before a choice. */
63
+ method: string | null;
64
+ /** Raised now, resumed from the server, or parked by a hand-off. */
65
+ order: CheckoutOrder | null;
66
+ outcome: OrderStatus | null;
67
+ intent: { oneClick: boolean; resuming: boolean; presetMethod: string | null };
68
+ /** Every step's declared slice, by step id. */
69
+ slices: Readonly<Record<string, unknown>>;
70
+ }
71
+
72
+ /**
73
+ * A step's own scrap of state, DECLARED rather than held.
74
+ *
75
+ * The engine owns the storage, which is what makes a reload survivable: a
76
+ * `session` slice is parked under `payments.checkout.<slug>.<stepId>` and
77
+ * rehydrated on mount. Nothing money-shaped belongs here — that is the order.
78
+ */
79
+ export interface StepSlice<S> {
80
+ initial(ctx: CheckoutContext): S;
81
+ /** `"session"` ⇒ `payments.checkout.<slug>.<stepId>`, rehydrated on mount. */
82
+ persist: "none" | "session";
83
+ /**
84
+ * Trust nothing that came back out of storage — the `hosted-return.ts` rule.
85
+ * A `session` slice with NO parser is never rehydrated: the engine cannot
86
+ * check a shape it has not been told, and a half-written value reaching a
87
+ * step as its state is exactly what that rule exists to stop.
88
+ */
89
+ parse?(raw: unknown): S | null;
90
+ }
91
+
92
+ /** What a step's `render` is handed. Its own type so the method syntax stays readable. */
93
+ export interface CheckoutStepRender<S> {
94
+ ctx: CheckoutContext;
95
+ slice: S;
96
+ setSlice(s: S): void;
97
+ /** Done here — clear any back-navigation and let the walk move on. */
98
+ advance(): void;
99
+ /** The previous applying step, or the host's catalog when there is none. */
100
+ back(): void;
101
+ /** A server refusal THIS step claimed through {@link CheckoutStep.answersCodes}. */
102
+ error: CheckoutError | null;
103
+ }
104
+
105
+ /** One step of the walk. */
106
+ export interface CheckoutStep<S = void, F = void> {
107
+ id: string;
108
+ phase: CheckoutStepPhase;
109
+ order?: number;
110
+ /** Host hook; runs EVERY render, in array order. */
111
+ useFacts?(): F;
112
+ /** Pure. */
113
+ applies(ctx: CheckoutContext, facts: F): boolean;
114
+ /** Pure — the current step is the first applying step whose answer is `false`. */
115
+ complete(ctx: CheckoutContext, facts: F, slice: S): boolean;
116
+ slice?: StepSlice<S>;
117
+ render(p: CheckoutStepRender<S> & { facts: F }): ReactNode;
118
+ /** Server refusal codes re-rendered HERE, never as a generic retry. */
119
+ answersCodes?: readonly string[];
120
+ contribute?(ctx: CheckoutContext, facts: F, slice: S): Partial<CreateOrderRequest>;
121
+ /** Absent ⇒ an interstitial, not a stepper node. */
122
+ label?: keyof CheckoutStepperCopy | null;
123
+ }
124
+
125
+ /** Any registered step. Reachable with no cast because every member above is a method. */
126
+ export type AnyCheckoutStep = CheckoutStep<unknown, unknown>;
127
+
128
+ /** What a gate decided about this shopper. */
129
+ export type GateVerdict =
130
+ | { kind: "pass" }
131
+ | { kind: "pending" }
132
+ | {
133
+ kind: "refuse";
134
+ Screen: ComponentType<{
135
+ ctx: CheckoutContext;
136
+ error: CheckoutError | null;
137
+ retry(): void;
138
+ }>;
139
+ };
140
+
141
+ /** Something that must be true before ANY step renders. */
142
+ export interface CheckoutGate<F = void> {
143
+ id: string;
144
+ useFacts?(): F;
145
+ /** Pure. */
146
+ decide(ctx: CheckoutContext, facts: F): GateVerdict;
147
+ answersCodes?: readonly string[];
148
+ /** Bypassed when a hand-off from this tab is still waiting to be resolved. */
149
+ standsAsideForResume?: boolean;
150
+ }
151
+
152
+ /** Any registered gate. */
153
+ export type AnyCheckoutGate = CheckoutGate<unknown>;
154
+
155
+ /**
156
+ * A way of settling — the seam a no-charge settlement never had.
157
+ *
158
+ * `raisesCharge: false` is the whole reason this type exists: placing the
159
+ * order IS the settlement, so there is no `/charge`, no poll, and no payment
160
+ * surface after the method is chosen. The engine enforces that ONCE, over the
161
+ * `pay` phase, so no lane can forget it.
162
+ */
163
+ export interface SettlementMethodDescriptor<F = void> {
164
+ /** `"PIX"`, `"CARD"`, `"ON_DELIVERY"`, `"WAITER"`, `"BOLETO"`… */
165
+ id: string;
166
+ /** `false` ⇒ no `/charge`, no poll; placing the order IS the settlement. */
167
+ raisesCharge: boolean;
168
+ /** The `pay`-phase step id shown once the order exists; `null` ⇒ Confirmation. */
169
+ pane: string | null;
170
+ useFacts?(): F;
171
+ offered(ctx: CheckoutContext, facts: F): boolean;
172
+ Review?: ComponentType<{
173
+ ctx: CheckoutContext;
174
+ place(): void;
175
+ placing: boolean;
176
+ error: CheckoutError | null;
177
+ }>;
178
+ /** Default: the package's own `PaymentStatus`. */
179
+ Confirmation?: ComponentType<{ ctx: CheckoutContext; order: CheckoutOrder }>;
180
+ tile(copy: CheckoutViewCopy): { label: string; hint?: string };
181
+ }
182
+
183
+ /** Any registered settlement method. */
184
+ export type AnySettlementMethod = SettlementMethodDescriptor<unknown>;
185
+
186
+ /**
187
+ * Where the shopper leaves for, and how.
188
+ *
189
+ * A hook for the label because it is the host's router that knows which
190
+ * catalog this checkout came from, and a value would freeze the first one.
191
+ */
192
+ export interface CheckoutExit {
193
+ useCatalog(): { to: string; label: string };
194
+ navigate(to: string): void;
195
+ }
196
+
197
+ /**
198
+ * The engine's half of `PaymentFlowsConfig` — every key OPTIONAL.
199
+ *
200
+ * Setting ANY of them switches `Checkout` onto the pipeline. Setting NONE
201
+ * leaves it rendering exactly today's `CheckoutFlow`, which is the whole
202
+ * meaning of this step being additive.
203
+ */
204
+ export interface CheckoutPipelineConfig {
205
+ /** Merged with the package's own, by phase then `order`. */
206
+ steps?: readonly AnyCheckoutStep[];
207
+ /** Evaluated in order before any step renders. */
208
+ gates?: readonly AnyCheckoutGate[];
209
+ /** Merged with the package's PIX + CARD. */
210
+ settlementMethods?: readonly AnySettlementMethod[];
211
+ /** The HOST reads its own URL; the package reads nothing. */
212
+ useIntent?(): CheckoutContext["intent"];
213
+ /** Resume from the SERVER, not only from a parked hand-off. */
214
+ useOpenPayable?(): { order: CheckoutOrder | null; pending: boolean };
215
+ /** Absent ⇒ `ports.exitToCatalog()`, which is what every host wires today. */
216
+ exit?: CheckoutExit;
217
+ /** The host invalidates ITS keys. */
218
+ onSettled?(outcome: OrderStatus, order: CheckoutOrder): void;
219
+ }
220
+
221
+ /** Whether a config asked for the pipeline at all. */
222
+ export function pipelineRequested(config: CheckoutPipelineConfig): boolean {
223
+ return (
224
+ config.steps !== undefined ||
225
+ config.gates !== undefined ||
226
+ config.settlementMethods !== undefined ||
227
+ config.useIntent !== undefined ||
228
+ config.useOpenPayable !== undefined ||
229
+ config.exit !== undefined ||
230
+ config.onSettled !== undefined
231
+ );
232
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * THE FACTORY'S PUBLIC SURFACE — `createPaymentFlows` (FUT-741) and the
3
+ * checkout pipeline it can now run (FUT-1240).
4
+ *
5
+ * Listed here rather than inline in `src/index.ts` for the reason
6
+ * `./activation/public.ts` already gives: the root barrel is at its size gate,
7
+ * and a surface that grows with every plugin type is one that will keep
8
+ * growing. Everything below is re-exported verbatim by the root.
9
+ */
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // The MOUNTED buyer checkout (FUT-741) — `createPaymentFlows` returns every
13
+ // screen pre-bound to one transport, one scope, one slot table and one set of
14
+ // host ports. Additive: the hand-composing path is unchanged.
15
+ // ---------------------------------------------------------------------------
16
+ export { createPaymentFlows } from './create-payment-flows';
17
+ // A type and nothing else now: `DEFAULT_CHECKOUT_COPY_FE` used to sit beside it
18
+ // and was the only value this module ever published (FUT-760).
19
+ export type { CheckoutCopyFE } from './copy';
20
+ export {
21
+ type BoundCheckoutClient,
22
+ type BuyerDetailsProps,
23
+ type CheckoutAvailability,
24
+ type CheckoutConfigState,
25
+ type CheckoutController,
26
+ type CheckoutPorts,
27
+ type CheckoutScreens,
28
+ type PaymentFlows,
29
+ type PaymentFlowsConfig,
30
+ } from './types';
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // The checkout PIPELINE (FUT-1240) — steps, gates and settlement methods as
34
+ // registered plugins, with the current step DERIVED from the server's own
35
+ // facts rather than held in a `useState` a reload forgets.
36
+ //
37
+ // ADDITIVE: a host that registers none of these gets exactly the flat
38
+ // three-step `CheckoutFlow`, unchanged, down to its test ids. What the types
39
+ // below buy is the ability to say "this store has a step of its own", "this
40
+ // shopper may not check out yet", and "this settlement raises no charge" — the
41
+ // last of which the package had no seam for at all, so every in-person payment
42
+ // path was written beside the checkout instead of inside it.
43
+ // ---------------------------------------------------------------------------
44
+ export type {
45
+ AnyCheckoutGate,
46
+ AnyCheckoutStep,
47
+ AnySettlementMethod,
48
+ CheckoutContext,
49
+ CheckoutExit,
50
+ CheckoutGate,
51
+ CheckoutPipelineConfig,
52
+ CheckoutStep,
53
+ CheckoutStepPhase,
54
+ CheckoutStepRender,
55
+ GateVerdict,
56
+ SettlementMethodDescriptor,
57
+ StepSlice,
58
+ } from './pipeline/types';
59
+ /**
60
+ * The step ids the PACKAGE registers, so a host ordering its own steps around
61
+ * them names a constant instead of retyping a string that can change.
62
+ */
63
+ export {
64
+ DADOS_STEP_ID,
65
+ HANDOFF_STEP_ID,
66
+ METHOD_STEP_ID,
67
+ RESUME_STEP_ID,
68
+ STATUS_STEP_ID,
69
+ } from './pipeline/steps';
70
+ export { CARD_PANE_STEP, PIX_PANE_STEP } from './pipeline/methods';
71
+ /**
72
+ * Where a step's parked slice lives — `payments.checkout.<slug>.<stepId>`.
73
+ * Public for the same reason `HOSTED_ORDER_STORAGE_KEY` is: a host clearing
74
+ * storage on sign-out, or a spec asserting a resume, otherwise retypes it.
75
+ */
76
+ export { sliceKey } from './pipeline/slices';
77
+ /** Whose plugin answers a refusal code — the routing, without the renderer. */
78
+ export { refusalOwner, type RefusalOwner } from './pipeline/refusal-routing';
@@ -23,6 +23,7 @@
23
23
  import { Box } from "@mui/material";
24
24
  import { useEffect, useState, type JSX } from "react";
25
25
 
26
+ import { parkedBasket, type CheckoutBasketIdentity } from "../components/checkout/basket";
26
27
  import { rememberHostedOrder, takeHostedOrder } from "../components/checkout/hosted-return";
27
28
  import type { CheckoutOrder, OrderStatus } from "../components/checkout/types";
28
29
  import { useCheckoutComponents } from "../components/checkout/ui";
@@ -31,15 +32,45 @@ import { usePaymentPolling } from "../components/checkout/use-payment-polling";
31
32
  import { FlowsShell, type FlowsRuntime } from "./runtime";
32
33
  import type { CheckoutScreens } from "./types";
33
34
 
35
+ /** Whose store and which basket this hand-off belongs to. */
36
+ interface HandoverScope {
37
+ tenantSlug?: string;
38
+ basket?: CheckoutBasketIdentity;
39
+ }
40
+
34
41
  /** Park the order, then navigate — exactly once, even under StrictMode. */
35
- function useHandover(payable: CheckoutOrder, url: string, navigate: (url: string) => void): void {
42
+ function useHandover(
43
+ payable: CheckoutOrder,
44
+ url: string,
45
+ navigate: (url: string) => void,
46
+ /**
47
+ * PARKED WITH THE STORE AND THE BASKET (FUT-1240).
48
+ *
49
+ * This call site named neither, and both absences are read as "no opinion"
50
+ * by the resume: `belongsHere` passes a slug-less entry at ANY store and the
51
+ * basket rule passes a basket-less entry against ANY basket. So the one
52
+ * hand-off screen this package ships was exempt from the multi-tenant
53
+ * scoping (FUT-556) and from the basket binding (FUT-1213) — it resumed over
54
+ * whatever checkout mounted next. The other three parks were fixed one at a
55
+ * time; this is the last of them.
56
+ */
57
+ scope: HandoverScope,
58
+ ): void {
36
59
  const [done, setDone] = useState(false);
37
60
  useEffect(() => {
38
61
  if (done) return;
39
62
  setDone(true);
40
- rememberHostedOrder(payable);
63
+ const basket = parkedBasket(scope.basket);
64
+ // `handoff: true` is what tells the return leg this order was finished on
65
+ // ANOTHER SITE (FUT-1140): only such an order resumes onto the confirmation
66
+ // screen, because only such an order has nothing left on our page to show.
67
+ rememberHostedOrder(payable, {
68
+ ...(scope.tenantSlug === undefined ? {} : { tenantSlug: scope.tenantSlug }),
69
+ ...(basket === undefined ? {} : { basket }),
70
+ handoff: true,
71
+ });
41
72
  navigate(url);
42
- }, [done, payable, url, navigate]);
73
+ }, [done, payable, url, navigate, scope.tenantSlug, scope.basket]);
43
74
  }
44
75
 
45
76
  function buildHostedHandoff(runtime: FlowsRuntime): CheckoutScreens["HostedHandoff"] {
@@ -54,7 +85,12 @@ function buildHostedHandoff(runtime: FlowsRuntime): CheckoutScreens["HostedHando
54
85
  }): JSX.Element {
55
86
  const { Button, LoadingState, Text } = useCheckoutComponents();
56
87
  const copy = runtime.copy;
57
- useHandover(payable, url, runtime.navigate);
88
+ const tenantSlug = runtime.useTenantSlug();
89
+ const cart = runtime.config.useCart();
90
+ useHandover(payable, url, runtime.navigate, {
91
+ ...(tenantSlug === undefined ? {} : { tenantSlug }),
92
+ ...(cart.identity === undefined ? {} : { basket: cart.identity }),
93
+ });
58
94
  return (
59
95
  <Box
60
96
  data-testid="checkout-hosted-handoff"
@@ -163,6 +199,20 @@ function ReturnStalled({
163
199
  );
164
200
  }
165
201
 
202
+ /**
203
+ * The parked order this return trip is for, or null.
204
+ *
205
+ * This screen is mounted at the host's RETURN route, so it carries no basket to
206
+ * weigh the entry against and the FUT-1213 rule cannot ask for one: with no
207
+ * basket named the decision is the pre-1213 one — resume what was parked —
208
+ * which is the right answer for a URL that exists only for buyers coming back
209
+ * from a payment. `ASK` is unreachable from here.
210
+ */
211
+ function resumeParked(): CheckoutOrder | null {
212
+ const decision = takeHostedOrder();
213
+ return decision.verdict === "RESUME" ? decision.order : null;
214
+ }
215
+
166
216
  function buildHostedReturn(runtime: FlowsRuntime): CheckoutScreens["HostedReturn"] {
167
217
  function HostedReturnBody({
168
218
  onResolved,
@@ -172,7 +222,7 @@ function buildHostedReturn(runtime: FlowsRuntime): CheckoutScreens["HostedReturn
172
222
  const { Alert, LoadingState } = useCheckoutComponents();
173
223
  // Read-and-clear, once, on first render: the resumed view belongs to
174
224
  // exactly one return trip.
175
- const [parked] = useState(takeHostedOrder);
225
+ const [parked] = useState(resumeParked);
176
226
  // Bounded, for the reason on RETURN_WINDOW_MS: nothing here can ever reach a
177
227
  // terminal state on its own, so an unbounded poll is a spinner the buyer
178
228
  // watches until they close the tab.
@@ -15,6 +15,7 @@ import { PixView } from "../components/checkout/pix-view";
15
15
  import type { CheckoutOrder, OrderStatus } from "../components/checkout/types";
16
16
  import { useCheckoutComponents } from "../components/checkout/ui";
17
17
 
18
+ import { useCatalogExit } from "./catalog-exit";
18
19
  import { FlowsShell, useResolvedConfig, type FlowsRuntime } from "./runtime";
19
20
  import type { CheckoutScreens } from "./types";
20
21
 
@@ -79,6 +80,10 @@ function buildPaymentStatus(runtime: FlowsRuntime): CheckoutScreens["PaymentStat
79
80
  status: OrderStatus | null;
80
81
  payable?: CheckoutOrder | null;
81
82
  }) {
83
+ // The SAME door the chrome's back link takes (FUT-1240). Bound straight to
84
+ // the port, this control ignored a registered `exit` — so one host got its
85
+ // router from one way out of the checkout and a page load from the other.
86
+ const backToMenu = useCatalogExit(runtime);
82
87
  return (
83
88
  <FlowsShell runtime={runtime}>
84
89
  <PaymentStatusView
@@ -86,7 +91,7 @@ function buildPaymentStatus(runtime: FlowsRuntime): CheckoutScreens["PaymentStat
86
91
  status={status}
87
92
  totalLabel={payable?.totalLabel ?? ""}
88
93
  orderId={payable?.orderId}
89
- onBackToMenu={runtime.config.ports.exitToCatalog}
94
+ onBackToMenu={backToMenu}
90
95
  paidExtra={runtime.config.confirmation?.extra}
91
96
  />
92
97
  </FlowsShell>
@@ -45,6 +45,7 @@ import type { useCheckoutController } from "../components/checkout/use-checkout-
45
45
  import type { Result } from "../result";
46
46
 
47
47
  import type { CheckoutCopyFE } from "./copy";
48
+ import type { CheckoutPipelineConfig, GateVerdict } from "./pipeline/types";
48
49
 
49
50
  /** The host's remedy on the unavailable screen, and its veto over a live chain. */
50
51
  export interface CheckoutAvailability {
@@ -88,8 +89,17 @@ export interface CheckoutPorts {
88
89
  useAvailability?(): CheckoutAvailability;
89
90
  }
90
91
 
91
- /** What `createPaymentFlows` is configured with. */
92
- export interface PaymentFlowsConfig {
92
+ /**
93
+ * What `createPaymentFlows` is configured with.
94
+ *
95
+ * It extends {@link CheckoutPipelineConfig}, whose every key is OPTIONAL: set
96
+ * one and `Checkout` renders through the checkout PIPELINE (FUT-1240); set
97
+ * none and it renders exactly the `CheckoutFlow` it always did, with the same
98
+ * pixels and the same test ids. That is the whole meaning of the pipeline
99
+ * landing additively — a host adopts it when it has a step, a gate or a
100
+ * settlement method of its own to register, and not a release earlier.
101
+ */
102
+ export interface PaymentFlowsConfig extends CheckoutPipelineConfig {
93
103
  /** Where the `createPaymentFlowsBE` mount lives. Default `/api/checkout`. */
94
104
  transport?: CheckoutTransportBinding;
95
105
 
@@ -209,6 +219,19 @@ export interface PaymentFlows {
209
219
  screens: CheckoutScreens;
210
220
  /** The flow controller, pre-bound to the ports — the easy path is not the only path. */
211
221
  useCheckout(): CheckoutController;
222
+ /**
223
+ * MAY THIS SHOPPER CHECK OUT — the registered gate list, headless (FUT-1240).
224
+ *
225
+ * The same list, in the same order, that `Checkout` runs before it renders
226
+ * anything. Exported so the surfaces that OFFER a checkout — a cart
227
+ * drawer's CTA, a buy-now button — consume the answer the checkout will
228
+ * give rather than a second one of their own. Two surfaces deciding this
229
+ * separately is how a storefront ends up offering a checkout the checkout
230
+ * itself refuses.
231
+ *
232
+ * A host that registered no gates always passes.
233
+ */
234
+ useAdmission(): GateVerdict;
212
235
  useCheckoutConfig(): CheckoutConfigState;
213
236
  client: BoundCheckoutClient;
214
237
  }
package/src/index.ts CHANGED
@@ -61,26 +61,14 @@ export {
61
61
  } from './components/checkout/checkout-flow';
62
62
 
63
63
  // ---------------------------------------------------------------------------
64
- // The MOUNTED buyer checkout (FUT-741) `createPaymentFlows` returns every
65
- // screen pre-bound to one transport, one scope, one slot table and one set of
66
- // host ports. Additive: everything above and below stays exported, and the
67
- // hand-composing path is unchanged.
64
+ // The MOUNTED buyer checkout (FUT-741) and the checkout PIPELINE (FUT-1240).
65
+ //
66
+ // Listed in `./flows/public` rather than inline, on the `./activation/public`
67
+ // precedent below: this barrel is at its size gate, and a surface that grows
68
+ // with every plugin type belongs beside the plugins.
68
69
  // ---------------------------------------------------------------------------
69
- export { createPaymentFlows } from './flows/create-payment-flows';
70
- // A type and nothing else now: `DEFAULT_CHECKOUT_COPY_FE` used to sit beside it
71
- // and was the only value this module ever published (FUT-760).
72
- export type { CheckoutCopyFE } from './flows/copy';
73
- export {
74
- type BoundCheckoutClient,
75
- type BuyerDetailsProps,
76
- type CheckoutAvailability,
77
- type CheckoutConfigState,
78
- type CheckoutController,
79
- type CheckoutPorts,
80
- type CheckoutScreens,
81
- type PaymentFlows,
82
- type PaymentFlowsConfig,
83
- } from './flows/types';
70
+ export * from './flows/public';
71
+
84
72
  export {
85
73
  buyerFieldsFor,
86
74
  fieldSatisfied,
@@ -198,6 +186,28 @@ export {
198
186
  type CheckoutStepperStep,
199
187
  type CheckoutTextProps,
200
188
  } from './components/checkout/ui';
189
+ /**
190
+ * WHICH basket a checkout is for (FUT-1213).
191
+ *
192
+ * A host computes the signature from its own cart lines and hands it to the
193
+ * flow on `cart.identity`, so a payment raised from an ABANDONED basket cannot
194
+ * resume itself over the one the shopper is holding now. Exported because the
195
+ * host owns the cart and therefore has to build it — see `basket.ts` for why
196
+ * the identity is the lines and never the cart's id.
197
+ */
198
+ export {
199
+ basketSignature,
200
+ type CheckoutBasketIdentity,
201
+ type CheckoutBasketLine,
202
+ } from './components/checkout/basket';
203
+ /**
204
+ * WHY a card was refused, and whether another attempt could work (FUT-1145).
205
+ * A host wiring the confirmation screen's per-reason copy names these.
206
+ */
207
+ export type {
208
+ CheckoutDecline,
209
+ CheckoutDeclineReason,
210
+ } from './components/checkout/decline';
201
211
  export {
202
212
  type BuyerContact,
203
213
  type BuyerField,