@12-apps/payments-frontend 3.33.0 → 3.34.1
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 +3 -1
- package/src/components/checkout/checkout-steps.tsx +48 -59
- package/src/components/checkout/icons.tsx +6 -0
- package/src/components/checkout/method-picker.tsx +51 -89
- package/src/components/checkout/payment-step-layout.tsx +65 -0
- package/src/components/checkout/pix-after-copy.tsx +182 -0
- package/src/components/checkout/pix-pane.tsx +238 -0
- package/src/components/checkout/pix-parts.tsx +79 -0
- package/src/components/checkout/pix-stage.tsx +34 -0
- package/src/components/checkout/pix-view.tsx +131 -160
- package/src/components/checkout/screens-copy.ts +76 -17
- package/src/components/checkout/screens-en-US.ts +38 -13
- package/src/components/checkout/screens-pt-BR.ts +37 -12
- package/src/index.ts +1 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Box } from "@mui/material";
|
|
2
|
-
import { useEffect, useMemo, useState, type JSX } from "react";
|
|
3
|
-
import QRCode from "react-qr-code";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState, type JSX } from "react";
|
|
4
3
|
|
|
5
4
|
import { useCheckoutCopy } from "./copy-context";
|
|
6
|
-
import {
|
|
5
|
+
import { PixAfterCopy } from "./pix-after-copy";
|
|
6
|
+
import { PixPane, type PixTab } from "./pix-pane";
|
|
7
|
+
import { PIX_CONTAINER, PIX_WIDE, useClipboardCopy } from "./pix-parts";
|
|
8
|
+
import { usePixStage } from "./pix-stage";
|
|
7
9
|
import type { PixPaneCopy, SettlingCopy } from "./screens-copy";
|
|
8
10
|
import { StalledWait } from "./stalled-wait";
|
|
9
11
|
import type { CheckoutOrder, OrderStatus, PixCharge } from "./types";
|
|
@@ -11,10 +13,11 @@ import { useCheckoutComponents } from "./ui";
|
|
|
11
13
|
import { usePaymentPolling } from "./use-payment-polling";
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
|
-
* PIX payment view:
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the payment-status
|
|
16
|
+
* PIX payment view: the pane to pay from (tabs on a phone, QR and code side by
|
|
17
|
+
* side on a desktop — `./pix-pane`), and after a copy the wait that follows
|
|
18
|
+
* (`./pix-after-copy`). Polls the order in the background throughout and hands
|
|
19
|
+
* a terminal status up to the parent, which then shows the payment-status
|
|
20
|
+
* screen. The 2026-10-06 redesign moved the layout out; the wait stays here.
|
|
18
21
|
*/
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -65,63 +68,6 @@ function pixWaitMs(expiresAt: string | undefined): number {
|
|
|
65
68
|
return Math.max(0, deadline - Date.now()) + PIX_EXPIRY_GRACE_MS;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
/** The copyable "copia e cola" strip with its copy button. */
|
|
69
|
-
function PixCodeBox({ pix, onCopied }: { pix: PixCharge; onCopied: () => void }): JSX.Element {
|
|
70
|
-
const { Button, Text } = useCheckoutComponents();
|
|
71
|
-
const copy = useCheckoutCopy().screens.pix;
|
|
72
|
-
const [copied, setCopied] = useState(false);
|
|
73
|
-
|
|
74
|
-
const copyCode = async (): Promise<void> => {
|
|
75
|
-
try {
|
|
76
|
-
await navigator.clipboard?.writeText(pix.copyPaste);
|
|
77
|
-
setCopied(true);
|
|
78
|
-
onCopied();
|
|
79
|
-
setTimeout(() => setCopied(false), 2000);
|
|
80
|
-
} catch {
|
|
81
|
-
/* clipboard unavailable — the code is still visible to copy manually */
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<Box
|
|
87
|
-
sx={{
|
|
88
|
-
display: "flex",
|
|
89
|
-
alignItems: "center",
|
|
90
|
-
gap: 1,
|
|
91
|
-
width: "100%",
|
|
92
|
-
maxWidth: 420,
|
|
93
|
-
p: 1,
|
|
94
|
-
borderRadius: 1,
|
|
95
|
-
border: "1px solid",
|
|
96
|
-
borderColor: "divider",
|
|
97
|
-
bgcolor: "background.default",
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
<Text
|
|
101
|
-
variant="code"
|
|
102
|
-
size="xs"
|
|
103
|
-
as="span"
|
|
104
|
-
data-testid="pix-code"
|
|
105
|
-
style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
|
|
106
|
-
>
|
|
107
|
-
{pix.copyPaste}
|
|
108
|
-
</Text>
|
|
109
|
-
<Button
|
|
110
|
-
variant="outline"
|
|
111
|
-
color="neutral"
|
|
112
|
-
size="sm"
|
|
113
|
-
icon={<ContentCopyIcon fontSize="small" />}
|
|
114
|
-
onClick={() => {
|
|
115
|
-
void copyCode();
|
|
116
|
-
}}
|
|
117
|
-
dataTestId="pix-copy"
|
|
118
|
-
>
|
|
119
|
-
{copied ? copy.copiedAction : copy.copyAction}
|
|
120
|
-
</Button>
|
|
121
|
-
</Box>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
71
|
/**
|
|
126
72
|
* Which of the wait's three faces the footer is showing — STOPPED, then STILL
|
|
127
73
|
* TRYING, then working.
|
|
@@ -164,18 +110,17 @@ function PixPollFooter({
|
|
|
164
110
|
error,
|
|
165
111
|
timedOut,
|
|
166
112
|
onCheckAgain,
|
|
167
|
-
|
|
113
|
+
awaiting,
|
|
168
114
|
}: {
|
|
169
115
|
error: string | null;
|
|
170
116
|
timedOut: boolean;
|
|
171
117
|
onCheckAgain: () => void;
|
|
172
|
-
/**
|
|
173
|
-
|
|
118
|
+
/** What the healthy wait says: whose confirmation it is waiting for, or "Verificando…" after a copy. */
|
|
119
|
+
awaiting: string;
|
|
174
120
|
}): JSX.Element {
|
|
175
121
|
const { Text } = useCheckoutComponents();
|
|
176
|
-
const {
|
|
122
|
+
const { settling } = useCheckoutCopy().screens;
|
|
177
123
|
const panel = pixWaitPanel(settling, error, timedOut);
|
|
178
|
-
const awaiting = (manual ? pix.manual?.awaiting : undefined) ?? pix.awaiting;
|
|
179
124
|
if (panel) {
|
|
180
125
|
// The same panel the card and wallet panes show, held to the width of the
|
|
181
126
|
// copy-and-paste strip above it so the centred PIX column stays a column.
|
|
@@ -194,7 +139,7 @@ function PixPollFooter({
|
|
|
194
139
|
return (
|
|
195
140
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1, color: "text.secondary" }}>
|
|
196
141
|
<LoadingDot />
|
|
197
|
-
<Text variant="
|
|
142
|
+
<Text variant="body" size="xs" color="secondary" as="span" data-testid="pix-awaiting">
|
|
198
143
|
{awaiting}
|
|
199
144
|
</Text>
|
|
200
145
|
</Box>
|
|
@@ -202,128 +147,154 @@ function PixPollFooter({
|
|
|
202
147
|
}
|
|
203
148
|
|
|
204
149
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* The footer's caption already names the store's wait, but in a grey line
|
|
208
|
-
* under the QR that a buyer on their way to the bank app does not read. Copying
|
|
209
|
-
* is that moment, so it answers with a notice that stays: the wait can run for
|
|
210
|
-
* minutes, and "volte para esta tela" is the instruction that keeps the buyer
|
|
211
|
-
* where the confirmation will land.
|
|
150
|
+
* The words that depend on who confirms. A store-confirmed Pix must not say
|
|
151
|
+
* "a confirmação é automática": nothing confirms it but the store.
|
|
212
152
|
*/
|
|
213
|
-
function
|
|
214
|
-
|
|
215
|
-
const notice = useCheckoutCopy().screens.pix.manual?.copied;
|
|
216
|
-
if (!manual || !copied || !notice) return null;
|
|
217
|
-
return (
|
|
218
|
-
<Box sx={{ width: "100%", maxWidth: 420, textAlign: "left" }}>
|
|
219
|
-
<Alert
|
|
220
|
-
variant="info"
|
|
221
|
-
title={notice.title}
|
|
222
|
-
description={notice.description}
|
|
223
|
-
showIcon
|
|
224
|
-
data-testid="pix-manual-copied"
|
|
225
|
-
/>
|
|
226
|
-
</Box>
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** The code strip and, once it has been copied, what that copy means. */
|
|
231
|
-
function PixCopyRow({ pix, manual }: { pix: PixCharge; manual: boolean }): JSX.Element {
|
|
232
|
-
const [copied, setCopied] = useState(false);
|
|
233
|
-
return (
|
|
234
|
-
<>
|
|
235
|
-
<PixCodeBox pix={pix} onCopied={() => setCopied(true)} />
|
|
236
|
-
<ManualCopiedNotice manual={manual} copied={copied} />
|
|
237
|
-
</>
|
|
238
|
-
);
|
|
153
|
+
function pixSentences(copy: PixPaneCopy, manual: boolean): Pick<PixPaneCopy, "validUntil" | "awaiting" | "afterCopy"> {
|
|
154
|
+
return (manual ? copy.manual : undefined) ?? copy;
|
|
239
155
|
}
|
|
240
156
|
|
|
241
157
|
/**
|
|
242
|
-
* The
|
|
243
|
-
*
|
|
158
|
+
* The deadline and the wait, rendered once for both layouts: narrow, two
|
|
159
|
+
* centred lines under the pane; wide, one footer row under a hairline with the
|
|
160
|
+
* wait on the left and the deadline on the right.
|
|
244
161
|
*/
|
|
245
|
-
function
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
162
|
+
function PixFooter({ deadline, children }: { deadline: string; children: JSX.Element }): JSX.Element {
|
|
163
|
+
const { Text } = useCheckoutComponents();
|
|
164
|
+
return (
|
|
165
|
+
<Box
|
|
166
|
+
sx={{
|
|
167
|
+
display: "flex",
|
|
168
|
+
flexDirection: "column",
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
gap: 1,
|
|
171
|
+
textAlign: "center",
|
|
172
|
+
[PIX_WIDE]: { flexDirection: "row-reverse", justifyContent: "space-between", textAlign: "left", pt: 2.25, borderTop: "1px solid", borderColor: "divider" },
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
<Text variant="body" size="xs" color="secondary" as="p" data-testid="pix-expiry" style={{ margin: 0 }}>
|
|
176
|
+
{deadline}
|
|
177
|
+
</Text>
|
|
178
|
+
{children}
|
|
179
|
+
</Box>
|
|
180
|
+
);
|
|
250
181
|
}
|
|
251
182
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
onResolved,
|
|
255
|
-
pollIntervalMs = 2500,
|
|
256
|
-
}: {
|
|
257
|
-
order: CheckoutOrder;
|
|
258
|
-
onResolved: (status: OrderStatus) => void;
|
|
259
|
-
pollIntervalMs?: number;
|
|
260
|
-
}): JSX.Element {
|
|
261
|
-
const { Text } = useCheckoutComponents();
|
|
262
|
-
const copy = useCheckoutCopy().screens.pix;
|
|
183
|
+
/** The poll, bounded by the charge's own expiry (FUT-1170). */
|
|
184
|
+
function usePixPolling(order: CheckoutOrder, onResolved: (status: OrderStatus) => void, pollIntervalMs: number) {
|
|
263
185
|
// Fixed once per charge (FUT-1170): the bound is a span, so recomputing it
|
|
264
186
|
// every render would keep pushing the deadline out — and it is an effect
|
|
265
187
|
// dependency, so it would also restart the wait on every render.
|
|
266
188
|
const expiresAt = order.pix?.expiresAt;
|
|
267
189
|
const maxWaitMs = useMemo(() => pixWaitMs(expiresAt), [expiresAt]);
|
|
268
|
-
const
|
|
190
|
+
const polling = usePaymentPolling(order.orderId, {
|
|
269
191
|
intervalMs: pollIntervalMs,
|
|
270
192
|
slowAfterPolls: PIX_FAST_POLLS,
|
|
271
193
|
// Never FASTER than the opening cadence: a host that opens slowly means it.
|
|
272
194
|
slowIntervalMs: Math.max(pollIntervalMs, PIX_SLOW_INTERVAL_MS),
|
|
273
195
|
maxWaitMs,
|
|
274
196
|
});
|
|
275
|
-
|
|
197
|
+
const { status } = polling;
|
|
276
198
|
// Bubble a terminal status up once, so the parent can advance to the status step.
|
|
277
199
|
useEffect(() => {
|
|
278
|
-
if (status && status !== "AWAITING_PAYMENT")
|
|
279
|
-
onResolved(status);
|
|
280
|
-
}
|
|
200
|
+
if (status && status !== "AWAITING_PAYMENT") onResolved(status);
|
|
281
201
|
}, [status, onResolved]);
|
|
202
|
+
return polling;
|
|
203
|
+
}
|
|
282
204
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
205
|
+
function PixBody({ order, pix, poll }: { order: CheckoutOrder; pix: PixCharge; poll: ReturnType<typeof usePixPolling> }): JSX.Element {
|
|
206
|
+
const { Text } = useCheckoutComponents();
|
|
207
|
+
const copy = useCheckoutCopy().screens.pix;
|
|
208
|
+
const stage = usePixStage();
|
|
209
|
+
const [tab, setTab] = useState<PixTab>("copy");
|
|
210
|
+
const { copy: writeClipboard, justCopied } = useClipboardCopy(pix.copyPaste);
|
|
211
|
+
// Leaving the payment step (a method switch, a terminal status) ends the
|
|
212
|
+
// after-copy face with it: the next pane starts on the code.
|
|
213
|
+
const { setAfterCopy } = stage;
|
|
214
|
+
// A clipboard write that settles after the pane is gone must not reopen the
|
|
215
|
+
// after-copy face over whatever replaced it (it would hide the picker).
|
|
216
|
+
const mounted = useRef(true);
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
mounted.current = true;
|
|
219
|
+
return () => {
|
|
220
|
+
mounted.current = false;
|
|
221
|
+
setAfterCopy(false);
|
|
222
|
+
};
|
|
223
|
+
}, [setAfterCopy]);
|
|
291
224
|
|
|
292
225
|
// The locale is the HOST's (FUT-760): it decides what a buyer reads off the
|
|
293
226
|
// clock, so it travels with the sentence it feeds rather than being frozen
|
|
294
227
|
// to the origin host's here.
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
const
|
|
228
|
+
const time = new Date(pix.expiresAt).toLocaleTimeString(copy.expiryLocale, { hour: "2-digit", minute: "2-digit" });
|
|
229
|
+
const sentences = pixSentences(copy, pix.confirmation === "MANUAL");
|
|
230
|
+
const footerLine = (awaiting: string): JSX.Element => (
|
|
231
|
+
<PixPollFooter error={poll.error} timedOut={poll.timedOut} onCheckAgain={poll.checkAgain} awaiting={awaiting} />
|
|
232
|
+
);
|
|
298
233
|
|
|
234
|
+
if (stage.afterCopy) {
|
|
235
|
+
return (
|
|
236
|
+
<PixAfterCopy
|
|
237
|
+
pix={pix}
|
|
238
|
+
words={sentences.afterCopy}
|
|
239
|
+
totalLabel={order.totalLabel}
|
|
240
|
+
deadline={time}
|
|
241
|
+
justCopied={justCopied}
|
|
242
|
+
onCopyAgain={() => void writeClipboard()}
|
|
243
|
+
onShowQr={() => {
|
|
244
|
+
setTab("qr");
|
|
245
|
+
setAfterCopy(false);
|
|
246
|
+
}}
|
|
247
|
+
onPreferCard={stage.preferCard}
|
|
248
|
+
footer={footerLine(copy.verifying)}
|
|
249
|
+
/>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
299
252
|
return (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
sx={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 2, textAlign: "center" }}
|
|
303
|
-
>
|
|
304
|
-
<Text variant="heading" size="md" weight="bold" as="h2">
|
|
253
|
+
<>
|
|
254
|
+
<Text variant="heading" size="md" weight="bold" as="h2" style={{ margin: 0 }}>
|
|
305
255
|
{copy.heading}
|
|
306
256
|
</Text>
|
|
307
|
-
<
|
|
308
|
-
{
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
<
|
|
257
|
+
<PixPane
|
|
258
|
+
pix={pix}
|
|
259
|
+
tab={tab}
|
|
260
|
+
onTab={setTab}
|
|
261
|
+
onCopy={() => {
|
|
262
|
+
// Only a copy the clipboard TOOK moves on: the next face opens on
|
|
263
|
+
// "Código Pix copiado", and a failed write leaves the code on screen
|
|
264
|
+
// to select by hand.
|
|
265
|
+
void writeClipboard().then((ok) => {
|
|
266
|
+
if (ok && mounted.current) setAfterCopy(true);
|
|
267
|
+
});
|
|
268
|
+
}}
|
|
269
|
+
/>
|
|
270
|
+
<PixFooter deadline={sentences.validUntil(time)}>{footerLine(sentences.awaiting)}</PixFooter>
|
|
271
|
+
</>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
321
274
|
|
|
322
|
-
|
|
323
|
-
|
|
275
|
+
export function PixView({
|
|
276
|
+
order,
|
|
277
|
+
onResolved,
|
|
278
|
+
pollIntervalMs = 2500,
|
|
279
|
+
}: {
|
|
280
|
+
order: CheckoutOrder;
|
|
281
|
+
onResolved: (status: OrderStatus) => void;
|
|
282
|
+
pollIntervalMs?: number;
|
|
283
|
+
}): JSX.Element {
|
|
284
|
+
const { Text } = useCheckoutComponents();
|
|
285
|
+
const copy = useCheckoutCopy().screens.pix;
|
|
286
|
+
const poll = usePixPolling(order, onResolved, pollIntervalMs);
|
|
287
|
+
const pix = order.pix;
|
|
288
|
+
if (!pix) {
|
|
289
|
+
return (
|
|
290
|
+
<Text variant="body" size="sm" color="danger" as="p" data-testid="pix-missing">
|
|
291
|
+
{copy.chargeMissing}
|
|
324
292
|
</Text>
|
|
325
|
-
|
|
326
|
-
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return (
|
|
296
|
+
<Box data-testid="pix-view" sx={{ ...PIX_CONTAINER, display: "flex", flexDirection: "column", gap: 2 }}>
|
|
297
|
+
<PixBody order={order} pix={pix} poll={poll} />
|
|
327
298
|
</Box>
|
|
328
299
|
);
|
|
329
300
|
}
|
|
@@ -21,25 +21,35 @@
|
|
|
21
21
|
|
|
22
22
|
/** The method tiles, and what each one promises. */
|
|
23
23
|
export interface MethodPickerCopy {
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* The group's accessible name. Not drawn: the tiles are self-explaining
|
|
26
|
+
* ("Pix", "Cartão") and the heading cost the phone a line above the code
|
|
27
|
+
* (the 2026-10-06 Pix redesign).
|
|
28
|
+
*/
|
|
25
29
|
groupLabel: string;
|
|
26
30
|
/**
|
|
27
|
-
* What each method is CALLED. "
|
|
31
|
+
* What each method is CALLED. "Pix" is a Brazilian instrument and keeps its
|
|
28
32
|
* name everywhere, the way "Visa" does; "Cartão" is just the word for card,
|
|
29
33
|
* and was the tile's label — so both are stated rather than one assumed.
|
|
30
34
|
*/
|
|
31
35
|
pixLabel: string;
|
|
32
36
|
cardLabel: string;
|
|
33
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* What each method is like to use. The checkout's tiles no longer draw it
|
|
39
|
+
* (the 2026-10-06 Pix redesign kept the picker to one line); the flow
|
|
40
|
+
* pipeline's method list (`flows/pipeline/methods.ts`) still says it.
|
|
41
|
+
*/
|
|
34
42
|
pixDescription: string;
|
|
35
43
|
/**
|
|
36
|
-
* PIX's line when the store confirms it by hand
|
|
37
|
-
*
|
|
38
|
-
* to `pixDescription`, so a host enabling such a provider should set it.
|
|
44
|
+
* PIX's line when the store confirms it by hand. Optional; absent falls
|
|
45
|
+
* back to `pixDescription`.
|
|
39
46
|
*/
|
|
40
47
|
pixManualDescription?: string;
|
|
41
48
|
cardDescription: string;
|
|
42
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* This store's providers cannot take that method: the disabled tile's
|
|
51
|
+
* accessible description, and a caption under the picker.
|
|
52
|
+
*/
|
|
43
53
|
unavailableHere: string;
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -88,16 +98,59 @@ export interface SettlingCopy {
|
|
|
88
98
|
checkAgainAction: string;
|
|
89
99
|
}
|
|
90
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The words the after-copy state says (the 2026-10-06 Pix redesign). One shape
|
|
103
|
+
* for both kinds of Pix: the manual one says the STORE confirms, the automatic
|
|
104
|
+
* one that the BANK does — the screen around it is the same.
|
|
105
|
+
*/
|
|
106
|
+
export interface PixAfterCopyCopy {
|
|
107
|
+
title: string;
|
|
108
|
+
/**
|
|
109
|
+
* The sentence under the title. `wide` is the desktop layout, whose reader is
|
|
110
|
+
* on a "page" rather than a "screen"; `**…**` marks the bold span.
|
|
111
|
+
*/
|
|
112
|
+
body(totalLabel: string, wide: boolean): string;
|
|
113
|
+
/** The three steps: copied (done), pay (current), the confirmation (pending). */
|
|
114
|
+
stepCopied: string;
|
|
115
|
+
stepPay(wide: boolean): string;
|
|
116
|
+
stepConfirm(time: string): string;
|
|
117
|
+
}
|
|
118
|
+
|
|
91
119
|
/** The PIX pane: the QR, the copyable code, and the wait. */
|
|
92
120
|
export interface PixPaneCopy {
|
|
93
121
|
heading: string;
|
|
94
|
-
/** What to do with the QR, carrying the order total the buyer is paying. */
|
|
95
|
-
instructions(totalLabel: string): string;
|
|
96
122
|
/** The QR image's alternative text — the only thing a screen reader gets. */
|
|
97
123
|
qrAlt: string;
|
|
124
|
+
/** The primary copy button. */
|
|
98
125
|
copyAction: string;
|
|
99
|
-
/**
|
|
126
|
+
/** Replaces a copy button's label for the two seconds after a successful copy. */
|
|
100
127
|
copiedAction: string;
|
|
128
|
+
/** The after-copy state's copy-again button. */
|
|
129
|
+
copyAgainAction: string;
|
|
130
|
+
/** The after-copy state's way back to the QR (narrow layout only). */
|
|
131
|
+
showQrAction: string;
|
|
132
|
+
/** The after-copy state's way to the card, when the store takes one. */
|
|
133
|
+
preferCardAction: string;
|
|
134
|
+
/** The after-copy state's poll line. */
|
|
135
|
+
verifying: string;
|
|
136
|
+
/** The narrow layout's tabs: their group name and the two labels. */
|
|
137
|
+
tabsLabel: string;
|
|
138
|
+
copyPasteTab: string;
|
|
139
|
+
qrTab: string;
|
|
140
|
+
/** The wide layout's two column headings and the word between them. */
|
|
141
|
+
qrHeading: string;
|
|
142
|
+
copyPasteHeading: string;
|
|
143
|
+
or: string;
|
|
144
|
+
/** Under the QR in the wide layout; `**…**` marks the bold span. */
|
|
145
|
+
qrInstructions: string;
|
|
146
|
+
/** Under the QR in the narrow layout's QR tab. */
|
|
147
|
+
qrTabCaption: string;
|
|
148
|
+
/** Under the copy button: narrow and wide. `**…**` marks the bold span. */
|
|
149
|
+
copyPasteHint: string;
|
|
150
|
+
internetBankingHint: string;
|
|
151
|
+
/** The wide after-copy state's QR box. */
|
|
152
|
+
notPaidYet: string;
|
|
153
|
+
scanFromPhone: string;
|
|
101
154
|
/**
|
|
102
155
|
* How long the code lasts. Takes the already-formatted clock time, and
|
|
103
156
|
* {@link expiryLocale} is what formatted it.
|
|
@@ -113,6 +166,14 @@ export interface PixPaneCopy {
|
|
|
113
166
|
awaiting: string;
|
|
114
167
|
/** The order came back with no PIX charge on it — nothing to show. */
|
|
115
168
|
chargeMissing: string;
|
|
169
|
+
/**
|
|
170
|
+
* @deprecated Unread since the 2026-10-06 redesign (FUT-3368): the pane's
|
|
171
|
+
* hints replaced the one instructions line. Kept, optional and in its old
|
|
172
|
+
* shape, so an old pack only has to ADD the new keys; remove it from yours.
|
|
173
|
+
*/
|
|
174
|
+
instructions?: (totalLabel: string) => string;
|
|
175
|
+
/** After a copy, for a Pix the BANK confirms. */
|
|
176
|
+
afterCopy: PixAfterCopyCopy;
|
|
116
177
|
/**
|
|
117
178
|
* The same pane for a Pix the STORE confirms (`PixCharge.confirmation:
|
|
118
179
|
* 'MANUAL'`). Optional so a host's own table keeps compiling; absent, the
|
|
@@ -120,15 +181,13 @@ export interface PixPaneCopy {
|
|
|
120
181
|
* enables a store-confirmed provider should set them.
|
|
121
182
|
*/
|
|
122
183
|
manual?: {
|
|
123
|
-
instructions(totalLabel: string): string;
|
|
124
184
|
validUntil(time: string): string;
|
|
125
185
|
awaiting: string;
|
|
126
|
-
/**
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
*/
|
|
186
|
+
/** Required: the automatic sentences promise "automática" (FUT-3232). */
|
|
187
|
+
afterCopy: PixAfterCopyCopy;
|
|
188
|
+
/** @deprecated Unread since FUT-3368; see {@link PixPaneCopy.instructions}. */
|
|
189
|
+
instructions?: (totalLabel: string) => string;
|
|
190
|
+
/** @deprecated Unread since FUT-3368: the after-copy screen replaced the notice. */
|
|
132
191
|
copied?: { title: string; description: string };
|
|
133
192
|
};
|
|
134
193
|
}
|
|
@@ -19,12 +19,12 @@ import type { CheckoutScreensCopy } from './screens-copy';
|
|
|
19
19
|
export const EN_US_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
20
20
|
method: {
|
|
21
21
|
groupLabel: 'Payment method',
|
|
22
|
-
pixLabel: '
|
|
22
|
+
pixLabel: 'Pix',
|
|
23
23
|
cardLabel: 'Card',
|
|
24
24
|
pixDescription: 'Approved instantly',
|
|
25
25
|
pixManualDescription: 'Confirmed by the store',
|
|
26
26
|
cardDescription: 'Credit, paid in full',
|
|
27
|
-
unavailableHere: '
|
|
27
|
+
unavailableHere: 'Not available at this store',
|
|
28
28
|
},
|
|
29
29
|
settling: {
|
|
30
30
|
cannotConfirm: 'Could not confirm the payment',
|
|
@@ -42,25 +42,50 @@ export const EN_US_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
|
42
42
|
checkAgainAction: 'Check again',
|
|
43
43
|
},
|
|
44
44
|
pix: {
|
|
45
|
-
heading: 'Pay with
|
|
46
|
-
instructions: (totalLabel) =>
|
|
47
|
-
`Scan the QR code in your banking app, or copy the code. Total ${totalLabel}.`,
|
|
45
|
+
heading: 'Pay with Pix',
|
|
48
46
|
qrAlt: 'PIX QR code for payment',
|
|
49
|
-
copyAction: 'Copy',
|
|
47
|
+
copyAction: 'Copy code',
|
|
50
48
|
copiedAction: 'Copied!',
|
|
49
|
+
copyAgainAction: 'Copy again',
|
|
50
|
+
showQrAction: 'Show QR code',
|
|
51
|
+
preferCardAction: 'Rather pay by card?',
|
|
52
|
+
verifying: 'Checking automatically…',
|
|
53
|
+
tabsLabel: 'How to pay the Pix',
|
|
54
|
+
copyPasteTab: 'Copy and paste',
|
|
55
|
+
qrTab: 'QR code',
|
|
56
|
+
qrHeading: 'On your phone',
|
|
57
|
+
copyPasteHeading: 'Pix copy and paste',
|
|
58
|
+
or: 'or',
|
|
59
|
+
qrInstructions: 'Open your banking app on your phone, choose **Pix › Scan QR code** and point it at the screen.',
|
|
60
|
+
qrTabCaption: 'Scan it with the banking app on another phone.',
|
|
61
|
+
copyPasteHint: 'Paste it into your banking app under **Pix copy and paste**.',
|
|
62
|
+
internetBankingHint: 'Paying through internet banking? Paste the code under **Pix copy and paste**.',
|
|
63
|
+
notPaidYet: 'Not paid yet?',
|
|
64
|
+
scanFromPhone: 'Scan it with the banking app on your phone.',
|
|
51
65
|
validUntil: (time) => `Valid until ${time}. Confirmation is automatic.`,
|
|
52
66
|
expiryLocale: 'en-US',
|
|
53
67
|
awaiting: 'Waiting for payment…',
|
|
54
68
|
chargeMissing: 'Could not generate the PIX code.',
|
|
69
|
+
afterCopy: {
|
|
70
|
+
title: 'Waiting for the payment',
|
|
71
|
+
body: (totalLabel, wide) =>
|
|
72
|
+
`As soon as the bank confirms the payment of ${wide ? `**${totalLabel}**` : totalLabel}, your order shows up here. You can leave this ${wide ? 'page' : 'screen'} open.`,
|
|
73
|
+
stepCopied: 'Pix code copied',
|
|
74
|
+
stepPay: (wide) =>
|
|
75
|
+
wide ? 'Pay in your banking app or internet banking (Pix copy and paste)' : 'Pay in your banking app (Pix copy and paste)',
|
|
76
|
+
stepConfirm: () => 'The bank confirms the payment automatically',
|
|
77
|
+
},
|
|
55
78
|
manual: {
|
|
56
|
-
instructions: (totalLabel) =>
|
|
57
|
-
`Pay ${totalLabel} with the QR code or the code below. The store checks it arrived and confirms your order.`,
|
|
58
79
|
validUntil: (time) => `The store confirms the payment by ${time}.`,
|
|
59
|
-
awaiting: 'Waiting for the store to confirm
|
|
60
|
-
|
|
61
|
-
title: '
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
awaiting: 'Waiting for the store to confirm…',
|
|
81
|
+
afterCopy: {
|
|
82
|
+
title: 'Waiting for the store',
|
|
83
|
+
body: (totalLabel, wide) =>
|
|
84
|
+
`As soon as the store confirms it received ${wide ? `**${totalLabel}**` : totalLabel}, your order shows up here. You can leave this ${wide ? 'page' : 'screen'} open.`,
|
|
85
|
+
stepCopied: 'Pix code copied',
|
|
86
|
+
stepPay: (wide) =>
|
|
87
|
+
wide ? 'Pay in your banking app or internet banking (Pix copy and paste)' : 'Pay in your banking app (Pix copy and paste)',
|
|
88
|
+
stepConfirm: (time) => `The store confirms the payment — by ${time}`,
|
|
64
89
|
},
|
|
65
90
|
},
|
|
66
91
|
},
|
|
@@ -12,7 +12,7 @@ import type { CheckoutScreensCopy } from './screens-copy';
|
|
|
12
12
|
export const PT_BR_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
13
13
|
method: {
|
|
14
14
|
groupLabel: 'Forma de pagamento',
|
|
15
|
-
pixLabel: '
|
|
15
|
+
pixLabel: 'Pix',
|
|
16
16
|
cardLabel: 'Cartão',
|
|
17
17
|
pixDescription: 'Aprovação imediata',
|
|
18
18
|
pixManualDescription: 'Confirmado pela loja',
|
|
@@ -31,25 +31,50 @@ export const PT_BR_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
|
|
|
31
31
|
checkAgainAction: 'Verificar de novo',
|
|
32
32
|
},
|
|
33
33
|
pix: {
|
|
34
|
-
heading: 'Pague com
|
|
35
|
-
instructions: (totalLabel) =>
|
|
36
|
-
`Escaneie o QR code no app do seu banco ou copie o código. Total ${totalLabel}.`,
|
|
34
|
+
heading: 'Pague com Pix',
|
|
37
35
|
qrAlt: 'QR Code PIX para pagamento',
|
|
38
|
-
copyAction: 'Copiar',
|
|
36
|
+
copyAction: 'Copiar código',
|
|
39
37
|
copiedAction: 'Copiado!',
|
|
38
|
+
copyAgainAction: 'Copiar de novo',
|
|
39
|
+
showQrAction: 'Ver QR code',
|
|
40
|
+
preferCardAction: 'Prefere pagar com cartão?',
|
|
41
|
+
verifying: 'Verificando automaticamente…',
|
|
42
|
+
tabsLabel: 'Forma de pagar o Pix',
|
|
43
|
+
copyPasteTab: 'Copia e cola',
|
|
44
|
+
qrTab: 'QR code',
|
|
45
|
+
qrHeading: 'Pelo celular',
|
|
46
|
+
copyPasteHeading: 'Pix Copia e Cola',
|
|
47
|
+
or: 'ou',
|
|
48
|
+
qrInstructions: 'Abra o app do seu banco no celular, escolha **Pix › Ler QR code** e aponte para a tela.',
|
|
49
|
+
qrTabCaption: 'Escaneie com o app do banco em outro celular.',
|
|
50
|
+
copyPasteHint: 'Cole no app do seu banco em **Pix Copia e Cola**.',
|
|
51
|
+
internetBankingHint: 'Pagando pelo internet banking? Cole o código em **Pix Copia e Cola**.',
|
|
52
|
+
notPaidYet: 'Ainda não pagou?',
|
|
53
|
+
scanFromPhone: 'Escaneie com o app do banco no celular.',
|
|
40
54
|
validUntil: (time) => `Válido até ${time}. A confirmação é automática.`,
|
|
41
55
|
expiryLocale: 'pt-BR',
|
|
42
56
|
awaiting: 'Aguardando pagamento…',
|
|
43
57
|
chargeMissing: 'Não foi possível gerar o código PIX.',
|
|
58
|
+
afterCopy: {
|
|
59
|
+
title: 'Aguardando o pagamento',
|
|
60
|
+
body: (totalLabel, wide) =>
|
|
61
|
+
`Assim que o banco confirmar o pagamento de ${wide ? `**${totalLabel}**` : totalLabel}, seu pedido aparece aqui. Pode deixar esta ${wide ? 'página' : 'tela'} aberta.`,
|
|
62
|
+
stepCopied: 'Código Pix copiado',
|
|
63
|
+
stepPay: (wide) =>
|
|
64
|
+
wide ? 'Pague no app ou no internet banking (Pix Copia e Cola)' : 'Pague no app do seu banco (Pix Copia e Cola)',
|
|
65
|
+
stepConfirm: () => 'O banco confirma o pagamento automaticamente',
|
|
66
|
+
},
|
|
44
67
|
manual: {
|
|
45
|
-
instructions: (totalLabel) =>
|
|
46
|
-
`Pague ${totalLabel} com o QR code ou o código abaixo. A loja confere o recebimento e confirma o seu pedido.`,
|
|
47
68
|
validUntil: (time) => `A loja confirma o pagamento até ${time}.`,
|
|
48
|
-
awaiting: 'Aguardando confirmação
|
|
49
|
-
|
|
50
|
-
title: '
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
awaiting: 'Aguardando confirmação da loja…',
|
|
70
|
+
afterCopy: {
|
|
71
|
+
title: 'Aguardando a loja',
|
|
72
|
+
body: (totalLabel, wide) =>
|
|
73
|
+
`Assim que a loja confirmar o recebimento de ${wide ? `**${totalLabel}**` : totalLabel}, seu pedido aparece aqui. Pode deixar esta ${wide ? 'página' : 'tela'} aberta.`,
|
|
74
|
+
stepCopied: 'Código Pix copiado',
|
|
75
|
+
stepPay: (wide) =>
|
|
76
|
+
wide ? 'Pague no app ou no internet banking (Pix Copia e Cola)' : 'Pague no app do seu banco (Pix Copia e Cola)',
|
|
77
|
+
stepConfirm: (time) => `A loja confirma o pagamento — até ${time}`,
|
|
53
78
|
},
|
|
54
79
|
},
|
|
55
80
|
},
|