@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
@@ -1,13 +1,25 @@
1
- import { useCallback, useEffect, useMemo, useState, type Dispatch, type SetStateAction } from "react";
2
-
3
- import { buyerGateError } from "./buyer-gate";
1
+ import { useCallback, useMemo, useState } from "react";
2
+
3
+ import type { CheckoutBasketIdentity } from "./basket";
4
+ import {
5
+ resumeSurface,
6
+ useCheckoutNav,
7
+ useCreateFailure,
8
+ useGoToPayment,
9
+ useResumedCheckout,
10
+ useRetryAction,
11
+ useSettledPort,
12
+ useStartPayment,
13
+ type Step,
14
+ } from "./checkout-actions";
15
+ import { useConfirmationWait } from "./confirmation-wait";
4
16
  import { useCheckoutCopy } from "./copy-context";
5
17
 
6
- import { rememberHostedOrder, takeHostedOrder } from "./hosted-return";
7
- import { useCheckoutNavigate, type CheckoutNavigate } from "./navigate-context";
18
+ import { forgetHostedOrder } from "./hosted-return";
19
+ import { useCheckoutNavigate } from "./navigate-context";
20
+ import type { CheckoutDecline } from "./decline";
8
21
  import type {
9
22
  BuyerContact,
10
- BuyerField,
11
23
  BuyerInfo,
12
24
  CheckoutOrder,
13
25
  CreateOrderRequest,
@@ -16,9 +28,7 @@ import type {
16
28
  OrderStatus,
17
29
  PaymentMethod,
18
30
  } from "./types";
19
- import { usePaymentPolling } from "./use-payment-polling";
20
-
21
- type Step = "dados" | "payment" | "status";
31
+ import { useHostedResume } from "./use-hosted-resume";
22
32
 
23
33
  const STEP_ORDER: Step[] = ["dados", "payment", "status"];
24
34
 
@@ -57,233 +67,6 @@ export interface CheckoutHostPorts {
57
67
  onPaid?: () => void;
58
68
  }
59
69
 
60
- /**
61
- * The flow's navigation actions, split out of {@link useCheckoutController} for
62
- * the 80-line per-function gate.
63
- *
64
- * `back` is where "the Dados step was skipped" has to be honoured: going back
65
- * off Pagamento normally lands on Dados, but for a buyer with a CPF on file
66
- * that step is not part of their flow, so the menu is the only honest
67
- * destination — until they open it themselves via `editBuyer` ("alterar" on the
68
- * payer block), after which it IS part of their flow and back returns to it.
69
- */
70
- function useCheckoutNav(
71
- taxIdOnFile: boolean,
72
- goToMenu: () => void,
73
- setStep: Dispatch<SetStateAction<Step>>,
74
- ): { back: () => void; editBuyer: (() => void) | undefined } {
75
- const [dadosOpened, setDadosOpened] = useState(false);
76
- const openDados = useCallback(() => {
77
- setDadosOpened(true);
78
- setStep("dados");
79
- }, [setStep]);
80
- const back = useCallback(() => {
81
- setStep((current) => {
82
- if (current === "payment" && (!taxIdOnFile || dadosOpened)) return "dados";
83
- goToMenu();
84
- return current;
85
- });
86
- }, [dadosOpened, goToMenu, setStep, taxIdOnFile]);
87
- // Undefined unless Dados was skipped — the payer block keys off its presence,
88
- // so the decision lives here rather than being re-derived by every caller.
89
- return { back, editBuyer: taxIdOnFile ? openDados : undefined };
90
- }
91
-
92
- /**
93
- * Where checkout opens: on the outcome for a buyer returning from a hosted
94
- * provider, on Pagamento when their CPF is already on file, else on Dados.
95
- */
96
- function initialStep(resuming: boolean, taxIdOnFile: boolean): Step {
97
- if (resuming) return "status";
98
- return taxIdOnFile ? "payment" : "dados";
99
- }
100
-
101
- /**
102
- * Hand the buyer to a redirect provider's own page, if that is where this
103
- * charge settles (FUT-556).
104
- *
105
- * Called BEFORE the order is stored: storing it first would render the PIX or
106
- * card step for a provider that returned neither, which is the dead end this
107
- * fixes.
108
- *
109
- * A full navigation rather than the host's router, because the destination is
110
- * another origin. The return trip comes back to this same checkout route
111
- * carrying `transaction_nsu` + `slug`, which the status poll already reads.
112
- *
113
- * @returns true when the buyer is on their way and the caller must stop.
114
- */
115
- function handOverToProvider(
116
- order: CheckoutOrder,
117
- navigate: CheckoutNavigate,
118
- tenantSlug?: string,
119
- ): boolean {
120
- if (!order.hostedCheckoutUrl) return false;
121
- // PARK FIRST, navigate second. The order is the only thing the return trip
122
- // has to rehydrate from, and the navigation may tear this SPA down before
123
- // any later write lands.
124
- //
125
- // The STORE goes with it: one tab holds one slot, and on a multi-tenant
126
- // storefront every store shares an origin. Without the slug, abandoning this
127
- // hand-off and opening another store's checkout resumed THIS order there.
128
- rememberHostedOrder(order, tenantSlug);
129
- navigate(order.hostedCheckoutUrl);
130
- return true;
131
- }
132
-
133
- /**
134
- * How long the resumed screen keeps asking, and how often.
135
- *
136
- * TWO RATES, because one rate cannot serve this wait. The interval decides two
137
- * things that pull opposite ways: how fast a buyer WHO PAID is told so, and
138
- * what an abandoned checkout costs for the rest of the window. Every poll is a
139
- * provider round trip, so a slow rate is cheap and leaves a paying buyer
140
- * watching a spinner seconds longer than they need to — and the person on this
141
- * screen has almost always paid. A single number picks one of them to lose;
142
- * this shipped at a flat 5 s and picked the wrong one.
143
- *
144
- * So: 2.5 s for the first two minutes, which is where essentially every real
145
- * webhook lands (it fires within seconds of the payment, and this rate matches
146
- * what card and PIX already use), then 10 s for the remaining thirteen. A
147
- * confirmation is at most 2.5 s late, and an abandoned checkout costs ~126
148
- * polls instead of the 360 a flat 2.5 s would have.
149
- *
150
- * Fifteen minutes because by then a webhook that was ever coming has come. Past
151
- * that the answer will not change while the buyer watches: the scheduled
152
- * reconciliation is what rescues a genuinely late one, and it does that whether
153
- * the tab is open or not.
154
- *
155
- * The BOUND is that wall-clock window, not a poll count (FUT-1144). The two are
156
- * the same number for a healthy wait — 48 × 2.5 s + 78 × 10 s = 15 min — and
157
- * they part company for the wait that needed bounding: a poll that FAILS
158
- * incremented nothing, so a connection that never came back left this screen
159
- * asking, and spinning, with no end at all. A clock cannot be stopped by the
160
- * failure it is measuring.
161
- *
162
- * The card wait is bounded at 90 s (`CARD_AWAITING_WAIT_MS`) because a card
163
- * authorises inline and a buyer is holding their phone. This leg is the other
164
- * shape: the buyer has already been off to another site and back, and may
165
- * legitimately still be finishing there.
166
- */
167
- const HOSTED_RESUME_FAST_MS = 2_500;
168
- const HOSTED_RESUME_SLOW_MS = 10_000;
169
- /** Two minutes at the fast rate, before the wait is worth economising on. */
170
- const HOSTED_RESUME_FAST_POLLS = (2 * 60_000) / HOSTED_RESUME_FAST_MS;
171
- /** Thirteen more at the slow one — 15 minutes all told. */
172
- const HOSTED_RESUME_WINDOW_MS = 15 * 60_000;
173
-
174
- /**
175
- * The leg of checkout that resumes after a hosted provider sent the buyer back
176
- * (FUT-556).
177
- *
178
- * The SPA was torn down by the redirect, so the order is rehydrated from the
179
- * parked copy — once, on first render — and polled here rather than in a PIX or
180
- * card view, because a redirect provider produced neither. The webhook is still
181
- * what settles the order; this only tells the buyer that it did.
182
- *
183
- * And it stops telling them eventually. This poll was the one unbounded wait
184
- * left in checkout — card and wallet both cap theirs — so a buyer who came back
185
- * from a payment they never completed got "Confirmando seu pagamento… isso
186
- * costuma levar alguns segundos" and a spinner, truthfully forever. The screen
187
- * had no way to reach a terminal state, because the ORDER has none: expiry is
188
- * PIX-only, and a redirect charge carries no QR window to lapse. `timedOut` is
189
- * what the screen says instead of spinning.
190
- *
191
- * `error` is the half that was dropped on the floor (FUT-1144), and dropping it
192
- * is what made this leg the SILENT one. The poll below has always been able to
193
- * fail; this hook returned `status` and `timedOut` and nothing else, so a leg
194
- * whose every request was failing looked identical to one still waiting — the
195
- * spinner, forever, with the reason a `console`-less browser away. It is
196
- * surfaced now, together with the wait's own `checkAgain`, because a screen that
197
- * says "we cannot reach the payment" and offers nothing to press is only half
198
- * of an answer.
199
- */
200
- function useHostedResume(tenantSlug?: string): {
201
- order: CheckoutOrder | null;
202
- status: OrderStatus | null;
203
- timedOut: boolean;
204
- error: string | null;
205
- checkAgain: () => void;
206
- } {
207
- const [order] = useState(() => takeHostedOrder(tenantSlug));
208
- const { status, timedOut, error, checkAgain } = usePaymentPolling(order?.orderId ?? null, {
209
- enabled: Boolean(order),
210
- intervalMs: HOSTED_RESUME_FAST_MS,
211
- slowAfterPolls: HOSTED_RESUME_FAST_POLLS,
212
- slowIntervalMs: HOSTED_RESUME_SLOW_MS,
213
- maxWaitMs: HOSTED_RESUME_WINDOW_MS,
214
- });
215
- return { order, status, timedOut, error, checkAgain };
216
- }
217
-
218
- /**
219
- * What the resumed leg contributes to the controller's surface.
220
- *
221
- * `resumeTimedOut` is only ever true on that leg — `useHostedResume` is the
222
- * sole caller that bounds its wait, and a buyer who never left has a card or
223
- * PIX view reporting its own. `resumeError` and `resumeCheckAgain` are the
224
- * transient failure and the buyer's way out of it (FUT-1144).
225
- *
226
- * All three are inert for a checkout that never left this tab: with nothing
227
- * parked the poll is disabled, so the error stays null, the bound never
228
- * elapses, and the action has no wait to restart.
229
- */
230
- function resumeSurface(resume: ReturnType<typeof useHostedResume>): {
231
- resumeTimedOut: boolean;
232
- resumeError: string | null;
233
- resumeCheckAgain: () => void;
234
- } {
235
- return {
236
- resumeTimedOut: resume.timedOut,
237
- resumeError: resume.error,
238
- resumeCheckAgain: resume.checkAgain,
239
- };
240
- }
241
-
242
- /**
243
- * Fire the host's `onPaid` port once the order settles PAID.
244
- *
245
- * FUT-601 made the SERVER empty the cart inside the confirmation transaction —
246
- * but nothing told the SPA, whose cart provider survives every checkout route
247
- * change and kept counting the items the buyer had just bought. The server was
248
- * right and the screen was stale; this is where the host is told to catch up.
249
- *
250
- * PAID only. A FAILED or EXPIRED order fires nothing — that shopper still has
251
- * a basket to retry with, and the host must not be told otherwise.
252
- */
253
- function usePaidPort(settled: OrderStatus | null, onPaid: (() => void) | undefined): void {
254
- useEffect(() => {
255
- if (settled !== "PAID") return;
256
- onPaid?.();
257
- }, [settled, onPaid]);
258
- }
259
-
260
- /**
261
- * The create-order refusal the steps render: what to say, which field to
262
- * highlight, and the machine CODE that decides how it is presented — an
263
- * unresolved charge is not a failed one, and the Pagamento step must not offer
264
- * it a "Tentar novamente" (FUT-563). One hook so the three always move
265
- * together; they were three `useState`s that could be cleared apart.
266
- */
267
- function useCreateFailure() {
268
- const [message, setMessage] = useState<string | null>(null);
269
- const [field, setField] = useState<BuyerField | null>(null);
270
- const [code, setCode] = useState<string | null>(null);
271
- const clear = useCallback(() => {
272
- setMessage(null);
273
- setField(null);
274
- setCode(null);
275
- }, []);
276
- const fail = useCallback(
277
- (next: { message: string; field?: BuyerField | null; code?: string }) => {
278
- setMessage(next.message);
279
- setField(next.field ?? null);
280
- setCode(next.code ?? null);
281
- },
282
- [],
283
- );
284
- return { message, field, code, clear, fail };
285
- }
286
-
287
70
  /**
288
71
  * All checkout state + handlers, so the checkout flow stays presentational.
289
72
  * - `setMethod` drops any order raised for the previous method (no stale QR/form).
@@ -295,6 +78,8 @@ function useCreateFailure() {
295
78
  * being saved.
296
79
  * - `startPayment` raises the order (PIX → QR, CARD → chargeable); `payWithEmail`
297
80
  * re-raises with a corrected e-mail for the merchant-email rejection.
81
+ * - `retry` re-charges the SAME order when the refusal says another instrument
82
+ * could work (FUT-1145), and raises a fresh one when it does not.
298
83
  *
299
84
  * @param taxIdOnFile The buyer already has a CPF saved (FUT-465) ⇒ the Dados
300
85
  * step has nothing left to ask, so checkout opens on Pagamento and `back`
@@ -304,6 +89,9 @@ function useCreateFailure() {
304
89
  * @param buyerFields What the store's chain declares it needs from the buyer
305
90
  * (FUT-595). Absent ⇒ CPF-required, which is what this gate demanded before
306
91
  * there was a declaration to read — never "ask nothing".
92
+ * @param basket WHICH basket this checkout is for (FUT-1213). Absent ⇒ the
93
+ * pre-1213 behaviour: a parked payment resumes on whatever checkout mounts
94
+ * next. See `./basket.ts` for why it is a signature of the lines.
307
95
  */
308
96
  export function useCheckoutController(
309
97
  ports: CheckoutHostPorts,
@@ -311,72 +99,78 @@ export function useCheckoutController(
311
99
  taxIdOnFile = false,
312
100
  buyerFields: readonly CheckoutCustomerField[] = CPF_ONLY,
313
101
  tenantSlug?: string,
102
+ basket?: CheckoutBasketIdentity,
314
103
  ) {
315
104
  const { createOrder, saveBuyerContact, onExitToMenu, onPaid } = ports;
316
105
  const validation = useCheckoutCopy().screens.validation;
317
106
  const navigate = useCheckoutNavigate();
318
- const resume = useHostedResume(tenantSlug);
319
- const [step, setStep] = useState<Step>(initialStep(Boolean(resume.order), taxIdOnFile));
107
+ const resume = useHostedResume(tenantSlug, basket);
108
+ const [step, setStep] = useState<Step>(taxIdOnFile ? "payment" : "dados");
320
109
  // No method pre-selected: the Pagamento step shows just the picker until the
321
110
  // buyer chooses PIX or card, then that method's order is raised and its UI
322
111
  // revealed. Avoids raising a throwaway PIX charge for a buyer who wants card.
323
112
  const [method, setMethodState] = useState<PaymentMethod | null>(null);
324
113
  const [buyer, setBuyerState] = useState<BuyerInfo>(defaultBuyer ?? {});
325
114
  const [saveProfile, setSaveProfile] = useState(true);
326
- const [order, setOrder] = useState<CheckoutOrder | null>(resume.order);
115
+ const [order, setOrder] = useState<CheckoutOrder | null>(null);
327
116
  const [finalStatus, setFinalStatus] = useState<OrderStatus | null>(null);
117
+ const [decline, setDecline] = useState<CheckoutDecline | null>(null);
118
+ const [freshInstrument, setFreshInstrument] = useState(false);
328
119
  const [creating, setCreating] = useState(false);
329
120
  const failure = useCreateFailure();
121
+ useResumedCheckout(resume, setOrder, setStep, setFinalStatus, setMethodState);
330
122
 
331
123
  const clearError = failure.clear;
332
124
  const setBuyer = useCallback((next: BuyerInfo) => { setBuyerState(next); clearError(); }, [clearError]);
333
125
  const { back, editBuyer } = useCheckoutNav(taxIdOnFile, onExitToMenu, setStep);
334
126
  const setMethod = useCallback((next: PaymentMethod) => {
335
- setMethodState((prev) => { if (prev !== next) { setOrder(null); clearError(); } return next; });
336
- }, [clearError]);
337
- const goToPayment = useCallback(() => {
338
- clearError();
339
- const complaint = buyerGateError(validation, buyer, buyerFields, taxIdOnFile);
340
- if (complaint) { failure.fail(complaint); return; }
341
- // Persist the buyer's details HERE, on "Continuar" — not when a payment is
342
- // raised. Everything after this step can fail (no provider configured, a
343
- // declined card, an abandoned PIX, a closed tab) and the details must
344
- // survive all of it. Fire-and-forget on purpose: making the buyer wait on
345
- // the write or blocking them when it fails — would trade the bug for a
346
- // worse one. Gated on the "salvar meus dados" consent (LGPD), which is
347
- // what the checkbox means.
348
- if (saveProfile) {
349
- saveBuyerContact?.({ name: buyer.name, phone: buyer.phone, taxId: buyer.taxId });
350
- }
351
- setStep("payment");
352
- }, [buyer, buyerFields, saveProfile, taxIdOnFile, clearError, saveBuyerContact, validation]);
353
- const startPayment = useCallback(async (chosen: PaymentMethod, override?: BuyerInfo) => {
354
- clearError();
355
- setCreating(true);
356
- const result = await createOrder({ method: chosen, buyer: override ?? buyer, saveProfile });
357
- setCreating(false);
358
- if (!result.ok) { failure.fail(result.error); return; }
359
- if (handOverToProvider(result.data, navigate, tenantSlug)) return;
360
- setOrder(result.data);
361
- setFinalStatus(null);
362
- }, [buyer, saveProfile, createOrder, clearError, navigate, tenantSlug]);
127
+ setMethodState((prev) => {
128
+ // SCOPED (FUT-1240): the order dropped here is THIS store's, and so is
129
+ // the parked entry that goes with it. Unscoped, changing method at store
130
+ // B threw away store A's parked hand-off — the same cross-store slot the
131
+ // slug closed on the read side, still open on the write side.
132
+ if (prev !== next) { setOrder(null); forgetHostedOrder(tenantSlug); clearError(); }
133
+ return next;
134
+ });
135
+ }, [clearError, tenantSlug]);
136
+ const goToPayment = useGoToPayment({
137
+ buyer, buyerFields, taxIdOnFile, saveProfile, saveBuyerContact, validation, failure, setStep,
138
+ });
139
+ const startPayment = useStartPayment({
140
+ buyer, saveProfile, createOrder, navigate, tenantSlug, basket, failure,
141
+ setCreating, setDecline, setOrder, setFinalStatus,
142
+ });
363
143
  const payWithEmail = useCallback((email: string) => {
364
144
  if (!method) return;
365
145
  const next = { ...buyer, email };
366
146
  setBuyerState(next);
367
147
  void startPayment(method, next);
368
148
  }, [buyer, method, startPayment]);
369
- const handleResolved = useCallback((s: OrderStatus) => { setFinalStatus(s); setStep("status"); }, []);
370
- const retry = useCallback(() => {
371
- setOrder(null); setFinalStatus(null); clearError(); setStep("payment");
372
- }, [clearError]);
149
+ const handleResolved = useCallback((s: OrderStatus, refusal?: CheckoutDecline | null) => {
150
+ setDecline(refusal ?? null);
151
+ setFinalStatus(s);
152
+ setStep("status");
153
+ }, []);
154
+ const retry = useRetryAction({
155
+ decline, order, clearError, setOrder, setDecline, setFinalStatus, setStep, setFreshInstrument,
156
+ });
373
157
  const completed = useMemo(() => new Set(STEP_ORDER.slice(0, STEP_ORDER.indexOf(step))), [step]);
374
- usePaidPort(finalStatus ?? resume.status, onPaid);
158
+ const settled = finalStatus ?? resume.status;
159
+ useSettledPort(settled, onPaid);
160
+ // The confirmation screen's own wait (FUT-1170). Live only where nothing else
161
+ // is polling and nothing has answered: a resumed leg brings its own wait, and
162
+ // every method's screen polls for itself on the payment step.
163
+ const confirming = useConfirmationWait({
164
+ orderId: order?.orderId ?? null,
165
+ active: step === "status" && settled === null && resume.order === null,
166
+ onSettled: setFinalStatus,
167
+ });
375
168
 
376
169
  return {
377
170
  step, setStep, method, setMethod, buyer, setBuyer, saveProfile, setSaveProfile,
378
- order, finalStatus: finalStatus ?? resume.status, creating,
379
- ...resumeSurface(resume),
171
+ order, finalStatus: settled, creating,
172
+ decline, freshInstrument,
173
+ ...resumeSurface(resume, confirming),
380
174
  createError: failure.message, errorField: failure.field, errorCode: failure.code,
381
175
  goToMenu: onExitToMenu, back, editBuyer,
382
176
  goToPayment, startPayment, payWithEmail, handleResolved, retry, completed,