@12-apps/payments-frontend 3.21.4 → 3.22.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 (31) 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 +341 -0
  6. package/src/components/checkout/checkout-flow.tsx +112 -18
  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/dados-step.tsx +141 -0
  11. package/src/components/checkout/decline.ts +48 -0
  12. package/src/components/checkout/en-US.ts +33 -0
  13. package/src/components/checkout/hosted-return.ts +190 -206
  14. package/src/components/checkout/hosted-store.ts +269 -0
  15. package/src/components/checkout/payment-status-parts.tsx +311 -0
  16. package/src/components/checkout/payment-status.tsx +69 -264
  17. package/src/components/checkout/providers/types.ts +20 -3
  18. package/src/components/checkout/pt-BR.ts +33 -0
  19. package/src/components/checkout/screens-copy.ts +14 -0
  20. package/src/components/checkout/screens-en-US.ts +1 -0
  21. package/src/components/checkout/screens-pt-BR.ts +3 -0
  22. package/src/components/checkout/transport.ts +21 -1
  23. package/src/components/checkout/types.ts +24 -0
  24. package/src/components/checkout/use-card-checkout.ts +31 -31
  25. package/src/components/checkout/use-checkout-controller.ts +51 -271
  26. package/src/components/checkout/use-hosted-resume.ts +326 -0
  27. package/src/components/checkout/view-copy.ts +32 -0
  28. package/src/components/checkout/wallet-pane.tsx +3 -0
  29. package/src/flows/create-payment-flows.tsx +7 -0
  30. package/src/flows/screens-hosted.tsx +19 -2
  31. package/src/index.ts +22 -0
