@12-apps/payments-frontend 1.20.1 → 1.21.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": "1.20.1",
3
+ "version": "1.21.0",
4
4
  "type": "module",
5
5
  "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.",
6
6
  "exports": {
@@ -17,7 +17,7 @@
17
17
  "storybook:build": "storybook build"
18
18
  },
19
19
  "dependencies": {
20
- "@12-apps/payments-backend": "^2.0.1",
20
+ "@12-apps/payments-backend": "^2.1.0",
21
21
  "react-qr-code": "^2.2.0"
22
22
  },
23
23
  "peerDependencies": {
@@ -7,7 +7,7 @@ import type { MaskedProviderConfig, ProviderDescriptor } from '@12-apps/payments
7
7
 
8
8
  import type { PaymentsSettingsClient } from '../client';
9
9
  import { isConnected } from './connection-state';
10
- import { ProbeAlert, type VerifyProbe } from './CredentialFormAlerts';
10
+ import { ProbeAlert, ProbeChecklist, type VerifyProbe } from './CredentialFormAlerts';
11
11
 
12
12
  /**
13
13
  * "Testar conexão" for a store whose connection is a GRANT, not a form.
@@ -73,6 +73,9 @@ export function ConnectionProbe({
73
73
  </Button>
74
74
  {error ? <Alert severity="error">{error}</Alert> : null}
75
75
  {probe ? <ProbeAlert probe={probe} busy={busy} onRetry={() => void verify()} /> : null}
76
+ {/* Shown on a pass too — see `ProbeChecklist`: what a green probe did
77
+ NOT check is the half an owner cannot otherwise find out. */}
78
+ {probe ? <ProbeChecklist probe={probe} /> : null}
76
79
  </Stack>
77
80
  );
78
81
  }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Alert, Button, CircularProgress, Stack, Typography } from '@mui/material';
3
+ import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
4
4
 
5
5
  import type { VerifiedProviderConfig } from '@12-apps/payments-backend';
6
6
 
@@ -78,6 +78,72 @@ export function ProbeAlert({
78
78
  );
79
79
  }
80
80
 
81
+ /**
82
+ * Off-screen but read aloud. Inline rather than `@mui/utils`'s `visuallyHidden`,
83
+ * which is not a dependency of this package — one style object is not worth a
84
+ * new one on a package that ships to every host.
85
+ */
86
+ const SCREEN_READER_ONLY = {
87
+ position: 'absolute',
88
+ width: 1,
89
+ height: 1,
90
+ overflow: 'hidden',
91
+ clip: 'rect(0 0 0 0)',
92
+ whiteSpace: 'nowrap',
93
+ } as const;
94
+
95
+ /** How each verdict reads at a glance — the mark, and what it is called. */
96
+ const CHECK_MARKS = {
97
+ PASS: { mark: '✓', color: 'success.main', label: 'Verificado' },
98
+ FAIL: { mark: '✕', color: 'error.main', label: 'Corrigir' },
99
+ UNCHECKED: { mark: '–', color: 'text.secondary', label: 'Não verificável' },
100
+ } as const;
101
+
102
+ /**
103
+ * What the probe established, credential by credential (FUT-796).
104
+ *
105
+ * Rendered on a PASS as well as a failure, which is the point. One boolean over
106
+ * a four-field form told an owner their connection was fine when only the
107
+ * secret key had been checked — the publishable key and the signing secret
108
+ * failed later, at a buyer's card and at a payment that never confirmed, where
109
+ * neither looks like a credential problem.
110
+ *
111
+ * The `UNCHECKED` row is the one that earns this component. It says a
112
+ * credential could NOT be verified and why, which a green tick would deny and a
113
+ * red cross would misattribute — and it tells the owner precisely which part of
114
+ * a passing result not to lean on.
115
+ */
116
+ export function ProbeChecklist({ probe }: { probe: VerifyProbe }) {
117
+ if (!probe.checks?.length) return null;
118
+ return (
119
+ <Stack spacing={0.5} data-testid="payments-probe-checks">
120
+ {probe.checks.map((check) => {
121
+ const { mark, color, label } = CHECK_MARKS[check.status];
122
+ return (
123
+ <Stack
124
+ key={check.key}
125
+ direction="row"
126
+ spacing={1}
127
+ alignItems="flex-start"
128
+ data-testid={`payments-probe-check-${check.key}`}
129
+ data-status={check.status}
130
+ >
131
+ <Typography component="span" sx={{ color, fontWeight: 700, lineHeight: 1.5 }}>
132
+ <span aria-hidden>{mark}</span>
133
+ {/* The mark alone is colour-only information; the state has to be
134
+ readable to anyone not seeing the colour. */}
135
+ <Box component="span" sx={SCREEN_READER_ONLY}>{` ${label}: `}</Box>
136
+ </Typography>
137
+ <Typography variant="body2" color="text.secondary">
138
+ {check.message}
139
+ </Typography>
140
+ </Stack>
141
+ );
142
+ })}
143
+ </Stack>
144
+ );
145
+ }
146
+
81
147
  /**
82
148
  * Changing the tag on a store that has ALREADY proved it can receive.
83
149
  *
@@ -25,6 +25,7 @@ import {
25
25
  import {
26
26
  FormActions,
27
27
  ProbeAlert,
28
+ ProbeChecklist,
28
29
  ReverifyWarning,
29
30
  type VerifyProbe,
30
31
  } from './CredentialFormAlerts';
@@ -375,6 +376,11 @@ export function ProviderForm(props: ProviderFormProps) {
375
376
  {form.probe ? (
376
377
  <ProbeAlert probe={form.probe} busy={form.busy !== null} onRetry={form.verify} />
377
378
  ) : null}
379
+ {/* The pasted-key path, and the one FUT-796 is about: this form is where
380
+ the credentials the probe reports on were typed, so its findings
381
+ belong beside them — including on a pass, where the unchecked rows are
382
+ the only place an owner learns what was NOT established. */}
383
+ {form.probe ? <ProbeChecklist probe={form.probe} /> : null}
378
384
 
