@12-apps/payments-frontend 3.23.0 → 3.24.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 +1 -1
- package/src/components/checkout/checkout-flow.tsx +13 -2
- package/src/components/checkout/payment-status-parts.tsx +38 -2
- package/src/components/checkout/payment-status.tsx +27 -1
- package/src/components/checkout/resolution-actions.ts +43 -0
- package/src/components/checkout/use-checkout-controller.ts +4 -11
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/payments-frontend",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"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.",
|
|
@@ -7,6 +7,7 @@ import { EmptyCart, PaymentStep } from "./checkout-steps";
|
|
|
7
7
|
import { DadosStep } from "./dados-step";
|
|
8
8
|
import { ArrowBackIcon } from "./icons";
|
|
9
9
|
import { PaymentStatus } from "./payment-status";
|
|
10
|
+
import type { BackActionEmphasis } from "./payment-status-parts";
|
|
10
11
|
import type { BuyerInfo, CheckoutProviderConfig, SettlementCheckout } from "./types";
|
|
11
12
|
import { CheckoutCopyProvider } from "./copy-context";
|
|
12
13
|
import { OneClickProvider, useOneClick } from "./one-click";
|
|
@@ -94,6 +95,10 @@ export interface CheckoutFlowProps extends CheckoutHostPorts {
|
|
|
94
95
|
validateApplePayMerchant?: (validationURL: string) => Promise<unknown>;
|
|
95
96
|
/** Host content shown on the paid confirmation (the storefront's install invite). */
|
|
96
97
|
confirmationExtra?: ReactNode;
|
|
98
|
+
/** Host content shown AFTER the confirmation's actions — `PaymentStatusProps.paidFooter`. */
|
|
99
|
+
confirmationFooter?: ReactNode;
|
|
100
|
+
/** Whether the way out leads the paid screen — see {@link BackActionEmphasis}. */
|
|
101
|
+
backActionEmphasis?: BackActionEmphasis;
|
|
97
102
|
/** Design-system slots; unfilled slots render the raw-MUI defaults. */
|
|
98
103
|
components?: Partial<CheckoutComponents>;
|
|
99
104
|
}
|
|
@@ -180,12 +185,16 @@ function StatusStep({
|
|
|
180
185
|
settlement,
|
|
181
186
|
cart,
|
|
182
187
|
confirmationExtra,
|
|
188
|
+
confirmationFooter,
|
|
189
|
+
backActionEmphasis,
|
|
183
190
|
}: {
|
|
184
191
|
copy: CheckoutViewCopy;
|
|
185
192
|
c: ReturnType<typeof useCheckoutController>;
|
|
186
193
|
settlement: SettlementCheckout | null | undefined;
|
|
187
194
|
cart: CheckoutCartView;
|
|
188
195
|
confirmationExtra: ReactNode;
|
|
196
|
+
confirmationFooter: ReactNode;
|
|
197
|
+
backActionEmphasis: BackActionEmphasis | undefined;
|
|
189
198
|
}): JSX.Element {
|
|
190
199
|
return (
|
|
191
200
|
<PaymentStatus
|
|
@@ -197,6 +206,8 @@ function StatusStep({
|
|
|
197
206
|
onRegenerate={() => { c.setStep("payment"); void c.startPayment("PIX"); }}
|
|
198
207
|
onBackToMenu={c.goToMenu}
|
|
199
208
|
paidExtra={confirmationExtra}
|
|
209
|
+
paidFooter={confirmationFooter}
|
|
210
|
+
backActionEmphasis={backActionEmphasis}
|
|
200
211
|
awaitingTimedOut={c.awaitingTimedOut}
|
|
201
212
|
// How the wait behind this screen is going, and the way out of it
|
|
202
213
|
// (FUT-1144). It is the resumed leg's wait for a checkout that came back
|
|
@@ -297,7 +308,7 @@ function PagamentoStep({
|
|
|
297
308
|
}
|
|
298
309
|
|
|
299
310
|
function CheckoutFlowBody(props: Omit<CheckoutFlowProps, "components">): JSX.Element {
|
|
300
|
-
const { copy, cart, defaultBuyer, settlement, taxIdOnFile = false, providerConfig, tenantSlug, confirmationExtra, validateApplePayMerchant, oneClick = false, ...ports } = props;
|
|
311
|
+
const { copy, cart, defaultBuyer, settlement, taxIdOnFile = false, providerConfig, tenantSlug, confirmationExtra, confirmationFooter, backActionEmphasis, validateApplePayMerchant, oneClick = false, ...ports } = props;
|
|
301
312
|
// Resolved for NO method on purpose (FUT-595): the Dados step opens before
|
|
302
313
|
// the picker, and the form is filled once — so it asks for the union of what
|
|
303
314
|
// any chain member may need rather than re-opening after the choice. A chain
|
|
@@ -354,7 +365,7 @@ function CheckoutFlowBody(props: Omit<CheckoutFlowProps, "components">): JSX.Ele
|
|
|
354
365
|
) : null}
|
|
355
366
|
|
|
356
367
|
{c.step === "status" ? (
|
|
357
|
-
<StatusStep copy={copy} c={c} settlement={settlement} cart={cart} confirmationExtra={confirmationExtra} />
|
|
368
|
+
<StatusStep copy={copy} c={c} settlement={settlement} cart={cart} confirmationExtra={confirmationExtra} confirmationFooter={confirmationFooter} backActionEmphasis={backActionEmphasis} />
|
|
358
369
|
) : null}
|
|
359
370
|
</Box>
|
|
360
371
|
);
|
|
@@ -234,6 +234,38 @@ function retryable(decline: CheckoutDecline | null): boolean {
|
|
|
234
234
|
return decline?.retriable !== false;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
/**
|
|
238
|
+
* How much of the eye the way out asks for on a PAID confirmation.
|
|
239
|
+
*
|
|
240
|
+
* `"primary"` is the default and the historical look: back-to-menu is the only
|
|
241
|
+
* control a settled screen carries, so it wears the solid fill. A host that
|
|
242
|
+
* renders an action of its OWN in {@link PaymentStatusProps.paidExtra} — a
|
|
243
|
+
* route to the order it just raised, say — has two controls on one screen, and
|
|
244
|
+
* only one of them may lead. `"secondary"` is how such a host says which.
|
|
245
|
+
*
|
|
246
|
+
* A prop rather than a guess, because the package cannot make it: whether the
|
|
247
|
+
* buyer's next move is "look at the pedido" or "buy something else" is a fact
|
|
248
|
+
* about the way this shop serves people, and nothing in a payment knows it.
|
|
249
|
+
* Every other status is unaffected — an unsettled screen already paints this
|
|
250
|
+
* button as the quiet way out, and the retry beside it is the lead.
|
|
251
|
+
*/
|
|
252
|
+
export type BackActionEmphasis = "primary" | "secondary";
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The back-to-menu button's look, from the outcome and the host's emphasis.
|
|
256
|
+
*
|
|
257
|
+
* Its own function so the two attributes cannot disagree: they moved together
|
|
258
|
+
* as a pair of inline ternaries, and a third condition would have made that
|
|
259
|
+
* four places to keep in step.
|
|
260
|
+
*/
|
|
261
|
+
function backLook(
|
|
262
|
+
status: OrderStatus,
|
|
263
|
+
emphasis: BackActionEmphasis,
|
|
264
|
+
): { variant: "solid" | "outline"; color: "primary" | "neutral" } {
|
|
265
|
+
const leads = status === "PAID" && emphasis === "primary";
|
|
266
|
+
return leads ? { variant: "solid", color: "primary" } : { variant: "outline", color: "neutral" };
|
|
267
|
+
}
|
|
268
|
+
|
|
237
269
|
/** The next-action row: retry / regenerate / check-again, always back-to-menu. */
|
|
238
270
|
export function StatusActions({
|
|
239
271
|
copy,
|
|
@@ -244,6 +276,7 @@ export function StatusActions({
|
|
|
244
276
|
onCheckAgain,
|
|
245
277
|
onNotPaid,
|
|
246
278
|
onBackToMenu,
|
|
279
|
+
backActionEmphasis = "primary",
|
|
247
280
|
}: {
|
|
248
281
|
copy: PaymentStatusCopy;
|
|
249
282
|
status: OrderStatus;
|
|
@@ -259,8 +292,11 @@ export function StatusActions({
|
|
|
259
292
|
/** The buyer's "I did not pay" (FUT-1146) — present only while it applies. */
|
|
260
293
|
onNotPaid?: () => void;
|
|
261
294
|
onBackToMenu: () => void;
|
|
295
|
+
/** Whether back-to-menu leads a PAID screen. See {@link BackActionEmphasis}. */
|
|
296
|
+
backActionEmphasis?: BackActionEmphasis;
|
|
262
297
|
}): JSX.Element {
|
|
263
298
|
const { Button } = useCheckoutComponents();
|
|
299
|
+
const back = backLook(status, backActionEmphasis);
|
|
264
300
|
return (
|
|
265
301
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
|
266
302
|
{onCheckAgain ? (
|
|
@@ -298,8 +334,8 @@ export function StatusActions({
|
|
|
298
334
|
<Button
|
|
299
335
|
// Full width and last, so the thumb lands on the same place in every
|
|
300
336
|
// outcome instead of hunting a button that moves with the state.
|
|
301
|
-
variant={
|
|
302
|
-
color={
|
|
337
|
+
variant={back.variant}
|
|
338
|
+
color={back.color}
|
|
303
339
|
size="lg"
|
|
304
340
|
onClick={onBackToMenu}
|
|
305
341
|
dataTestId="payment-back-to-menu"
|
|
@@ -2,7 +2,13 @@ import { Box } from "@mui/material";
|
|
|
2
2
|
import type { JSX, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import type { CheckoutDecline } from "./decline";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
OutcomeHero,
|
|
7
|
+
PaidFacts,
|
|
8
|
+
StatusActions,
|
|
9
|
+
type BackActionEmphasis,
|
|
10
|
+
type WaitState,
|
|
11
|
+
} from "./payment-status-parts";
|
|
6
12
|
import type { OrderStatus } from "./types";
|
|
7
13
|
import { useCheckoutComponents } from "./ui";
|
|
8
14
|
import type { PaymentStatusCopy } from "./view-copy";
|
|
@@ -50,6 +56,23 @@ interface PaymentStatusProps {
|
|
|
50
56
|
* is the difference between an offer and an interruption.
|
|
51
57
|
*/
|
|
52
58
|
paidExtra?: ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Host content rendered AFTER the action row, and ONLY on PAID.
|
|
61
|
+
*
|
|
62
|
+
* The other half of {@link paidExtra}, and the difference between them is the
|
|
63
|
+
* actions. What a buyer has to DO next belongs beside the way out, in one
|
|
64
|
+
* block: a panel dropped between two controls splits the only decision on the
|
|
65
|
+
* screen in half. What is merely OFFERED — an install invite, an ask to turn
|
|
66
|
+
* on alerts — belongs after that block, because it is about everything that
|
|
67
|
+
* happens once this screen is closed and must not come between a shopper and
|
|
68
|
+
* the order they just paid for.
|
|
69
|
+
*/
|
|
70
|
+
paidFooter?: ReactNode;
|
|
71
|
+
/**
|
|
72
|
+
* Whether back-to-menu leads a PAID screen (default) or stands down for an
|
|
73
|
+
* action the host renders in {@link paidExtra}. See {@link BackActionEmphasis}.
|
|
74
|
+
*/
|
|
75
|
+
backActionEmphasis?: BackActionEmphasis;
|
|
53
76
|
/**
|
|
54
77
|
* The wait has been given up on. Only meaningful while AWAITING_PAYMENT;
|
|
55
78
|
* every other status has already resolved, so a stale flag cannot change what
|
|
@@ -180,7 +203,10 @@ export function PaymentStatus(props: PaymentStatusProps): JSX.Element {
|
|
|
180
203
|
onCheckAgain={view.checkAgain}
|
|
181
204
|
onNotPaid={view.notPaid}
|
|
182
205
|
onBackToMenu={onBackToMenu}
|
|
206
|
+
backActionEmphasis={props.backActionEmphasis}
|
|
183
207
|
/>
|
|
208
|
+
|
|
209
|
+
{view.paid ? props.paidFooter : null}
|
|
184
210
|
</Box>
|
|
185
211
|
);
|
|
186
212
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useCallback, type Dispatch, type SetStateAction } from "react";
|
|
2
|
+
|
|
3
|
+
import type { Step } from "./checkout-actions";
|
|
4
|
+
import type { CheckoutDecline } from "./decline";
|
|
5
|
+
import type { BuyerInfo, OrderStatus, PaymentMethod } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The two actions the payment step raises on its own: paying once an e-mail
|
|
9
|
+
* has been typed, and reporting the result the provider's screen reached.
|
|
10
|
+
*
|
|
11
|
+
* Extracted from `useCheckoutController` for the same reason `useRetryAction`
|
|
12
|
+
* and `useGoToPayment` were: the controller is the one function that has to
|
|
13
|
+
* hold every piece of checkout state at once, and each action it also spells
|
|
14
|
+
* out inline is a line it cannot spend on the state. FUT-1170 and FUT-1240
|
|
15
|
+
* together pushed it past the size gate; these two callbacks are the part that
|
|
16
|
+
* reads the same wherever it lives.
|
|
17
|
+
*/
|
|
18
|
+
export function useResolutionActions(input: {
|
|
19
|
+
buyer: BuyerInfo;
|
|
20
|
+
method: PaymentMethod | null;
|
|
21
|
+
startPayment: (method: PaymentMethod, buyer: BuyerInfo) => Promise<void>;
|
|
22
|
+
setBuyerState: Dispatch<SetStateAction<BuyerInfo>>;
|
|
23
|
+
setDecline: Dispatch<SetStateAction<CheckoutDecline | null>>;
|
|
24
|
+
setFinalStatus: Dispatch<SetStateAction<OrderStatus | null>>;
|
|
25
|
+
setStep: Dispatch<SetStateAction<Step>>;
|
|
26
|
+
}): {
|
|
27
|
+
payWithEmail: (email: string) => void;
|
|
28
|
+
handleResolved: (status: OrderStatus, refusal?: CheckoutDecline | null) => void;
|
|
29
|
+
} {
|
|
30
|
+
const { buyer, method, startPayment, setBuyerState, setDecline, setFinalStatus, setStep } = input;
|
|
31
|
+
const payWithEmail = useCallback((email: string) => {
|
|
32
|
+
if (!method) return;
|
|
33
|
+
const next = { ...buyer, email };
|
|
34
|
+
setBuyerState(next);
|
|
35
|
+
void startPayment(method, next);
|
|
36
|
+
}, [buyer, method, setBuyerState, startPayment]);
|
|
37
|
+
const handleResolved = useCallback((status: OrderStatus, refusal?: CheckoutDecline | null) => {
|
|
38
|
+
setDecline(refusal ?? null);
|
|
39
|
+
setFinalStatus(status);
|
|
40
|
+
setStep("status");
|
|
41
|
+
}, [setDecline, setFinalStatus, setStep]);
|
|
42
|
+
return { payWithEmail, handleResolved };
|
|
43
|
+
}
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
OrderStatus,
|
|
29
29
|
PaymentMethod,
|
|
30
30
|
} from "./types";
|
|
31
|
+
import { useResolutionActions } from "./resolution-actions";
|
|
31
32
|
import { useHostedResume } from "./use-hosted-resume";
|
|
32
33
|
|
|
33
34
|
const STEP_ORDER: Step[] = ["dados", "payment", "status"];
|
|
@@ -140,17 +141,9 @@ export function useCheckoutController(
|
|
|
140
141
|
buyer, saveProfile, createOrder, navigate, tenantSlug, basket, failure,
|
|
141
142
|
setCreating, setDecline, setOrder, setFinalStatus,
|
|
142
143
|
});
|
|
143
|
-
const payWithEmail =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
setBuyerState(next);
|
|
147
|
-
void startPayment(method, next);
|
|
148
|
-
}, [buyer, method, startPayment]);
|
|
149
|
-
const handleResolved = useCallback((s: OrderStatus, refusal?: CheckoutDecline | null) => {
|
|
150
|
-
setDecline(refusal ?? null);
|
|
151
|
-
setFinalStatus(s);
|
|
152
|
-
setStep("status");
|
|
153
|
-
}, []);
|
|
144
|
+
const { payWithEmail, handleResolved } = useResolutionActions({
|
|
145
|
+
buyer, method, startPayment, setBuyerState, setDecline, setFinalStatus, setStep,
|
|
146
|
+
});
|
|
154
147
|
const retry = useRetryAction({
|
|
155
148
|
decline, order, clearError, setOrder, setDecline, setFinalStatus, setStep, setFreshInstrument,
|
|
156
149
|
});
|
package/src/index.ts
CHANGED
|
@@ -59,6 +59,7 @@ export {
|
|
|
59
59
|
type CheckoutCartView,
|
|
60
60
|
type CheckoutFlowProps,
|
|
61
61
|
} from './components/checkout/checkout-flow';
|
|
62
|
+
export type { BackActionEmphasis } from './components/checkout/payment-status-parts';
|
|
62
63
|
|
|
63
64
|
// ---------------------------------------------------------------------------
|
|
64
65
|
// The MOUNTED buyer checkout (FUT-741) and the checkout PIPELINE (FUT-1240).
|