@12-apps/payments-frontend 3.25.0 → 3.26.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 +10 -2
- package/src/components/checkout/payment-status-parts.tsx +19 -3
- package/src/components/checkout/payment-status.tsx +17 -1
- package/src/flows/create-payment-flows.tsx +1 -0
- package/src/flows/screens-pay.tsx +1 -0
- package/src/flows/types.ts +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/payments-frontend",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.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.",
|
|
@@ -95,6 +95,11 @@ export interface CheckoutFlowProps extends CheckoutHostPorts {
|
|
|
95
95
|
validateApplePayMerchant?: (validationURL: string) => Promise<unknown>;
|
|
96
96
|
/** Host content shown on the paid confirmation (the storefront's install invite). */
|
|
97
97
|
confirmationExtra?: ReactNode;
|
|
98
|
+
/**
|
|
99
|
+
* Host illustration that replaces the paid confirmation's icon —
|
|
100
|
+
* `PaymentStatusProps.paidHero`: pass it only when there is one, and mark it `aria-hidden`.
|
|
101
|
+
*/
|
|
102
|
+
confirmationHero?: ReactNode;
|
|
98
103
|
/** Host content shown AFTER the confirmation's actions — `PaymentStatusProps.paidFooter`. */
|
|
99
104
|
confirmationFooter?: ReactNode;
|
|
100
105
|
/** Whether the way out leads the paid screen — see {@link BackActionEmphasis}. */
|
|
@@ -185,6 +190,7 @@ function StatusStep({
|
|
|
185
190
|
settlement,
|
|
186
191
|
cart,
|
|
187
192
|
confirmationExtra,
|
|
193
|
+
confirmationHero,
|
|
188
194
|
confirmationFooter,
|
|
189
195
|
backActionEmphasis,
|
|
190
196
|
}: {
|
|
@@ -193,6 +199,7 @@ function StatusStep({
|
|
|
193
199
|
settlement: SettlementCheckout | null | undefined;
|
|
194
200
|
cart: CheckoutCartView;
|
|
195
201
|
confirmationExtra: ReactNode;
|
|
202
|
+
confirmationHero: ReactNode;
|
|
196
203
|
confirmationFooter: ReactNode;
|
|
197
204
|
backActionEmphasis: BackActionEmphasis | undefined;
|
|
198
205
|
}): JSX.Element {
|
|
@@ -206,6 +213,7 @@ function StatusStep({
|
|
|
206
213
|
onRegenerate={() => { c.setStep("payment"); void c.startPayment("PIX"); }}
|
|
207
214
|
onBackToMenu={c.goToMenu}
|
|
208
215
|
paidExtra={confirmationExtra}
|
|
216
|
+
paidHero={confirmationHero}
|
|
209
217
|
paidFooter={confirmationFooter}
|
|
210
218
|
backActionEmphasis={backActionEmphasis}
|
|
211
219
|
awaitingTimedOut={c.awaitingTimedOut}
|
|
@@ -308,7 +316,7 @@ function PagamentoStep({
|
|
|
308
316
|
}
|
|
309
317
|
|
|
310
318
|
function CheckoutFlowBody(props: Omit<CheckoutFlowProps, "components">): JSX.Element {
|
|
311
|
-
const { copy, cart, defaultBuyer, settlement, taxIdOnFile = false, providerConfig, tenantSlug, confirmationExtra, confirmationFooter, backActionEmphasis, validateApplePayMerchant, oneClick = false, ...ports } = props;
|
|
319
|
+
const { copy, cart, defaultBuyer, settlement, taxIdOnFile = false, providerConfig, tenantSlug, confirmationExtra, confirmationHero, confirmationFooter, backActionEmphasis, validateApplePayMerchant, oneClick = false, ...ports } = props;
|
|
312
320
|
// Resolved for NO method on purpose (FUT-595): the Dados step opens before
|
|
313
321
|
// the picker, and the form is filled once — so it asks for the union of what
|
|
314
322
|
// any chain member may need rather than re-opening after the choice. A chain
|
|
@@ -365,7 +373,7 @@ function CheckoutFlowBody(props: Omit<CheckoutFlowProps, "components">): JSX.Ele
|
|
|
365
373
|
) : null}
|
|
366
374
|
|
|
367
375
|
{c.step === "status" ? (
|
|
368
|
-
<StatusStep copy={copy} c={c} settlement={settlement} cart={cart} confirmationExtra={confirmationExtra} confirmationFooter={confirmationFooter} backActionEmphasis={backActionEmphasis} />
|
|
376
|
+
<StatusStep copy={copy} c={c} settlement={settlement} cart={cart} confirmationExtra={confirmationExtra} confirmationHero={confirmationHero} confirmationFooter={confirmationFooter} backActionEmphasis={backActionEmphasis} />
|
|
369
377
|
) : null}
|
|
370
378
|
</Box>
|
|
371
379
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box } from "@mui/material";
|
|
2
|
-
import type { JSX } from "react";
|
|
2
|
+
import type { JSX, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import { CheckCircleOutlineIcon, ErrorOutlineIcon, ScheduleIcon } from "./icons";
|
|
5
5
|
import type { CheckoutDecline } from "./decline";
|
|
@@ -120,17 +120,27 @@ function failedOutcome(copy: PaymentStatusCopy, decline: CheckoutDecline | null)
|
|
|
120
120
|
return (reason ? copy.declined?.[reason] : undefined) ?? copy.failed;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* The headline block: icon, outcome, and one supporting line.
|
|
125
|
+
*
|
|
126
|
+
* `hero` replaces the PAID icon with the host's own illustration — a store's
|
|
127
|
+
* mascot celebrating the order, say — and only the icon: the heading and the
|
|
128
|
+
* support line stay the package's. Every other status keeps its icon, because
|
|
129
|
+
* a host celebrating a payment that failed would be telling the buyer the
|
|
130
|
+
* opposite of what happened.
|
|
131
|
+
*/
|
|
124
132
|
export function OutcomeHero({
|
|
125
133
|
copy,
|
|
126
134
|
status,
|
|
127
135
|
wait,
|
|
128
136
|
decline,
|
|
137
|
+
hero,
|
|
129
138
|
}: {
|
|
130
139
|
copy: PaymentStatusCopy;
|
|
131
140
|
status: OrderStatus;
|
|
132
141
|
wait: WaitState;
|
|
133
142
|
decline: CheckoutDecline | null;
|
|
143
|
+
hero?: ReactNode;
|
|
134
144
|
}): JSX.Element {
|
|
135
145
|
const { Text } = useCheckoutComponents();
|
|
136
146
|
const face = status === "AWAITING_PAYMENT" ? awaitingFace(copy, wait) : null;
|
|
@@ -154,7 +164,13 @@ export function OutcomeHero({
|
|
|
154
164
|
}
|
|
155
165
|
sx={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 1, textAlign: "center" }}
|
|
156
166
|
>
|
|
157
|
-
|
|
167
|
+
{status === "PAID" && hero ? (
|
|
168
|
+
<Box data-testid="payment-paid-hero" sx={{ display: "flex" }}>
|
|
169
|
+
{hero}
|
|
170
|
+
</Box>
|
|
171
|
+
) : (
|
|
172
|
+
<Box sx={{ color: TONE_COLOR[visual.tone], display: "flex" }}>{visual.icon}</Box>
|
|
173
|
+
)}
|
|
158
174
|
<Text variant="heading" size="md" weight="bold" as="h2">
|
|
159
175
|
{outcome.heading}
|
|
160
176
|
</Text>
|
|
@@ -56,6 +56,16 @@ interface PaymentStatusProps {
|
|
|
56
56
|
* is the difference between an offer and an interruption.
|
|
57
57
|
*/
|
|
58
58
|
paidExtra?: ReactNode;
|
|
59
|
+
/**
|
|
60
|
+
* Host content that REPLACES the PAID icon at the top of the screen — the
|
|
61
|
+
* storefront's mascot celebrating a buyer's first order or birthday. Absent,
|
|
62
|
+
* the screen is exactly what it was. Never shown on any other status.
|
|
63
|
+
*
|
|
64
|
+
* Pass it only when there is something to show (`cond ? <Mascot /> : undefined`):
|
|
65
|
+
* an element that renders nothing still replaces the icon. It is decorative —
|
|
66
|
+
* the heading beside it says what happened — so mark it `aria-hidden`.
|
|
67
|
+
*/
|
|
68
|
+
paidHero?: ReactNode;
|
|
59
69
|
/**
|
|
60
70
|
* Host content rendered AFTER the action row, and ONLY on PAID.
|
|
61
71
|
*
|
|
@@ -182,7 +192,13 @@ export function PaymentStatus(props: PaymentStatusProps): JSX.Element {
|
|
|
182
192
|
data-timed-out={view.wait.timedOut ? "true" : undefined}
|
|
183
193
|
sx={{ display: "flex", flexDirection: "column", gap: 3, alignItems: "stretch", py: 2 }}
|
|
184
194
|
>
|
|
185
|
-
<OutcomeHero
|
|
195
|
+
<OutcomeHero
|
|
196
|
+
copy={copy}
|
|
197
|
+
status={view.effective}
|
|
198
|
+
wait={view.wait}
|
|
199
|
+
decline={view.decline}
|
|
200
|
+
hero={props.paidHero}
|
|
201
|
+
/>
|
|
186
202
|
|
|
187
203
|
{view.paid ? (
|
|
188
204
|
<PaidFacts copy={copy} totalLabel={totalLabel} orderId={orderId} buyerEmail={buyerEmail} />
|
|
@@ -106,6 +106,7 @@ function buildCheckout(
|
|
|
106
106
|
providerConfig={config}
|
|
107
107
|
tenantSlug={tenantSlug}
|
|
108
108
|
confirmationExtra={runtime.config.confirmation?.extra}
|
|
109
|
+
confirmationHero={runtime.config.confirmation?.hero}
|
|
109
110
|
validateApplePayMerchant={ports.validateApplePayMerchant}
|
|
110
111
|
/>
|
|
111
112
|
);
|
|
@@ -93,6 +93,7 @@ function buildPaymentStatus(runtime: FlowsRuntime): CheckoutScreens["PaymentStat
|
|
|
93
93
|
orderId={payable?.orderId}
|
|
94
94
|
onBackToMenu={backToMenu}
|
|
95
95
|
paidExtra={runtime.config.confirmation?.extra}
|
|
96
|
+
paidHero={runtime.config.confirmation?.hero}
|
|
96
97
|
/>
|
|
97
98
|
</FlowsShell>
|
|
98
99
|
);
|
package/src/flows/types.ts
CHANGED
|
@@ -144,8 +144,11 @@ export interface PaymentFlowsConfig extends CheckoutPipelineConfig {
|
|
|
144
144
|
* makes a new host answer once, at the one call site that knows the answer.
|
|
145
145
|
*/
|
|
146
146
|
copy: CheckoutCopyFE;
|
|
147
|
-
/**
|
|
148
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Host content under the paid receipt (the storefront's PWA install invite),
|
|
149
|
+
* and the host's own illustration in place of the paid icon (`hero`).
|
|
150
|
+
*/
|
|
151
|
+
confirmation?: { extra?: ReactNode; hero?: ReactNode };
|
|
149
152
|
/**
|
|
150
153
|
* Warnings the host reports (Sentry). Default SILENT — never `console`: a
|
|
151
154
|
* library that writes to a buyer's console tells them nothing and tells the
|