@12-apps/payments-frontend 3.29.1 → 3.31.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.29.1",
3
+ "version": "3.31.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.",
@@ -23,7 +23,7 @@
23
23
  "storybook:build": "storybook build"
24
24
  },
25
25
  "dependencies": {
26
- "@12-apps/payments-backend": "^4.29.0",
26
+ "@12-apps/payments-backend": "^4.30.0",
27
27
  "react-qr-code": "^2.2.0"
28
28
  },
29
29
  "peerDependencies": {
@@ -6,6 +6,7 @@ import type { PaymentEnvironment, ProviderDescriptor } from '@12-apps/payments-b
6
6
 
7
7
  import { CredentialField } from './CredentialFields';
8
8
  import { appliesIn, saveLabel } from './credential-rules';
9
+ import { checksLocally } from './checks-locally';
9
10
  import { FormActions, ReverifyWarning } from './CredentialFormAlerts';
10
11
  import type { CredentialFormState } from './ProviderCredentialForm';
11
12
  import { usePaymentsSettingsCopy } from './settings-copy-context';
@@ -58,6 +59,7 @@ export function CredentialFields({
58
59
  willTest={form.complete}
59
60
  disabled={form.nothingEdited || !form.valid}
60
61
  onSave={form.requestSave}
62
+ local={checksLocally(descriptor)}
61
63
  />
62
64
  </Stack>
63
65
  );
@@ -116,9 +116,11 @@ export function CredentialField({ spec, state, value, onChange, check }: Credent
116
116
  sx={{ display: 'block', fontSize: '12px', fontWeight: 650, color: T.ink2, mb: '5px' }}
117
117
  >
118
118
  {spec.label}
119
- {spec.advanced ? (
120
- <Box component="span" sx={{ fontWeight: 500, color: T.ink4 }}>
121
- {copy.advancedSuffix}
119
+ {spec.advanced || spec.optional ? (
120
+ // `ink3`, not `ink4`: "opcional" carries real information, and at 12px
121
+ // the lighter ink fell under AA contrast.
122
+ <Box component="span" sx={{ fontWeight: 500, color: spec.optional ? T.ink3 : T.ink4 }}>
123
+ {spec.advanced ? copy.advancedSuffix : copy.optionalSuffix}
122
124
  </Box>
123
125
  ) : null}
124
126
  </Typography>
@@ -189,12 +189,20 @@ export function ReverifyWarning({ displayName }: { displayName: string }) {
189
189
  * Nothing is learned by separating them: the probe reads what was just stored,
190
190
  * so the only useful moment to run it is immediately after a save.
191
191
  */
192
+ /** What the bar promises: testing now, what a save will do, or that it only writes down. */
193
+ function barMessage(copy: CredentialFormCopy, verifying: boolean, willTest: boolean, local: boolean): string {
194
+ if (verifying) return (local ? copy.probeLocalRunning : undefined) ?? copy.probeRunning;
195
+ if (!willTest) return copy.probeIncompleteNote;
196
+ return (local ? copy.probeLocalSaveNote : undefined) ?? copy.probeSaveNote;
197
+ }
198
+
192
199
  export function FormActions({
193
200
  busy,
194
201
  label,
195
202
  willTest,
196
203
  disabled,
197
204
  onSave,
205
+ local = false,
198
206
  }: {
199
207
  busy: string | null;
200
208
  label: string;
@@ -202,17 +210,14 @@ export function FormActions({
202
210
  willTest: boolean;
203
211
  disabled: boolean;
204
212
  onSave: () => void;
213
+ /** The "test" is a local check (`checksLocally`): nothing is sent to a provider. */
214
+ local?: boolean;
205
215
  }) {
206
216
  // The bar states what pressing it costs, which is the half a verb cannot
207
217
  // carry: a complete set is sent to the provider on save, and a partial one is
208
218
  // only written down. The owner reads the promise and the button in one line.
209
219
  const copy = usePaymentsSettingsCopy().credentials;
210
- const message =
211
- busy === 'verify'
212
- ? copy.probeRunning
213
- : willTest
214
- ? copy.probeSaveNote
215
- : copy.probeIncompleteNote;
220
+ const message = barMessage(copy, busy === 'verify', willTest, local);
216
221
 
217
222
  return (
218
223
  <Box sx={BAR_SX} data-testid="payments-form-bar">
@@ -226,12 +226,49 @@ function oauthWalkthrough(props: ActivePanelProps, path: 'oauth' | 'credentials'
226
226
  * a FUNCTION: which steps to show depends on the disclosure, whose state lives
227
227
  * in `OAuthPanel`.
228
228
  */
229
+ /** The one environment a provider has, when it declares a single one. */
230
+ function soleEnvironment(descriptor: ActivePanelProps['descriptor']): PaymentEnvironment | null {
231
+ // Read defensively: a host-built descriptor (and many fixtures) may carry no capabilities at all.
232
+ const declared = (descriptor.capabilities as typeof descriptor.capabilities | undefined)?.environments;
233
+ return declared && declared.length === 1 ? (declared[0] ?? null) : null;
234
+ }
235
+
236
+ /**
237
+ * The environment selector and its notice — full-bleed across the card, and
238
+ * inset for the manual disclosure, where it no longer spans anything (see
239
+ * `EnvironmentNotice`). A provider with ONE environment (no sandbox: every code
240
+ * is real money) gets neither: there is nothing to choose, and a "test
241
+ * environment" notice would promise that no money moves.
242
+ */
243
+ function environmentChrome(
244
+ sole: PaymentEnvironment | null,
245
+ environment: PaymentEnvironment,
246
+ onChange: (next: PaymentEnvironment) => void,
247
+ active: PaymentEnvironment | null,
248
+ ): { selector: ReactNode; band: ReactNode; notice: ReactNode } {
249
+ // Nothing to choose with one environment. The notice goes only for a
250
+ // PRODUCTION-only provider (it would promise no money moves); a sandbox-only
251
+ // one keeps its truthful "nothing is real" notice.
252
+ if (sole === 'PRODUCTION') return { selector: null, band: undefined, notice: null };
253
+ if (sole) {
254
+ return {
255
+ selector: null,
256
+ band: <EnvironmentNotice environment={environment} active={active} />,
257
+ notice: <EnvironmentNotice environment={environment} active={active} band={false} />,
258
+ };
259
+ }
260
+ return {
261
+ selector: <EnvironmentSelector environment={environment} onChange={onChange} />,
262
+ band: <EnvironmentNotice environment={environment} active={active} />,
263
+ notice: <EnvironmentNotice environment={environment} active={active} band={false} />,
264
+ };
265
+ }
266
+
229
267
  export function ActivePanel(props: ActivePanelProps) {
230
268
  const { descriptor, config, client, onChanged, reload, guide, verification } = props;
231
269
  const statusBar = <EnableBar {...props} />;
232
- const [environment, setEnvironment] = useState<PaymentEnvironment>(
233
- config?.environment ?? 'SANDBOX',
234
- );
270
+ const sole = soleEnvironment(descriptor);
271
+ const [environment, setEnvironment] = useState<PaymentEnvironment>(sole ?? config?.environment ?? 'SANDBOX');
235
272
  const [editing, setEditing] = useState(false);
236
273
 
237
274
  const oauthConnect = oauthWithConnect(props);
@@ -251,12 +288,7 @@ export function ActivePanel(props: ActivePanelProps) {
251
288
  />
252
289
  );
253
290
 
254
- const selector = <EnvironmentSelector environment={environment} onChange={setEnvironment} />;
255
- // Full-bleed across the card, and the same notice inset for the manual
256
- // disclosure, where it no longer spans anything. See `EnvironmentNotice`.
257
- const active = config?.environment ?? null;
258
- const band = <EnvironmentNotice environment={environment} active={active} />;
259
- const notice = <EnvironmentNotice environment={environment} active={active} band={false} />;
291
+ const { selector, band, notice } = environmentChrome(sole, environment, setEnvironment, config?.environment ?? null);
260
292
 
261
293
  const storedHere = requiredStored(descriptor, config, environment);
262
294
 
@@ -5,6 +5,7 @@ import { Box, Stack, Switch, Tooltip, Typography } from '@mui/material';
5
5
  import type { MaskedProviderConfig, ProviderDescriptor } from '@12-apps/payments-backend';
6
6
 
7
7
  import { certificateExpiryProximity, isConnected } from './connection-state';
8
+ import { checksLocally } from './checks-locally';
8
9
  import { T } from './panel-tokens';
9
10
  import type { ConnectionStatusCopy } from './settings-copy';
10
11
  import { usePaymentsSettingsCopy } from './settings-copy-context';
@@ -54,7 +55,9 @@ export function statusBadge(
54
55
  if (config?.status === 'RECONNECT_REQUIRED') {
55
56
  return { label: copy.reconnectRequired, color: 'error' };
56
57
  }
57
- if (isConnected(config)) return { label: copy.connectionOk, color: 'info' };
58
+ if (isConnected(config)) {
59
+ return { label: (checksLocally(descriptor) ? copy.detailsOk : undefined) ?? copy.connectionOk, color: 'info' };
60
+ }
58
61
  // A FAILED probe lands here, and it says NÃO VERIFICADO like every other
59
62
  // not-yet-connected state. `FALHOU` was a third word for the same fact — the
60
63
  // store cannot take money through this provider yet — and it was the only
@@ -125,7 +128,7 @@ function toggleGate(
125
128
  if (!lockedOff) return { lockedOff, hint: '' };
126
129
  return {
127
130
  lockedOff,
128
- hint: provable ? copy.threeStepsAhead : copy.connectAndVerifyFirst,
131
+ hint: provable ? copy.threeStepsAhead : ((checksLocally(descriptor) ? copy.checkDetailsFirst : undefined) ?? copy.connectAndVerifyFirst),
129
132
  };
130
133
  }
131
134
 
@@ -186,12 +186,25 @@ export function SetupGuideSection({
186
186
  // have to render: they are the credential form, and swallowing them left a
187
187
  // provider with a status chip, a pair of environment tabs and no way to type
188
188
  // anything in.
189
+ // They take the card's 20px inset themselves, as every block in its
190
+ // unpadded body does (`ProviderCard`) — a guide's step panel carries it for
191
+ // them, and without one the boxes ran into the card's edges.
189
192
  if (!guide) {
190
193
  return (
191
- <>
194
+ // The save bar and the saved-details row carry their own inset for the
195
+ // guided layout; here the wrapper already supplies it, so theirs is zeroed
196
+ // and their rule, text and button line up with the fields.
197
+ <Box
198
+ data-testid="payments-setup-unguided"
199
+ sx={{
200
+ px: '20px',
201
+ '& [data-testid="payments-form-bar"]': { px: 0 },
202
+ '& [data-testid="payments-credential-summary"]': { mx: 0 },
203
+ }}
204
+ >
192
205
  {rows}
193
206
  {sectionFooter}
194
- </>
207
+ </Box>
195
208
  );
196
209
  }
197
210
 
@@ -4,7 +4,7 @@ import QRCode from "react-qr-code";
4
4
 
5
5
  import { useCheckoutCopy } from "./copy-context";
6
6
  import { ContentCopyIcon } from "./icons";
7
- import type { SettlingCopy } from "./screens-copy";
7
+ import type { PixPaneCopy, SettlingCopy } from "./screens-copy";
8
8
  import { StalledWait } from "./stalled-wait";
9
9
  import type { CheckoutOrder, OrderStatus, PixCharge } from "./types";
10
10
  import { useCheckoutComponents } from "./ui";
@@ -163,14 +163,18 @@ function PixPollFooter({
163
163
  error,
164
164
  timedOut,
165
165
  onCheckAgain,
166
+ manual,
166
167
  }: {
167
168
  error: string | null;
168
169
  timedOut: boolean;
169
170
  onCheckAgain: () => void;
171
+ /** The store confirms this Pix: the wait is for THEM, not for the bank. */
172
+ manual: boolean;
170
173
  }): JSX.Element {
171
174
  const { Text } = useCheckoutComponents();
172
175
  const { pix, settling } = useCheckoutCopy().screens;
173
176
  const panel = pixWaitPanel(settling, error, timedOut);
177
+ const awaiting = (manual ? pix.manual?.awaiting : undefined) ?? pix.awaiting;
174
178
  if (panel) {
175
179
  // The same panel the card and wallet panes show, held to the width of the
176
180
  // copy-and-paste strip above it so the centred PIX column stays a column.
@@ -190,12 +194,23 @@ function PixPollFooter({
190
194
  <Box sx={{ display: "flex", alignItems: "center", gap: 1, color: "text.secondary" }}>
191
195
  <LoadingDot />
192
196
  <Text variant="caption" size="xs" color="secondary" as="span" data-testid="pix-awaiting">
193
- {pix.awaiting}
197
+ {awaiting}
194
198
  </Text>
195
199
  </Box>
196
200
  );
197
201
  }
198
202
 
203
+ /**
204
+ * The pane's two sentences that depend on who confirms. A store-confirmed Pix
205
+ * must not say "a confirmação é automática": nothing confirms it but the store.
206
+ */
207
+ function pixSentences(
208
+ copy: PixPaneCopy,
209
+ manual: boolean,
210
+ ): Pick<PixPaneCopy, "instructions" | "validUntil"> {
211
+ return (manual ? copy.manual : undefined) ?? copy;
212
+ }
213
+
199
214
  export function PixView({
200
215
  order,
201
216
  onResolved,
@@ -243,6 +258,7 @@ export function PixView({
243
258
  hour: "2-digit",
244
259
  minute: "2-digit",
245
260
  });
261
+ const sentences = pixSentences(copy, pix.confirmation === "MANUAL");
246
262
 
247
263
  return (
248
264
  <Box
@@ -253,7 +269,7 @@ export function PixView({
253
269
  {copy.heading}
254
270
  </Text>
255
271
  <Text variant="body" size="sm" color="secondary" as="p">
256
- {copy.instructions(order.totalLabel)}
272
+ {sentences.instructions(order.totalLabel)}
257
273
  </Text>
258
274
 
259
275
  <Box
@@ -268,10 +284,10 @@ export function PixView({
268
284
  <PixCodeBox pix={pix} />
269
285
 
270
286
  <Text variant="caption" size="xs" color="secondary" as="p" data-testid="pix-expiry">
271
- {copy.validUntil(validUntil)}
287
+ {sentences.validUntil(validUntil)}
272
288
  </Text>
273
289
 
274
- <PixPollFooter error={error} timedOut={timedOut} onCheckAgain={checkAgain} />
290
+ <PixPollFooter error={error} timedOut={timedOut} onCheckAgain={checkAgain} manual={sentences !== copy} />
275
291
  </Box>
276
292
  );
277
293
  }
@@ -107,6 +107,17 @@ export interface PixPaneCopy {
107
107
  awaiting: string;
108
108
  /** The order came back with no PIX charge on it — nothing to show. */
109
109
  chargeMissing: string;
110
+ /**
111
+ * The same pane for a Pix the STORE confirms (`PixCharge.confirmation:
112
+ * 'MANUAL'`). Optional so a host's own table keeps compiling; absent, the
113
+ * automatic sentences are used — which promise "automática", so a host that
114
+ * enables a store-confirmed provider should set them.
115
+ */
116
+ manual?: {
117
+ instructions(totalLabel: string): string;
118
+ validUntil(time: string): string;
119
+ awaiting: string;
120
+ };
110
121
  }
111
122
 
112
123
  /** The card pane's own heading. Everything else in it is {@link SettlingCopy}. */
@@ -51,6 +51,12 @@ export const EN_US_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
51
51
  expiryLocale: 'en-US',
52
52
  awaiting: 'Waiting for payment…',
53
53
  chargeMissing: 'Could not generate the PIX code.',
54
+ manual: {
55
+ instructions: (totalLabel) =>
56
+ `Pay ${totalLabel} with the QR code or the code below. The store checks it arrived and confirms your order.`,
57
+ validUntil: (time) => `The store confirms the payment by ${time}.`,
58
+ awaiting: 'Waiting for the store to confirm the payment…',
59
+ },
54
60
  },
55
61
  card: {
56
62
  heading: 'Pay by card',
@@ -40,6 +40,12 @@ export const PT_BR_CHECKOUT_SCREENS_COPY: CheckoutScreensCopy = {
40
40
  expiryLocale: 'pt-BR',
41
41
  awaiting: 'Aguardando pagamento…',
42
42
  chargeMissing: 'Não foi possível gerar o código PIX.',
43
+ manual: {
44
+ instructions: (totalLabel) =>
45
+ `Pague ${totalLabel} com o QR code ou o código abaixo. A loja confere o recebimento e confirma o seu pedido.`,
46
+ validUntil: (time) => `A loja confirma o pagamento até ${time}.`,
47
+ awaiting: 'Aguardando a loja confirmar o pagamento…',
48
+ },
43
49
  },
44
50
  card: {
45
51
  heading: 'Pague com cartão',
@@ -66,15 +66,15 @@ export interface BuyerInfo {
66
66
  }
67
67
 
68
68
  /**
69
- * A raised PIX charge. `copyPaste` is the EMV "copia e cola" payload — it is both
70
- * what the QR code encodes and what the copy button copies. The real backend also
71
- * returns a provider QR image; the client renders its own QR from `copyPaste`, so
72
- * no image URL is needed here.
69
+ * A raised PIX charge. `copyPaste` is the EMV "copia e cola" payload: what the
70
+ * QR encodes (rendered client-side, so no image URL) and what the button copies.
73
71
  */
74
72
  export interface PixCharge {
75
73
  copyPaste: string;
76
74
  /** ISO-8601 instant when the charge expires. */
77
75
  expiresAt: string;
76
+ /** `'MANUAL'`: only the store confirms it (`pixmanual`); absent is automatic. */
77
+ confirmation?: 'AUTOMATIC' | 'MANUAL';
78
78
  }
79
79
 
80
80
  /**
@@ -0,0 +1,13 @@
1
+ import type { ProviderDescriptor } from '@12-apps/payments-backend';
2
+
3
+ /**
4
+ * Whether this provider's "test" is a LOCAL check — there is no provider to
5
+ * connect to (the store's own Pix key, `capabilities.confirmation: 'MANUAL'`).
6
+ * Its screen must not say keys are tested "no provedor" or call a passing
7
+ * check a CONNECTION. Read defensively: a host-built descriptor may carry no
8
+ * capabilities.
9
+ */
10
+ export function checksLocally(descriptor: Pick<ProviderDescriptor, 'capabilities'> | undefined): boolean {
11
+ const capabilities = descriptor?.capabilities as ProviderDescriptor['capabilities'] | undefined;
12
+ return capabilities?.confirmation === 'MANUAL';
13
+ }
@@ -5,6 +5,7 @@ import type {
5
5
  ProviderDescriptor,
6
6
  } from '@12-apps/payments-backend';
7
7
 
8
+ import { checksLocally } from './checks-locally';
8
9
  import type { PendingSave } from './ConfirmCredentialSave';
9
10
  import type { CredentialFormCopy } from './settings-copy';
10
11
 
@@ -49,7 +50,7 @@ export function credentialsComplete(
49
50
  ): boolean {
50
51
  const stored = config?.environments[environment] ?? {};
51
52
  return descriptor.credentialSchema.every((spec) => {
52
- if (spec.advanced || !appliesIn(spec, environment)) return true;
53
+ if (spec.advanced || spec.optional || !appliesIn(spec, environment)) return true;
53
54
  const typed = values[spec.key];
54
55
  if (typed !== undefined && typed.trim() !== '') return true;
55
56
  return stored[spec.key]?.configured === true;
@@ -104,10 +105,15 @@ export function saveLabel(
104
105
  ): string {
105
106
  const required = descriptor.credentialSchema.filter((field) => field.required);
106
107
  const only = required.length === 1 ? required[0] : undefined;
107
- if (!only) return complete ? copy.saveAndTest : copy.save;
108
+ if (!only) return complete ? saveAndTestLabel(copy, descriptor) : copy.save;
108
109
  return copy.saveOnly(only.label.replace(/\s*\([^)]*\)\s*$/, ''));
109
110
  }
110
111
 
112
+ /** "Salvar e testar conexão", or "Salvar e conferir" where nothing leaves for a provider. */
113
+ function saveAndTestLabel(copy: CredentialFormCopy, descriptor: ProviderDescriptor): string {
114
+ return (checksLocally(descriptor) ? copy.saveAndCheck : undefined) ?? copy.saveAndTest;
115
+ }
116
+
111
117
  /**
112
118
  * The stored value of the field a finished step collapses around.
113
119
  *
@@ -30,6 +30,12 @@ export interface ConnectionStatusCopy {
30
30
  unverified: string;
31
31
  /** The credential probe passed; no charge has been made. */
32
32
  connectionOk: string;
33
+ /**
34
+ * `connectionOk` for a provider there is nothing to connect to — the store's
35
+ * own Pix key, checked here (`checksLocally`). Optional: absent, the chip
36
+ * falls back to `connectionOk`.
37
+ */
38
+ detailsOk?: string;
33
39
  /**
34
40
  * A connection that WAS working and stopped — the one red chip, because it
35
41
  * is news rather than a step still to finish.
@@ -41,6 +47,8 @@ export interface ConnectionStatusCopy {
41
47
  threeStepsAhead: string;
42
48
  /** The same, when the store must connect and verify first. */
43
49
  connectAndVerifyFirst: string;
50
+ /** `connectAndVerifyFirst` for a provider checked locally. Optional, falls back to it. */
51
+ checkDetailsFirst?: string;
44
52
  /** Live: this provider is taking money. */
45
53
  receiving: { state: string; sub: string };
46
54
  /** Proven, and switched off by the owner. */
@@ -194,6 +202,8 @@ export interface CredentialFormCopy {
194
202
  configuredKeepBlank: string;
195
203
  /** The suffix on an `advanced` field most stores must leave empty. */
196
204
  advancedSuffix: string;
205
+ /** The suffix on an `optional` field: blank keeps its default. */
206
+ optionalSuffix: string;
197
207
  probeAction: string;
198
208
  /**
199
209
  * What replacing a PROVEN connection's credentials costs.
@@ -206,6 +216,13 @@ export interface CredentialFormCopy {
206
216
  reverifyWarning(displayName: string): string;
207
217
  probeRunning: string;
208
218
  probeSaveNote: string;
219
+ /**
220
+ * `probeRunning` / `probeSaveNote` for a provider checked locally: nothing is
221
+ * sent anywhere, so saying the keys are tested "no provedor" would be false.
222
+ * Optional; absent, the provider sentences are used.
223
+ */
224
+ probeLocalRunning?: string;
225
+ probeLocalSaveNote?: string;
209
226
  probeIncompleteNote: string;
210
227
  probeFailed(environmentName: string): string;
211
228
  /** The per-credential marks beside each field. */
@@ -219,6 +236,8 @@ export interface CredentialFormCopy {
219
236
  * only written down, and a one-field provider names its field instead.
220
237
  */
221
238
  saveAndTest: string;
239
+ /** `saveAndTest` for a provider checked locally (no connection to test). Optional. */
240
+ saveAndCheck?: string;
222
241
  save: string;
223
242
  saveOnly(fieldLabel: string): string;
224
243
  /** Reopen a finished credential step — unaccented, so only a reading found it. */
@@ -22,10 +22,12 @@ export const EN_US_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
22
22
  verified: 'VERIFIED',
23
23
  unverified: 'NOT VERIFIED',
24
24
  connectionOk: 'CONNECTION OK',
25
+ detailsOk: 'DETAILS OK',
25
26
  reconnectRequired: 'RECONNECT',
26
27
  notConnected: 'Not connected',
27
28
  threeStepsAhead: 'This provider only starts taking sales after the 3 steps below.',
28
29
  connectAndVerifyFirst: 'Connect and verify the provider before switching sales on.',
30
+ checkDetailsFirst: 'Fill in and check the details before switching sales on.',
29
31
  receiving: {
30
32
  state: 'Taking sales',
31
33
  sub: 'Your store is being paid through this provider.',
@@ -131,12 +133,15 @@ export const EN_US_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
131
133
  configuredKeepBlank: 'Configured — leave blank to keep the current value.',
132
134
  // Leading space: it is appended to a field label.
133
135
  advancedSuffix: ' · Connect platforms only',
136
+ optionalSuffix: ' · optional',
134
137
  probeAction: 'Test connection',
135
138
  reverifyWarning: (displayName) =>
136
139
  `This store is already verified. Changing the credentials **requires re-verification** ` +
137
140
  `and the store **stops taking payments** through ${displayName} until it finishes.`,
138
141
  probeRunning: 'Testing the connection…',
139
142
  probeSaveNote: 'We save and test the keys with the provider before going on.',
143
+ probeLocalRunning: 'Checking the details…',
144
+ probeLocalSaveNote: 'We check the details before saving.',
140
145
  probeIncompleteNote: 'We kept what you have filled in. Complete the fields to test.',
141
146
  probeFailed: (environmentName) =>
142
147
  `Could not connect in ${environmentName}. Check the credentials for this environment.`,
@@ -144,6 +149,7 @@ export const EN_US_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
144
149
  checkFail: 'Fix',
145
150
  uncheckable: 'Not verifiable',
146
151
  saveAndTest: 'Save and test connection',
152
+ saveAndCheck: 'Save and check',
147
153
  save: 'Save',
148
154
  saveOnly: (fieldLabel) => `Save ${fieldLabel}`,
149
155
  changeAction: 'Change',
@@ -14,10 +14,12 @@ export const PT_BR_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
14
14
  verified: 'VERIFICADO',
15
15
  unverified: 'NÃO VERIFICADO',
16
16
  connectionOk: 'CONEXÃO OK',
17
+ detailsOk: 'DADOS OK',
17
18
  reconnectRequired: 'RECONECTAR',
18
19
  notConnected: 'Não conectado',
19
20
  threeStepsAhead: 'Este provedor só passa a receber vendas depois dos 3 passos abaixo.',
20
21
  connectAndVerifyFirst: 'Conecte e verifique o provedor antes de ativar as vendas.',
22
+ checkDetailsFirst: 'Preencha e confira os dados antes de ativar as vendas.',
21
23
  receiving: {
22
24
  state: 'Recebendo vendas',
23
25
  sub: 'Sua loja está recebendo por este provedor.',
@@ -119,12 +121,15 @@ export const PT_BR_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
119
121
  credentials: {
120
122
  configuredKeepBlank: 'Configurado — deixe em branco para manter o valor atual.',
121
123
  advancedSuffix: ' · só para plataformas Connect',
124
+ optionalSuffix: ' · opcional',
122
125
  probeAction: 'Testar conexão',
123
126
  reverifyWarning: (displayName) =>
124
127
  `Esta loja já está verificada. Trocar as credenciais **exige uma nova verificação** ` +
125
128
  `e a loja **para de receber** pelo ${displayName} até que ela termine.`,
126
129
  probeRunning: 'Testando a conexão…',
127
130
  probeSaveNote: 'Salvamos e testamos as chaves no provedor antes de seguir.',
131
+ probeLocalRunning: 'Conferindo os dados…',
132
+ probeLocalSaveNote: 'Conferimos os dados antes de salvar.',
128
133
  probeIncompleteNote: 'Guardamos o que você já preencheu. Complete os campos para testar.',
129
134
  probeFailed: (environmentName) =>
130
135
  `Não foi possível conectar em ${environmentName}. Confira as credenciais deste ambiente.`,
@@ -132,6 +137,7 @@ export const PT_BR_PAYMENTS_SETTINGS_COPY: PaymentsSettingsCopy = {
132
137
  checkFail: 'Corrigir',
133
138
  uncheckable: 'Não verificável',
134
139
  saveAndTest: 'Salvar e testar conexão',
140
+ saveAndCheck: 'Salvar e conferir',
135
141
  save: 'Salvar',
136
142
  saveOnly: (fieldLabel) => `Salvar ${fieldLabel}`,
137
143
  changeAction: 'Alterar',