@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/payments-frontend",
3
- "version": "3.25.0",
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
- /** The headline block: icon, outcome, and one supporting line. */
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
- <Box sx={{ color: TONE_COLOR[visual.tone], display: "flex" }}>{visual.icon}</Box>
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 copy={copy} status={view.effective} wait={view.wait} decline={view.decline} />
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
  );
@@ -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
- /** Host content under the paid receipt (the storefront's PWA install invite). */
148
- confirmation?: { extra?: ReactNode };
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