@@ -0,0 +1,141 @@
1
+ import { Box } from "@mui/material";
2
+ import type { JSX, ReactNode } from "react";
3
+
4
+ import { BuyerInfoForm } from "./buyer-info-form";
5
+ import { LockOutlinedIcon } from "./icons";
6
+ import { displayTotals, PayBarTotal } from "./checkout-totals";
7
+ import type { BuyerField, BuyerInfo, CheckoutCustomerField } from "./types";
8
+ import { useCheckoutComponents } from "./ui";
9
+ import type { DadosStepCopy } from "./view-copy";
10
+
11
+ /**
12
+ * Step 1 of the buyer checkout, and its sticky bar.
13
+ *
14
+ * Split out of `./checkout-steps.tsx` when the Pagamento step grew a money line
15
+ * of its own (FUT-1179) and took that file past its size gate. The seam is the
16
+ * one the flow already renders on: one step per module, with the totals both of
17
+ * them show living in `./checkout-totals.tsx`.
18
+ */
19
+
20
+ /**
21
+ * Step 1 "Dados" — the buyer's register info (CPF plus optional name/email/
22
+ * phone; contact pre-filled from the saved buyer profile). NO payment method
23
+ * here (that's step 2); nav lives in the slim checkout header. "Continuar"
24
+ * (sticky, with the live total) validates the CPF and advances to "Pagamento"
25
+ * — no charge yet.
26
+ */
27
+ export function DadosStep({
28
+ copy,
29
+ buyer,
30
+ onBuyerChange,
31
+ saveProfile,
32
+ onSaveProfileChange,
33
+ createError,
34
+ errorField,
35
+ onContinue,
36
+ cartTotals,
37
+ buyerFields,
38
+ discountLines,
39
+ totalOverride,
40
+ }: {
41
+ /** The step's own sentences — the HOST's words (see `./view-copy`). */
42
+ copy: DadosStepCopy;
43
+ buyer: BuyerInfo;
44
+ onBuyerChange: (buyer: BuyerInfo) => void;
45
+ saveProfile: boolean;
46
+ onSaveProfileChange: (value: boolean) => void;
47
+ createError: string | null;
48
+ errorField: BuyerField | null;
49
+ onContinue: () => void;
50
+ /** The host cart's own totals — what the pay bar shows in cart mode. */
51
+ cartTotals: { totalLabel: string; totalItems: number };
52
+ /** What the store's chain declares it needs (FUT-595); absent ⇒ CPF-required. */
53
+ buyerFields?: readonly CheckoutCustomerField[];
54
+ /**
55
+ * The saving, itemized under the total the buyer is about to authorize
56
+ * (FUT-246) — RENDERED BY THE HOST from its cart (the storefront passes its
57
+ * cart footer's money block), never re-implemented here, so the two surfaces
58
+ * can never word the same discount differently.
59
+ */
60
+ discountLines?: ReactNode;
61
+ /** Settling an open balance: totals come from the settlement, not the cart. */
62
+ totalOverride?: { label: string; items: number };
63
+ }): JSX.Element {
64
+ const { Checkbox } = useCheckoutComponents();
65
+ const { label: totalLabel, items: totalItems } = displayTotals(totalOverride, cartTotals);
66
+
67
+ return (
68
+ <>
69
+ <Box sx={{ display: "flex", flexDirection: "column", gap: 3, pb: { xs: createError ? 22 : 14, sm: createError ? 20 : 12 } }}>
70
+ <BuyerInfoForm
71
+ value={buyer}
72
+ onChange={onBuyerChange}
73
+ fields={buyerFields}
74
+ fieldError={errorField && createError ? { field: errorField, message: createError } : null}
75
+ />
76
+ <Checkbox
77
+ checked={saveProfile}
78
+ onChange={(_event, checked) => onSaveProfileChange(checked)}
79
+ label={copy.saveProfile}
80
+ data-testid="buyer-save-profile"
81
+ />
82
+ </Box>
83
+
84
+ <DadosPayBar
85
+ copy={copy}
86
+ totalLabel={totalLabel}
87
+ totalItems={totalItems}
88
+ createError={createError}
89
+ onContinue={onContinue}
90
+ >
91
+ {/* Suppressed while settling a balance: those totals come from the
92
+ frozen ticket, not the cart. */}
93
+ {totalOverride ? null : discountLines}
94
+ </DadosPayBar>
95
+ </>
96
+ );
97
+ }
98
+
99
+ /** The sticky "Continuar" bar: the refusal, the money, and the one action. */
100
+ function DadosPayBar({
101
+ copy,
102
+ totalLabel,
103
+ totalItems,
104
+ createError,
105
+ onContinue,
106
+ children,
107
+ }: {
108
+ copy: DadosStepCopy;
109
+ totalLabel: string;
110
+ totalItems: number;
111
+ createError: string | null;
112
+ onContinue: () => void;
113
+ children?: ReactNode;
114
+ }): JSX.Element {
115
+ const { ActionBar, Alert, Button, Text } = useCheckoutComponents();
116
+ return (
117
+ <ActionBar dataTestId="checkout-pay-bar">
118
+ <Box sx={{ width: "100%", display: "flex", flexDirection: "column", gap: 1.5 }}>
119
+ {createError ? (
120
+ <Alert variant="danger" title={copy.cannotContinueTitle} description={createError} showIcon data-testid="checkout-error" />
121
+ ) : null}
122
+ <Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
123
+ <PayBarTotal totalLabel={totalLabel} totalItems={totalItems}>{children}</PayBarTotal>
124
+ <Box sx={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 0.5, minWidth: 0 }}>
125
+ <Button variant="solid" color="primary" size="lg" fullWidth onClick={onContinue} dataTestId="checkout-continue">
126
+ {copy.continueAction}
127
+ </Button>
128
+ {copy.secureNotice ? (
129
+ <Box sx={{ display: "flex", alignItems: "center", gap: 0.5, color: "text.secondary" }}>
130
+ <LockOutlinedIcon sx={{ fontSize: 13 }} />
131
+ <Text variant="caption" size="xs" color="secondary" as="span">
132
+ {copy.secureNotice}
133
+ </Text>
134
+ </Box>
135
+ ) : null}
136
+ </Box>
137
+ </Box>
138
+ </Box>
139
+ </ActionBar>
140
+ );
141
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * HOW A REFUSED CARD IS DESCRIBED (FUT-1145).
3
+ *
4
+ * Its own module rather than another block in `./types.ts`, which is at its
5
+ * size ceiling — and because this is one idea with two halves that must travel
6
+ * together: what happened, and whether trying again could ever work.
7
+ */
8
+
9
+ /**
10
+ * WHY a card was refused, in the cross-provider vocabulary the server
11
+ * normalizes every acquirer's own code into (FUT-1145).
12
+ *
13
+ * A MIRROR of `@12-apps/payments-backend`'s `DeclineReason`, restated rather
14
+ * than imported for the reason every other mirror in `./types.ts` is: the two
15
+ * packages version independently, so a server one release ahead can answer a
16
+ * reason this bundle has never heard of. Everything that reads it degrades to
17
+ * the generic refusal rather than rendering nothing — see
18
+ * `PaymentStatusCopy.declined`. `declines.test.ts` pins the mirror against the
19
+ * backend union, so a reason ADDED there fails a test here rather than going
20
+ * quietly unworded.
21
+ */
22
+ export type CheckoutDeclineReason =
23
+ | "INSUFFICIENT_FUNDS"
24
+ | "CARD_DECLINED"
25
+ | "INVALID_CARD"
26
+ | "EXPIRED_CARD"
27
+ | "FRAUD_SUSPECTED"
28
+ | "PROVIDER_ERROR"
29
+ | "UNKNOWN";
30
+
31
+ /**
32
+ * A refusal as the checkout has to present it (FUT-1145): what happened, and
33
+ * whether another attempt could ever work.
34
+ *
35
+ * The two answer different questions and neither can carry the other. "Attempts
36
+ * exhausted — do not retry" and "the standing authorization was cancelled at
37
+ * the bank" are both terminal for completely different causes, and a
38
+ * seven-value taxonomy has to flatten one of them into a lie; meanwhile a
39
+ * screen deciding whether to OFFER a retry needs the verdict and not the cause.
40
+ *
41
+ * `retriable` UNDEFINED means the provider offered no guidance, which is not
42
+ * the same as "no": the retry stays on offer, because withholding it on
43
+ * silence would strand a buyer whose card is fine.
44
+ */
45
+ export interface CheckoutDecline {
46
+ reason?: CheckoutDeclineReason;
47
+ retriable?: boolean;
48
+ }
@@ -48,9 +48,42 @@ export const EN_US_PAYMENT_STATUS_COPY: PaymentStatusCopy = {
48
48
  "We are still trying. If you have already paid, do not pay again — " +
49
49
  "the order is confirmed as soon as the provider tells us.",
50
50
  },
