@12-apps/payments-frontend 3.40.0 → 3.41.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.
|
|
3
|
+
"version": "3.41.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.
|
|
26
|
+
"@12-apps/payments-backend": "^4.31.0",
|
|
27
27
|
"react-qr-code": "^2.2.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
@@ -53,7 +53,10 @@ import { clearReturnedSettlement, takeReturnedSettlement } from './returned-sett
|
|
|
53
53
|
* things that clear it are settlement and an explicit "give up".
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* How often to ask the provider whether the payment landed — the recorded
|
|
58
|
+
* third-party-wait exception, see {@link usePollTimer}.
|
|
59
|
+
*/
|
|
57
60
|
const DEFAULT_POLL_MS = 4000;
|
|
58
61
|
|
|
59
62
|
/** Give up asking after this long, rather than polling a tab forever. */
|
|
@@ -126,6 +129,24 @@ interface ActivationCallbacks {
|
|
|
126
129
|
/**
|
|
127
130
|
* Ask the provider every few seconds, and stop asking eventually.
|
|
128
131
|
*
|
|
132
|
+
* ## A deliberate poll: the recorded exception to "hear, do not poll" (FUT-2433)
|
|
133
|
+
*
|
|
134
|
+
* Every other wait in this package hears the host's channel and keeps no timer
|
|
135
|
+
* while it is live. This one cannot, and it is the one exception class the
|
|
136
|
+
* origin host records: a timer-driven read of a THIRD PARTY's state that no
|
|
137
|
+
* webhook or publisher of the host's reaches yet, bounded in time, and named.
|
|
138
|
+
* Each tick is not a read of the host's own rows — `verify-charge` asks the
|
|
139
|
+
* PROVIDER whether the activation charge was paid, and while nobody has asked,
|
|
140
|
+
* nothing server-side has moved that a hint could carry. The activation webhook
|
|
141
|
+
* that would settle it is not guaranteed to arrive (a tunnel, a preview box), so
|
|
142
|
+
* the wait is the confirmation. It runs only while the owner is paying on the
|
|
143
|
+
* provider's page and gives up at `pollTimeoutMs` (ten minutes by default).
|
|
144
|
+
*
|
|
145
|
+
* Where it is recorded: the origin host's realtime doc ("Allowed exceptions")
|
|
146
|
+
* and the decision record that superseded its 0071 with it. If activation
|
|
147
|
+
* ever becomes webhook-only, this relaxes to the channel exactly as the
|
|
148
|
+
* checkout's waits did (FUT-649) and the exception goes away.
|
|
149
|
+
*
|
|
129
150
|
* The give-up is a `failed` state carrying the host's own sentence, because the
|
|
130
151
|
* wording is load-bearing: the charge is STILL valid, and an owner who has
|
|
131
152
|
* genuinely paid must not be told they did not pay in time.
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type ProviderChangeHandler,
|
|
22
22
|
} from './settings-state';
|
|
23
23
|
import { openSection, SetupGuideSection } from './SetupGuideSection';
|
|
24
|
+
import { useSettingsLive, type PaymentsSettingsLiveSignal } from './settings-live';
|
|
24
25
|
import type { PaymentsSettingsCopy } from './settings-copy';
|
|
25
26
|
import { PaymentsSettingsCopyProvider } from './settings-copy-context';
|
|
26
27
|
|
|
@@ -102,6 +103,16 @@ export interface PaymentProviderSettingsProps {
|
|
|
102
103
|
* its history ({@link ProviderChangeHandler}).
|
|
103
104
|
*/
|
|
104
105
|
onProviderChange?: ProviderChangeHandler;
|
|
106
|
+
/**
|
|
107
|
+
* The host's realtime channel for this store's payment settings (FUT-648).
|
|
108
|
+
*
|
|
109
|
+
* A hint re-reads the view, and so does every (re)connect: an OAuth grant
|
|
110
|
+
* completed in another tab, a colleague's save, a webhook settling the
|
|
111
|
+
* activation charge all reach this screen without a reload. Omit it and the
|
|
112
|
+
* screen reads on mount and after its own actions, exactly as before — it
|
|
113
|
+
* keeps no timer either way. See {@link PaymentsSettingsLiveSignal}.
|
|
114
|
+
*/
|
|
115
|
+
live?: PaymentsSettingsLiveSignal;
|
|
105
116
|
/**
|
|
106
117
|
* The activation step, rendered under the connection card.
|
|
107
118
|
*
|
|
@@ -299,8 +310,10 @@ export function PaymentProviderSettings({
|
|
|
299
310
|
selectedProvider,
|
|
300
311
|
onProviderChange,
|
|
301
312
|
renderVerification,
|
|
313
|
+
live,
|
|
302
314
|
}: PaymentProviderSettingsProps) {
|
|
303
315
|
const { view, error, reload } = useSettingsState(client);
|
|
316
|
+
useSettingsLive(live, () => void reload());
|
|
304
317
|
const { selected, setSelected } = useSelectedProvider(
|
|
305
318
|
initialProvider,
|
|
306
319
|
selectedProvider,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The host's realtime channel for the settings screen (FUT-648).
|
|
7
|
+
*
|
|
8
|
+
* A provider's connection moves while nobody touches this tab: the OAuth grant
|
|
9
|
+
* completes in the tab the owner sent to the provider, a colleague saves a key
|
|
10
|
+
* or reorders the chain, a webhook or a sweep settles the activation charge.
|
|
11
|
+
* The screen read its view once on mount and then sat on it, so all of that
|
|
12
|
+
* reached the owner only through a reload.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here knows what the channel is — the same split as the checkout's
|
|
15
|
+
* `CheckoutLiveSignal`. A host that can hear "this store's payment settings
|
|
16
|
+
* moved" passes whether its channel is live and a way to be told when it
|
|
17
|
+
* fires; the screen re-reads its view on every hint and once each time the
|
|
18
|
+
* channel (re)opens, because a hint published while it was down reached
|
|
19
|
+
* nobody. The hint carries nothing and is never trusted as data: the re-read
|
|
20
|
+
* goes through the same client, which is the only thing that decides what the
|
|
21
|
+
* owner may see.
|
|
22
|
+
*
|
|
23
|
+
* There is no timer, live or not. The screen never polled, so a host without a
|
|
24
|
+
* channel — or one whose channel is down — keeps exactly the mount read and its
|
|
25
|
+
* own actions' re-reads, as before.
|
|
26
|
+
*/
|
|
27
|
+
export interface PaymentsSettingsLiveSignal {
|
|
28
|
+
/** Whether the host's channel is connected right now. */
|
|
29
|
+
live: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Be told when the channel says this store's payment settings moved.
|
|
32
|
+
* Returns the unsubscribe. Should be stable across renders: a new function
|
|
33
|
+
* re-subscribes.
|
|
34
|
+
*/
|
|
35
|
+
subscribe: (onHint: () => void) => () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Re-read on every hint, and once whenever the channel goes from not-live to
|
|
40
|
+
* live — the first connect after mount included, since the mount read may
|
|
41
|
+
* have been answered before the subscription existed.
|
|
42
|
+
*
|
|
43
|
+
* `reload` is read through a ref so a host re-rendering with a new function
|
|
44
|
+
* neither re-subscribes nor re-reads.
|
|
45
|
+
*/
|
|
46
|
+
export function useSettingsLive(
|
|
47
|
+
signal: PaymentsSettingsLiveSignal | undefined,
|
|
48
|
+
reload: () => void,
|
|
49
|
+
): void {
|
|
50
|
+
const latest = useRef(reload);
|
|
51
|
+
latest.current = reload;
|
|
52
|
+
const live = signal?.live === true;
|
|
53
|
+
const wasLive = useRef(live);
|
|
54
|
+
const subscribe = signal?.subscribe;
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const opened = live && !wasLive.current;
|
|
58
|
+
wasLive.current = live;
|
|
59
|
+
if (opened) latest.current();
|
|
60
|
+
}, [live]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!subscribe) return undefined;
|
|
64
|
+
return subscribe(() => latest.current());
|
|
65
|
+
}, [subscribe]);
|
|
66
|
+
}
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
* settings page behind.
|
|
19
19
|
*
|
|
20
20
|
* **`prepareConnect`, `selectedProvider` / `onProviderChange`,
|
|
21
|
-
* `initialProvider`, `onChanged`.** The OAuth CSRF state is minted against
|
|
21
|
+
* `initialProvider`, `onChanged`, `live`.** The OAuth CSRF state is minted against
|
|
22
22
|
* the host's admin session; which provider is open may live in the host's
|
|
23
23
|
* URL. Both are the host's, per render, and a factory that froze them would
|
|
24
|
-
* make the page uncontrollable.
|
|
24
|
+
* make the page uncontrollable. `live` is the host's realtime channel, whose
|
|
25
|
+
* status changes from render to render (FUT-648).
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
import type { ComponentType, JSX } from "react";
|
|
@@ -32,6 +33,9 @@ import {
|
|
|
32
33
|
} from "../components/PaymentProviderSettings";
|
|
33
34
|
import type { PaymentsSettingsClient } from "../client";
|
|
34
35
|
|
|
36
|
+
/** The `live` prop's shape — the host's channel, see `../components/settings-live`. */
|
|
37
|
+
export type { PaymentsSettingsLiveSignal } from "../components/settings-live";
|
|
38
|
+
|
|
35
39
|
/** What a host binds ONCE: the settings port. */
|
|
36
40
|
export interface PaymentsSettingsWebConfig {
|
|
37
41
|
/**
|
package/src/index.ts
CHANGED
|
@@ -323,7 +323,7 @@ export {
|
|
|
323
323
|
type PaymentProviderSettingsProps,
|
|
324
324
|
} from './components/PaymentProviderSettings';
|
|
325
325
|
export { createWebPaymentsSettings } from './flows/create-payments-settings';
|
|
326
|
-
export type { BoundPaymentsSettingsProps, PaymentsSettingsSurface, PaymentsSettingsWebConfig } from './flows/create-payments-settings';
|
|
326
|
+
export type { BoundPaymentsSettingsProps, PaymentsSettingsLiveSignal, PaymentsSettingsSurface, PaymentsSettingsWebConfig } from './flows/create-payments-settings';
|
|
327
327
|
|
|
328
328
|
// ---------------------------------------------------------------------------
|
|
329
329
|
// The PLATFORM operations screens (FUT-479 / FUT-483, packaged by FUT-573) —
|