379
385
  <ConfirmCredentialSave
380
386
  pending={form.pending}
@@ -26,6 +26,18 @@ import { ProviderSetupGuide } from './ProviderSetupGuide';
26
26
  */
27
27
  export const CHECKOUT_CONFIRM_ACTION = 'checkout-integrado-confirmado';
28
28
 
29
+ /**
30
+ * The confirm button's copy when a section does not author its own.
31
+ *
32
+ * InfinitePay's wording, because it was the only confirmable guide when this
33
+ * was a constant — and a constant is what made it wrong everywhere else: Stripe
34
+ * and Stone both own a step no API can report, and neither of them has a
35
+ * "Checkout Integrado" to have enabled. Sections say what they are asking about
36
+ * via `SetupSection.confirmLabel`; this fallback keeps InfinitePay's rendered
37
+ * guide identical.
38
+ */
39
+ const CONFIRM_LABEL_FALLBACK = 'Já habilitei o Checkout Integrado';
40
+
29
41
  /** Does this section end in a question only the owner can answer? */
30
42
  function isConfirmable(section: SetupSection): boolean {
31
43
  return section.steps.some((step) => step.action === CHECKOUT_CONFIRM_ACTION);
@@ -133,6 +145,28 @@ function ConfirmedRow({ section, onReopen }: { section: SetupSection; onReopen:
133
145
  );
134
146
  }
135
147
 
148
+ /**
149
+ * Everything the owner's confirmation contributes to this screen: the button's
150
+ * copy, and the one-line row the step collapses into once the walkthrough is
151
+ * past it.
152
+ *
153
+ * One function because both read the SAME section — the one that ASKS, which is
154
+ * not necessarily the one that is open. Deriving them apart is how a screen
155
+ * ends up captioning one step and collapsing another.
156
+ */
157
+ function confirmationOf(
158
+ guide: Guide,
159
+ { stage, confirmed, editing }: { stage: number; confirmed: boolean; editing: boolean },
160
+ ): { label: string; settledSection: SetupSection | null } {
161
+ const asking = confirmableStage(guide);
162
+ const section = asking >= 0 ? sectionAt(guide, asking) : null;
163
+ const settled = confirmed && !editing && asking >= 0 && stage > asking;
164
+ return {
165
+ label: section?.confirmLabel ?? CONFIRM_LABEL_FALLBACK,
166
+ settledSection: settled ? section : null,
167
+ };
168
+ }
169
+
136
170
  export function SetupGuideSection({
137
171
  guide,
138
172
  confirmed,
@@ -159,12 +193,7 @@ export function SetupGuideSection({
159
193
 
160
194
  const stage = effectiveStage(guide, confirmed, editing, stored);
161
195
  const open = sectionAt(guide, stage);
162
- // The confirmed step keeps a row of its own once the walkthrough has moved
163
- // past it — it is the claim step 3 is about to test, and when the provider
164
- // refuses to mint a link the owner needs somewhere to press Revisar.
165
- const settledStage = confirmableStage(guide);
166
- const settled = confirmed && !editing && settledStage >= 0 && stage > settledStage;
167
- const settledSection = settled ? sectionAt(guide, settledStage) : null;
196
+ const { label, settledSection } = confirmationOf(guide, { stage, confirmed, editing });
168
197
 
169
198
  return (
170
199
  <Box data-testid="payments-setup">
@@ -172,10 +201,7 @@ export function SetupGuideSection({
172
201
  guide={{ ...guide, sections: open ? [open] : [] }}
173
202
  activeStage={stage}
174
203
  actions={{
175
- [CHECKOUT_CONFIRM_ACTION]: {
176
- label: 'Já habilitei o Checkout Integrado',
177
- run: onConfirm,
178
- },
204
+ [CHECKOUT_CONFIRM_ACTION]: { label, run: onConfirm },
179
205
  }}
180
206
  beforeSections={
181
207
  <>