51
+ /**
52
+ * One refusal at a time, in the cardholder's own terms (FUT-1145).
53
+ *
54
+ * `UNKNOWN` is deliberately absent: with no recognised reason there is
55
+ * nothing specific to say, and `failed` above is already that sentence.
56
+ */
57
+ declined: {
58
+ INSUFFICIENT_FUNDS: {
59
+ heading: "There were not enough funds",
60
+ support: "Nothing was charged. Try another card.",
61
+ },
62
+ CARD_DECLINED: {
63
+ heading: "Your bank did not authorise the payment",
64
+ support: "Nothing was charged. Try another card, or talk to your bank.",
65
+ },
66
+ INVALID_CARD: {
67
+ heading: "The card details were not accepted",
68
+ support: "Nothing was charged. Check the number, the expiry and the CVV, or use another card.",
69
+ },
70
+ EXPIRED_CARD: {
71
+ heading: "That card has expired",
72
+ support: "Nothing was charged. Use a card that is still in date.",
73
+ },
74
+ FRAUD_SUSPECTED: {
75
+ heading: "Your bank blocked this purchase for security",
76
+ support: "Nothing was charged. Talk to your bank, or use another card.",
77
+ },
78
+ PROVIDER_ERROR: {
79
+ heading: "We could not process the payment right now",
80
+ support: "Nothing was charged. Try again in a moment.",
81
+ },
82
+ },
51
83
  retryAction: "Try again",
52
84
  regenerateAction: "Generate a new code",
53
85
  checkAgainAction: "Check again",
86
+ notPaidAction: "I could not pay",
54
87
  backAction: "Back to the menu",
55
88
  amountLabel: "Amount paid",
56
89
  referenceLabel: "Order",