@phala/pay 0.7.0 → 0.8.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/README.md +44 -22
- package/dist/checkout.d.ts +9 -1
- package/dist/checkout.d.ts.map +1 -1
- package/dist/checkout.js +22 -2
- package/dist/checkout.js.map +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/client.js.map +1 -1
- package/dist/deposit-address.d.ts +3 -0
- package/dist/deposit-address.d.ts.map +1 -1
- package/dist/deposit-address.js +6 -1
- package/dist/deposit-address.js.map +1 -1
- package/dist/react/Checkout.d.ts +1 -1
- package/dist/react/Checkout.d.ts.map +1 -1
- package/dist/react/Checkout.js +14 -9
- package/dist/react/Checkout.js.map +1 -1
- package/dist/react/DepositAddress.d.ts +1 -1
- package/dist/react/DepositAddress.d.ts.map +1 -1
- package/dist/react/DepositAddress.js +7 -9
- package/dist/react/DepositAddress.js.map +1 -1
- package/dist/react/appearance.d.ts +1 -22
- package/dist/react/appearance.d.ts.map +1 -1
- package/dist/react/appearance.js +1 -100
- package/dist/react/appearance.js.map +1 -1
- package/dist/react/useCheckout.d.ts +3 -9
- package/dist/react/useCheckout.d.ts.map +1 -1
- package/dist/react/useCheckout.js +5 -3
- package/dist/react/useCheckout.js.map +1 -1
- package/dist/request.d.ts +6 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +6 -0
- package/dist/request.js.map +1 -0
- package/package.json +13 -9
- package/src/checkout.ts +32 -3
- package/src/client.ts +8 -2
- package/src/css.d.ts +1 -0
- package/src/deposit-address.ts +9 -1
- package/src/react/Checkout.tsx +13 -9
- package/src/react/DepositAddress.tsx +28 -31
- package/src/react/appearance.ts +1 -119
- package/src/react/useCheckout.ts +15 -9
- package/src/request.ts +8 -0
- package/src/styles.css +76 -0
|
@@ -1,26 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
/** Theming in the spirit of Stripe's Appearance API: a base theme plus a few variables. */
|
|
1
|
+
/** Base theme; customize CSS properties on `.pp-root` in your stylesheet. */
|
|
3
2
|
export interface Appearance {
|
|
4
3
|
theme?: "light" | "dark";
|
|
5
|
-
variables?: {
|
|
6
|
-
colorPrimary?: string;
|
|
7
|
-
/** The color of text appearing on top of any `colorPrimary` background, such as the pay button. */
|
|
8
|
-
accessibleColorOnColorPrimary?: string;
|
|
9
|
-
colorBackground?: string;
|
|
10
|
-
colorText?: string;
|
|
11
|
-
colorTextSecondary?: string;
|
|
12
|
-
colorBorder?: string;
|
|
13
|
-
colorDanger?: string;
|
|
14
|
-
colorSuccess?: string;
|
|
15
|
-
fontFamily?: string;
|
|
16
|
-
borderRadius?: string;
|
|
17
|
-
};
|
|
18
4
|
}
|
|
19
|
-
/** The root element's custom properties for `appearance.variables`. */
|
|
20
|
-
export declare function appearanceStyle(appearance: Appearance | undefined): CSSProperties;
|
|
21
|
-
/**
|
|
22
|
-
* Every rule is scoped under `.pp-root`; the variables can also be set from the page's own CSS on
|
|
23
|
-
* `.pp-root`.
|
|
24
|
-
*/
|
|
25
|
-
export declare const STYLES = "\n.pp-root {\n --pp-color-primary: #0f62fe;\n --pp-accessible-color-on-color-primary: #ffffff;\n --pp-color-background: #ffffff;\n --pp-color-text: #1a1a1a;\n --pp-color-text-secondary: #5c5f66;\n --pp-color-border: #d9dce1;\n --pp-color-danger: #c62828;\n --pp-color-success: #1b7f3b;\n --pp-font-family: system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n --pp-border-radius: 8px;\n box-sizing: border-box;\n max-width: 440px;\n padding: 20px;\n border: 1px solid var(--pp-color-border);\n border-radius: var(--pp-border-radius);\n background: var(--pp-color-background);\n color: var(--pp-color-text);\n font-family: var(--pp-font-family);\n font-size: 14px;\n line-height: 1.45;\n}\n.pp-root[data-theme=\"dark\"] {\n --pp-color-primary: #78a9ff;\n --pp-color-background: #161616;\n --pp-color-text: #f4f4f4;\n --pp-color-text-secondary: #a8a8a8;\n --pp-color-border: #393939;\n --pp-color-danger: #ff8389;\n --pp-color-success: #42be65;\n}\n.pp-root *, .pp-root *::before, .pp-root *::after { box-sizing: inherit; }\n.pp-amount { margin: 0; overflow-wrap: anywhere; font-size: 22px; font-weight: 600; }\n.pp-subtitle { margin: 2px 0 16px; color: var(--pp-color-text-secondary); }\n.pp-badge { font-weight: 600; }\n.pp-payments { list-style: none; margin: 12px 0 0; padding: 0; }\n.pp-payments li { margin: 4px 0; }\n.pp-status { display: flex; gap: 8px; align-items: baseline; justify-content: space-between;\n margin-bottom: 12px; padding: 10px 12px; border-radius: var(--pp-border-radius);\n border: 1px solid var(--pp-color-border); }\n.pp-status[data-tone=\"success\"] { border-color: var(--pp-color-success); color: var(--pp-color-success); }\n.pp-status[data-tone=\"danger\"] { border-color: var(--pp-color-danger); color: var(--pp-color-danger); }\n.pp-countdown { font-variant-numeric: tabular-nums; white-space: nowrap; }\n.pp-notice { margin: 0 0 16px; color: var(--pp-color-text-secondary); font-size: 13px; }\n.pp-notice strong { color: var(--pp-color-text); }\n.pp-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--pp-color-border); margin-bottom: 16px; }\n.pp-tab { appearance: none; border: 0; border-bottom: 2px solid transparent; background: none;\n color: var(--pp-color-text-secondary); font: inherit; padding: 8px 10px; cursor: pointer; }\n.pp-tab[aria-selected=\"true\"] { color: var(--pp-color-text); border-bottom-color: var(--pp-color-primary); font-weight: 600; }\n.pp-root button:focus-visible, .pp-root a:focus-visible { outline: 2px solid var(--pp-color-primary); outline-offset: 2px; }\n.pp-wallets { display: grid; gap: 8px; }\n.pp-button { display: flex; gap: 10px; align-items: center; justify-content: center; width: 100%;\n appearance: none; border: 1px solid var(--pp-color-primary); border-radius: var(--pp-border-radius);\n background: var(--pp-color-primary); color: var(--pp-accessible-color-on-color-primary); font: inherit; font-weight: 600; padding: 10px 14px; cursor: pointer; }\n.pp-button:disabled { opacity: 0.6; cursor: progress; }\n.pp-button img { width: 20px; height: 20px; }\n.pp-wallet-name { font-weight: 400; opacity: 0.85; }\n.pp-message { margin: 10px 0 0; color: var(--pp-color-text-secondary); }\n.pp-message[data-tone=\"danger\"] { color: var(--pp-color-danger); }\n.pp-qr-panel { display: grid; justify-items: center; gap: 10px; text-align: center; }\n.pp-qr { border-radius: 4px; }\n.pp-fields { display: grid; gap: 10px; margin: 0; }\n.pp-field dt { color: var(--pp-color-text-secondary); font-size: 12px; }\n.pp-field dd { display: flex; gap: 8px; align-items: center; margin: 2px 0 0; }\n.pp-value { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }\n.pp-copy { flex: none; appearance: none; border: 1px solid var(--pp-color-border); border-radius: 6px;\n background: none; color: var(--pp-color-text); font: inherit; font-size: 12px; padding: 3px 8px; cursor: pointer; }\n.pp-tx { margin: 0 0 12px; font-size: 13px; }\n.pp-tx a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }\n.pp-tx .pp-value { overflow-wrap: anywhere; }\n";
|
|
26
5
|
//# sourceMappingURL=appearance.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../../src/react/appearance.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"appearance.d.ts","sourceRoot":"","sources":["../../src/react/appearance.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B"}
|
package/dist/react/appearance.js
CHANGED
|
@@ -1,101 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
colorPrimary: "--pp-color-primary",
|
|
3
|
-
accessibleColorOnColorPrimary: "--pp-accessible-color-on-color-primary",
|
|
4
|
-
colorBackground: "--pp-color-background",
|
|
5
|
-
colorText: "--pp-color-text",
|
|
6
|
-
colorTextSecondary: "--pp-color-text-secondary",
|
|
7
|
-
colorBorder: "--pp-color-border",
|
|
8
|
-
colorDanger: "--pp-color-danger",
|
|
9
|
-
colorSuccess: "--pp-color-success",
|
|
10
|
-
fontFamily: "--pp-font-family",
|
|
11
|
-
borderRadius: "--pp-border-radius",
|
|
12
|
-
};
|
|
13
|
-
/** The root element's custom properties for `appearance.variables`. */
|
|
14
|
-
export function appearanceStyle(appearance) {
|
|
15
|
-
const variables = appearance?.variables ?? {};
|
|
16
|
-
const style = {};
|
|
17
|
-
for (const [name, property] of Object.entries(VARIABLES)) {
|
|
18
|
-
const value = variables[name];
|
|
19
|
-
if (value !== undefined) {
|
|
20
|
-
style[property] = value;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return style;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Every rule is scoped under `.pp-root`; the variables can also be set from the page's own CSS on
|
|
27
|
-
* `.pp-root`.
|
|
28
|
-
*/
|
|
29
|
-
export const STYLES = `
|
|
30
|
-
.pp-root {
|
|
31
|
-
--pp-color-primary: #0f62fe;
|
|
32
|
-
--pp-accessible-color-on-color-primary: #ffffff;
|
|
33
|
-
--pp-color-background: #ffffff;
|
|
34
|
-
--pp-color-text: #1a1a1a;
|
|
35
|
-
--pp-color-text-secondary: #5c5f66;
|
|
36
|
-
--pp-color-border: #d9dce1;
|
|
37
|
-
--pp-color-danger: #c62828;
|
|
38
|
-
--pp-color-success: #1b7f3b;
|
|
39
|
-
--pp-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
40
|
-
--pp-border-radius: 8px;
|
|
41
|
-
box-sizing: border-box;
|
|
42
|
-
max-width: 440px;
|
|
43
|
-
padding: 20px;
|
|
44
|
-
border: 1px solid var(--pp-color-border);
|
|
45
|
-
border-radius: var(--pp-border-radius);
|
|
46
|
-
background: var(--pp-color-background);
|
|
47
|
-
color: var(--pp-color-text);
|
|
48
|
-
font-family: var(--pp-font-family);
|
|
49
|
-
font-size: 14px;
|
|
50
|
-
line-height: 1.45;
|
|
51
|
-
}
|
|
52
|
-
.pp-root[data-theme="dark"] {
|
|
53
|
-
--pp-color-primary: #78a9ff;
|
|
54
|
-
--pp-color-background: #161616;
|
|
55
|
-
--pp-color-text: #f4f4f4;
|
|
56
|
-
--pp-color-text-secondary: #a8a8a8;
|
|
57
|
-
--pp-color-border: #393939;
|
|
58
|
-
--pp-color-danger: #ff8389;
|
|
59
|
-
--pp-color-success: #42be65;
|
|
60
|
-
}
|
|
61
|
-
.pp-root *, .pp-root *::before, .pp-root *::after { box-sizing: inherit; }
|
|
62
|
-
.pp-amount { margin: 0; overflow-wrap: anywhere; font-size: 22px; font-weight: 600; }
|
|
63
|
-
.pp-subtitle { margin: 2px 0 16px; color: var(--pp-color-text-secondary); }
|
|
64
|
-
.pp-badge { font-weight: 600; }
|
|
65
|
-
.pp-payments { list-style: none; margin: 12px 0 0; padding: 0; }
|
|
66
|
-
.pp-payments li { margin: 4px 0; }
|
|
67
|
-
.pp-status { display: flex; gap: 8px; align-items: baseline; justify-content: space-between;
|
|
68
|
-
margin-bottom: 12px; padding: 10px 12px; border-radius: var(--pp-border-radius);
|
|
69
|
-
border: 1px solid var(--pp-color-border); }
|
|
70
|
-
.pp-status[data-tone="success"] { border-color: var(--pp-color-success); color: var(--pp-color-success); }
|
|
71
|
-
.pp-status[data-tone="danger"] { border-color: var(--pp-color-danger); color: var(--pp-color-danger); }
|
|
72
|
-
.pp-countdown { font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
73
|
-
.pp-notice { margin: 0 0 16px; color: var(--pp-color-text-secondary); font-size: 13px; }
|
|
74
|
-
.pp-notice strong { color: var(--pp-color-text); }
|
|
75
|
-
.pp-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--pp-color-border); margin-bottom: 16px; }
|
|
76
|
-
.pp-tab { appearance: none; border: 0; border-bottom: 2px solid transparent; background: none;
|
|
77
|
-
color: var(--pp-color-text-secondary); font: inherit; padding: 8px 10px; cursor: pointer; }
|
|
78
|
-
.pp-tab[aria-selected="true"] { color: var(--pp-color-text); border-bottom-color: var(--pp-color-primary); font-weight: 600; }
|
|
79
|
-
.pp-root button:focus-visible, .pp-root a:focus-visible { outline: 2px solid var(--pp-color-primary); outline-offset: 2px; }
|
|
80
|
-
.pp-wallets { display: grid; gap: 8px; }
|
|
81
|
-
.pp-button { display: flex; gap: 10px; align-items: center; justify-content: center; width: 100%;
|
|
82
|
-
appearance: none; border: 1px solid var(--pp-color-primary); border-radius: var(--pp-border-radius);
|
|
83
|
-
background: var(--pp-color-primary); color: var(--pp-accessible-color-on-color-primary); font: inherit; font-weight: 600; padding: 10px 14px; cursor: pointer; }
|
|
84
|
-
.pp-button:disabled { opacity: 0.6; cursor: progress; }
|
|
85
|
-
.pp-button img { width: 20px; height: 20px; }
|
|
86
|
-
.pp-wallet-name { font-weight: 400; opacity: 0.85; }
|
|
87
|
-
.pp-message { margin: 10px 0 0; color: var(--pp-color-text-secondary); }
|
|
88
|
-
.pp-message[data-tone="danger"] { color: var(--pp-color-danger); }
|
|
89
|
-
.pp-qr-panel { display: grid; justify-items: center; gap: 10px; text-align: center; }
|
|
90
|
-
.pp-qr { border-radius: 4px; }
|
|
91
|
-
.pp-fields { display: grid; gap: 10px; margin: 0; }
|
|
92
|
-
.pp-field dt { color: var(--pp-color-text-secondary); font-size: 12px; }
|
|
93
|
-
.pp-field dd { display: flex; gap: 8px; align-items: center; margin: 2px 0 0; }
|
|
94
|
-
.pp-value { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; word-break: break-all; }
|
|
95
|
-
.pp-copy { flex: none; appearance: none; border: 1px solid var(--pp-color-border); border-radius: 6px;
|
|
96
|
-
background: none; color: var(--pp-color-text); font: inherit; font-size: 12px; padding: 3px 8px; cursor: pointer; }
|
|
97
|
-
.pp-tx { margin: 0 0 12px; font-size: 13px; }
|
|
98
|
-
.pp-tx a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
|
|
99
|
-
.pp-tx .pp-value { overflow-wrap: anywhere; }
|
|
100
|
-
`;
|
|
1
|
+
export {};
|
|
101
2
|
//# sourceMappingURL=appearance.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appearance.js","sourceRoot":"","sources":["../../src/react/appearance.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"appearance.js","sourceRoot":"","sources":["../../src/react/appearance.ts"],"names":[],"mappings":""}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { type CheckoutState } from "../checkout.js";
|
|
2
|
-
export
|
|
3
|
-
clientSecret: string;
|
|
4
|
-
/** The quote's address as your backend recomputed it; another one fails closed. */
|
|
5
|
-
expectedAddress: string;
|
|
6
|
-
apiBase: string;
|
|
7
|
-
pollInterval?: number;
|
|
8
|
-
}
|
|
1
|
+
import { type CheckoutOptions, type CheckoutState } from "../checkout.js";
|
|
2
|
+
export type UseCheckoutOptions = Pick<CheckoutOptions, "clientSecret" | "expectedAddress" | "apiBase" | "pollInterval" | "signal" | "requestTimeout">;
|
|
9
3
|
export interface UseCheckoutResult extends CheckoutState {
|
|
10
4
|
/** Reads the quote now, for example right after the wallet broadcast the payment. */
|
|
11
5
|
refresh: () => void;
|
|
12
6
|
}
|
|
13
7
|
/** Follows a quote's public view for as long as the component is mounted. */
|
|
14
|
-
export declare function useCheckout({ clientSecret, expectedAddress, apiBase, pollInterval, }: UseCheckoutOptions): UseCheckoutResult;
|
|
8
|
+
export declare function useCheckout({ clientSecret, expectedAddress, apiBase, pollInterval, signal, requestTimeout, }: UseCheckoutOptions): UseCheckoutResult;
|
|
15
9
|
//# sourceMappingURL=useCheckout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckout.d.ts","sourceRoot":"","sources":["../../src/react/useCheckout.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"useCheckout.d.ts","sourceRoot":"","sources":["../../src/react/useCheckout.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,eAAe,EACf,cAAc,GAAG,iBAAiB,GAAG,SAAS,GAAG,cAAc,GAAG,QAAQ,GAAG,gBAAgB,CAC9F,CAAC;AAEF,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,qFAAqF;IACrF,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,EAC1B,YAAY,EACZ,eAAe,EACf,OAAO,EACP,YAAY,EACZ,MAAM,EACN,cAAc,GACf,EAAE,kBAAkB,GAAG,iBAAiB,CA6BxC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { createCheckout } from "../checkout.js";
|
|
2
|
+
import { createCheckout, } from "../checkout.js";
|
|
3
3
|
const LOADING = { status: "loading", quote: null, error: null };
|
|
4
4
|
/** Follows a quote's public view for as long as the component is mounted. */
|
|
5
|
-
export function useCheckout({ clientSecret, expectedAddress, apiBase, pollInterval, }) {
|
|
5
|
+
export function useCheckout({ clientSecret, expectedAddress, apiBase, pollInterval, signal, requestTimeout, }) {
|
|
6
6
|
const [current, setCurrent] = useState({ key: "", state: LOADING });
|
|
7
7
|
const checkout = useRef(null);
|
|
8
8
|
const key = `${apiBase} ${clientSecret} ${expectedAddress}`;
|
|
@@ -12,6 +12,8 @@ export function useCheckout({ clientSecret, expectedAddress, apiBase, pollInterv
|
|
|
12
12
|
expectedAddress,
|
|
13
13
|
apiBase,
|
|
14
14
|
...(pollInterval === undefined ? {} : { pollInterval }),
|
|
15
|
+
...(signal === undefined ? {} : { signal }),
|
|
16
|
+
...(requestTimeout === undefined ? {} : { requestTimeout }),
|
|
15
17
|
});
|
|
16
18
|
checkout.current = instance;
|
|
17
19
|
const unsubscribe = instance.subscribe((state) => setCurrent({ key, state }));
|
|
@@ -20,7 +22,7 @@ export function useCheckout({ clientSecret, expectedAddress, apiBase, pollInterv
|
|
|
20
22
|
instance.destroy();
|
|
21
23
|
checkout.current = null;
|
|
22
24
|
};
|
|
23
|
-
}, [key, clientSecret, expectedAddress, apiBase, pollInterval]);
|
|
25
|
+
}, [key, clientSecret, expectedAddress, apiBase, pollInterval, signal, requestTimeout]);
|
|
24
26
|
const refresh = useCallback(() => {
|
|
25
27
|
void checkout.current?.refresh();
|
|
26
28
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../src/react/useCheckout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,
|
|
1
|
+
{"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../src/react/useCheckout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EACL,cAAc,GAIf,MAAM,gBAAgB,CAAC;AAYxB,MAAM,OAAO,GAAkB,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAE/E,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,EAC1B,YAAY,EACZ,eAAe,EACf,OAAO,EACP,YAAY,EACZ,MAAM,EACN,cAAc,GACK;IACnB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,MAAM,CAAyB,IAAI,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,YAAY,IAAI,eAAe,EAAE,CAAC;IAE5D,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,QAAQ,GAAG,cAAc,CAAC;YAC9B,YAAY;YACZ,eAAe;YACf,OAAO;YACP,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;YACvD,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;YAC3C,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC;SAC5D,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC;QAC5B,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC9E,OAAO,GAAG,EAAE;YACV,WAAW,EAAE,CAAC;YACd,QAAQ,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAExF,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,KAAK,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,kFAAkF;IAClF,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,WAAW,CAGd"}
|
package/dist/request.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Compose caller cancellation with a finite deadline, including response-body reads. */
|
|
2
|
+
export function requestSignal(options) {
|
|
3
|
+
const deadline = AbortSignal.timeout(options.requestTimeout ?? 10_000);
|
|
4
|
+
return options.signal === undefined ? deadline : AbortSignal.any([options.signal, deadline]);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,MAAM,UAAU,aAAa,CAAC,OAG7B;IACC,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,IAAI,MAAM,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC/F,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phala/pay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Phala Pay checkout: a framework-agnostic client and React bindings",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"crypto",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"directory": "sdk/js"
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
|
-
"sideEffects":
|
|
26
|
+
"sideEffects": [
|
|
27
|
+
"**/*.css"
|
|
28
|
+
],
|
|
27
29
|
"exports": {
|
|
28
30
|
".": {
|
|
29
31
|
"types": "./dist/index.d.ts",
|
|
@@ -36,7 +38,8 @@
|
|
|
36
38
|
"./server": {
|
|
37
39
|
"types": "./dist/server/index.d.ts",
|
|
38
40
|
"default": "./dist/server/index.js"
|
|
39
|
-
}
|
|
41
|
+
},
|
|
42
|
+
"./styles.css": "./src/styles.css"
|
|
40
43
|
},
|
|
41
44
|
"files": [
|
|
42
45
|
"dist",
|
|
@@ -47,7 +50,7 @@
|
|
|
47
50
|
},
|
|
48
51
|
"packageManager": "pnpm@12.6.0",
|
|
49
52
|
"engines": {
|
|
50
|
-
"node": ">=20"
|
|
53
|
+
"node": ">=20.3"
|
|
51
54
|
},
|
|
52
55
|
"scripts": {
|
|
53
56
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -78,16 +81,17 @@
|
|
|
78
81
|
"@testing-library/dom": "^10.4.2",
|
|
79
82
|
"@testing-library/react": "^16.3.3",
|
|
80
83
|
"@testing-library/user-event": "^14.6.7",
|
|
81
|
-
"@types/node": "^
|
|
82
|
-
"@types/react": "^
|
|
83
|
-
"@types/react-dom": "^
|
|
84
|
+
"@types/node": "^26.6.3",
|
|
85
|
+
"@types/react": "^19.3.0",
|
|
86
|
+
"@types/react-dom": "^19.3.0",
|
|
84
87
|
"@vitejs/plugin-react": "^6.1.1",
|
|
88
|
+
"axe-core": "^4.13.0",
|
|
85
89
|
"eslint": "^10.11.0",
|
|
86
90
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
87
91
|
"jsdom": "^30.1.1",
|
|
88
92
|
"jsqr": "^1.4.0",
|
|
89
|
-
"react": "^
|
|
90
|
-
"react-dom": "^
|
|
93
|
+
"react": "^19.3.0",
|
|
94
|
+
"react-dom": "^19.3.0",
|
|
91
95
|
"typescript": "~6.0.3",
|
|
92
96
|
"typescript-eslint": "^8.70.1",
|
|
93
97
|
"viem": "^2.56.9",
|
package/src/checkout.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestSignal } from "./request.js";
|
|
1
2
|
import { getAddress, isAddress, isAddressEqual } from "viem";
|
|
2
3
|
import { parseClientQuote, quoteIdFromClientSecret, type ClientQuote } from "./quote.js";
|
|
3
4
|
|
|
@@ -109,6 +110,10 @@ export interface CheckoutOptions {
|
|
|
109
110
|
apiBase: string;
|
|
110
111
|
/** Milliseconds between status reads; default 3000. */
|
|
111
112
|
pollInterval?: number;
|
|
113
|
+
/** Cancels this request/session together with its own deadline. */
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
/** Request deadline in milliseconds; default 10000. */
|
|
116
|
+
requestTimeout?: number;
|
|
112
117
|
fetch?: typeof globalThis.fetch;
|
|
113
118
|
/** Current time in milliseconds; for tests. */
|
|
114
119
|
now?: () => number;
|
|
@@ -120,7 +125,7 @@ export interface CheckoutSession {
|
|
|
120
125
|
subscribe(listener: (state: CheckoutState) => void): () => void;
|
|
121
126
|
/** Reads the quote now instead of at the next poll. */
|
|
122
127
|
refresh(): Promise<void>;
|
|
123
|
-
/** Stops polling. */
|
|
128
|
+
/** Stops polling and aborts the active request. */
|
|
124
129
|
destroy(): void;
|
|
125
130
|
}
|
|
126
131
|
|
|
@@ -132,6 +137,10 @@ export interface RetrieveQuoteOptions {
|
|
|
132
137
|
/** The address your backend recomputed; a quote naming another one is refused. */
|
|
133
138
|
expectedAddress: string;
|
|
134
139
|
apiBase: string;
|
|
140
|
+
/** Cancels this request/session together with its own deadline. */
|
|
141
|
+
signal?: AbortSignal;
|
|
142
|
+
/** Request deadline in milliseconds; default 10000. */
|
|
143
|
+
requestTimeout?: number;
|
|
135
144
|
fetch?: typeof globalThis.fetch;
|
|
136
145
|
}
|
|
137
146
|
|
|
@@ -147,7 +156,11 @@ export async function retrieveQuote(options: RetrieveQuoteOptions): Promise<Clie
|
|
|
147
156
|
const url = `${base}/v1/quotes/${quoteId}?client_secret=${encodeURIComponent(options.clientSecret)}`;
|
|
148
157
|
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
149
158
|
// A simple GET with no custom headers, so the browser sends no CORS preflight.
|
|
150
|
-
const response = await fetchImpl(url, {
|
|
159
|
+
const response = await fetchImpl(url, {
|
|
160
|
+
cache: "no-store",
|
|
161
|
+
credentials: "omit",
|
|
162
|
+
signal: requestSignal(options),
|
|
163
|
+
});
|
|
151
164
|
if (!response.ok) {
|
|
152
165
|
throw responseError(response, "the quote or its client secret is unknown");
|
|
153
166
|
}
|
|
@@ -198,6 +211,11 @@ export function checkoutStatus(quote: ClientQuote, nowSeconds: number): Checkout
|
|
|
198
211
|
export function createCheckout(options: CheckoutOptions): CheckoutSession {
|
|
199
212
|
quoteIdFromClientSecret(options.clientSecret);
|
|
200
213
|
expectedAddress(options.expectedAddress);
|
|
214
|
+
const controller = new AbortController();
|
|
215
|
+
const signal =
|
|
216
|
+
options.signal === undefined
|
|
217
|
+
? controller.signal
|
|
218
|
+
: AbortSignal.any([controller.signal, options.signal]);
|
|
201
219
|
const now = options.now ?? Date.now;
|
|
202
220
|
const interval = options.pollInterval ?? DEFAULT_POLL_INTERVAL;
|
|
203
221
|
const listeners = new Set<(state: CheckoutState) => void>();
|
|
@@ -226,6 +244,7 @@ export function createCheckout(options: CheckoutOptions): CheckoutSession {
|
|
|
226
244
|
const { status, quote } = state;
|
|
227
245
|
return (
|
|
228
246
|
destroyed ||
|
|
247
|
+
signal.aborted ||
|
|
229
248
|
status === "error" ||
|
|
230
249
|
status === "credited" ||
|
|
231
250
|
status === "rejected" ||
|
|
@@ -237,10 +256,16 @@ export function createCheckout(options: CheckoutOptions): CheckoutSession {
|
|
|
237
256
|
|
|
238
257
|
async function load(): Promise<void> {
|
|
239
258
|
try {
|
|
240
|
-
const quote = await retrieveQuote(options);
|
|
259
|
+
const quote = await retrieveQuote({ ...options, signal });
|
|
260
|
+
if (destroyed || signal.aborted) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
241
263
|
failures = 0;
|
|
242
264
|
setState({ status: checkoutStatus(quote, now() / 1000), quote, error: null });
|
|
243
265
|
} catch (cause) {
|
|
266
|
+
if (destroyed || signal.aborted) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
244
269
|
const error =
|
|
245
270
|
cause instanceof CheckoutError
|
|
246
271
|
? cause
|
|
@@ -263,6 +288,9 @@ export function createCheckout(options: CheckoutOptions): CheckoutSession {
|
|
|
263
288
|
}
|
|
264
289
|
|
|
265
290
|
function refresh(): Promise<void> {
|
|
291
|
+
if (destroyed || signal.aborted) {
|
|
292
|
+
return Promise.resolve();
|
|
293
|
+
}
|
|
266
294
|
inFlight ??= load().finally(() => {
|
|
267
295
|
inFlight = undefined;
|
|
268
296
|
});
|
|
@@ -292,6 +320,7 @@ export function createCheckout(options: CheckoutOptions): CheckoutSession {
|
|
|
292
320
|
refresh,
|
|
293
321
|
destroy() {
|
|
294
322
|
destroyed = true;
|
|
323
|
+
controller.abort();
|
|
295
324
|
clearTimeout(timer);
|
|
296
325
|
listeners.clear();
|
|
297
326
|
},
|
package/src/client.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
retrieveQuote,
|
|
4
4
|
type CheckoutOptions,
|
|
5
5
|
type CheckoutSession,
|
|
6
|
+
type RetrieveQuoteOptions,
|
|
6
7
|
} from "./checkout.js";
|
|
7
8
|
import type { ClientQuote } from "./quote.js";
|
|
8
9
|
|
|
@@ -32,10 +33,15 @@ export class PhalaPay {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** Reads the quote's public view once, refusing one whose address is not `expectedAddress`. */
|
|
35
|
-
retrieveQuote(
|
|
36
|
+
retrieveQuote(
|
|
37
|
+
clientSecret: string,
|
|
38
|
+
expectedAddress: string,
|
|
39
|
+
options: Pick<RetrieveQuoteOptions, "signal" | "requestTimeout"> = {},
|
|
40
|
+
): Promise<ClientQuote> {
|
|
36
41
|
return retrieveQuote({
|
|
37
42
|
clientSecret,
|
|
38
43
|
expectedAddress,
|
|
44
|
+
...options,
|
|
39
45
|
apiBase: this.apiBase,
|
|
40
46
|
...this.#fetchOption(),
|
|
41
47
|
});
|
|
@@ -44,7 +50,7 @@ export class PhalaPay {
|
|
|
44
50
|
/** Follows the quote until it is credited, rejected, reversed, canceled, or expired. */
|
|
45
51
|
checkout(
|
|
46
52
|
clientSecret: string,
|
|
47
|
-
options: Pick<CheckoutOptions, "expectedAddress" | "pollInterval">,
|
|
53
|
+
options: Pick<CheckoutOptions, "expectedAddress" | "pollInterval" | "signal" | "requestTimeout">,
|
|
48
54
|
): CheckoutSession {
|
|
49
55
|
return createCheckout({
|
|
50
56
|
clientSecret,
|
package/src/css.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "*.css";
|
package/src/deposit-address.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestSignal } from "./request.js";
|
|
1
2
|
import { CheckoutError, responseError } from "./checkout.js";
|
|
2
3
|
|
|
3
4
|
/** A payment to a deposit address as the customer's page sees it. Display only. */
|
|
@@ -51,6 +52,9 @@ export interface RetrieveDepositAddressOptions {
|
|
|
51
52
|
/** A `client_secret` from your backend's `POST /v1/deposit_addresses` or `…/rotate`. */
|
|
52
53
|
clientSecret: string;
|
|
53
54
|
apiBase: string;
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
/** Request deadline in milliseconds; default 10000. */
|
|
57
|
+
requestTimeout?: number;
|
|
54
58
|
fetch?: typeof globalThis.fetch;
|
|
55
59
|
}
|
|
56
60
|
|
|
@@ -77,7 +81,11 @@ export async function retrieveDepositAddress(
|
|
|
77
81
|
const url = `${base}/v1/deposit_addresses/${id}?client_secret=${encodeURIComponent(options.clientSecret)}`;
|
|
78
82
|
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
79
83
|
// A simple GET with no custom headers, so the browser sends no CORS preflight.
|
|
80
|
-
const response = await fetchImpl(url, {
|
|
84
|
+
const response = await fetchImpl(url, {
|
|
85
|
+
cache: "no-store",
|
|
86
|
+
credentials: "omit",
|
|
87
|
+
signal: requestSignal(options),
|
|
88
|
+
});
|
|
81
89
|
if (!response.ok) {
|
|
82
90
|
throw responseError(response, "the address or its client secret is unknown");
|
|
83
91
|
}
|
package/src/react/Checkout.tsx
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type EthereumProvider,
|
|
22
22
|
type Wallet,
|
|
23
23
|
} from "../wallet.js";
|
|
24
|
-
import {
|
|
24
|
+
import type { Appearance } from "./appearance.js";
|
|
25
25
|
import { Field } from "./Field.js";
|
|
26
26
|
import { QrCode } from "./QrCode.js";
|
|
27
27
|
import { useCheckout } from "./useCheckout.js";
|
|
@@ -104,17 +104,23 @@ export function Checkout({
|
|
|
104
104
|
useEffect(() => {
|
|
105
105
|
callbacks.current.onChange?.(state.current);
|
|
106
106
|
}, [status]);
|
|
107
|
-
const notified = useRef<string
|
|
107
|
+
const notified = useRef(new Set<string>());
|
|
108
108
|
useEffect(() => {
|
|
109
|
-
if (quote === null
|
|
109
|
+
if (quote === null) {
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
if (status === "credited") {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
const key = `${quote.id}:success`;
|
|
114
|
+
if (!notified.current.has(key)) {
|
|
115
|
+
notified.current.add(key);
|
|
116
|
+
callbacks.current.onSuccess?.(quote);
|
|
117
|
+
}
|
|
115
118
|
} else if (status === "expired" || status === "canceled") {
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
const key = `${quote.id}:expire`;
|
|
120
|
+
if (!notified.current.has(key)) {
|
|
121
|
+
notified.current.add(key);
|
|
122
|
+
callbacks.current.onExpire?.(quote);
|
|
123
|
+
}
|
|
118
124
|
}
|
|
119
125
|
}, [status, quote]);
|
|
120
126
|
|
|
@@ -122,9 +128,7 @@ export function Checkout({
|
|
|
122
128
|
<div
|
|
123
129
|
className={className === undefined ? "pp-root" : `pp-root ${className}`}
|
|
124
130
|
data-theme={appearance?.theme ?? "light"}
|
|
125
|
-
style={appearanceStyle(appearance)}
|
|
126
131
|
>
|
|
127
|
-
<style>{STYLES}</style>
|
|
128
132
|
{quote !== null && (
|
|
129
133
|
<>
|
|
130
134
|
<p className="pp-amount">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useEffect, useId, useState } from "react";
|
|
4
4
|
import { formatUnits } from "viem";
|
|
5
5
|
import { networkName } from "../chains.js";
|
|
6
6
|
import {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { CheckoutError, pollDelay } from "../checkout.js";
|
|
12
12
|
import { formatWait } from "../format.js";
|
|
13
13
|
import { depositAddressTransfer, type DepositAddressDetails } from "../payment.js";
|
|
14
|
-
import {
|
|
14
|
+
import type { Appearance } from "./appearance.js";
|
|
15
15
|
import { Field } from "./Field.js";
|
|
16
16
|
import { QrCode } from "./QrCode.js";
|
|
17
17
|
|
|
@@ -55,6 +55,7 @@ export function DepositAddress({
|
|
|
55
55
|
className,
|
|
56
56
|
}: DepositAddressProps) {
|
|
57
57
|
const { networks } = depositAddress;
|
|
58
|
+
const id = useId();
|
|
58
59
|
const view = useClientView(clientSecret, apiBase, pollInterval ?? 3000);
|
|
59
60
|
const payments = view?.payments ?? [];
|
|
60
61
|
const [selectedChain, setSelectedChain] = useState(chainId ?? networks[0]?.chain_id);
|
|
@@ -76,49 +77,43 @@ export function DepositAddress({
|
|
|
76
77
|
<div
|
|
77
78
|
className={className === undefined ? "pp-root" : `pp-root ${className}`}
|
|
78
79
|
data-theme={appearance?.theme ?? "light"}
|
|
79
|
-
style={appearanceStyle(appearance)}
|
|
80
80
|
>
|
|
81
|
-
<style>{STYLES}</style>
|
|
82
81
|
<p className="pp-subtitle">
|
|
83
82
|
{depositAddress.address === null
|
|
84
83
|
? "Your deposit address for every supported token; it differs on some networks"
|
|
85
84
|
: "One address for all supported tokens and networks"}
|
|
86
85
|
</p>
|
|
87
86
|
{networks.length > 1 && (
|
|
88
|
-
<
|
|
87
|
+
<fieldset className="pp-choices">
|
|
88
|
+
<legend>Network</legend>
|
|
89
89
|
{networks.map((each) => (
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
setSelectedChain(each.chain_id);
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
90
|
+
<label key={each.chain_id}>
|
|
91
|
+
<input
|
|
92
|
+
type="radio"
|
|
93
|
+
name={`${id}-network`}
|
|
94
|
+
checked={each.chain_id === network.chain_id}
|
|
95
|
+
onChange={() => setSelectedChain(each.chain_id)}
|
|
96
|
+
/>
|
|
100
97
|
{networkName(each.chain_id)}
|
|
101
|
-
</
|
|
98
|
+
</label>
|
|
102
99
|
))}
|
|
103
|
-
</
|
|
100
|
+
</fieldset>
|
|
104
101
|
)}
|
|
105
102
|
{network.assets.length > 1 && (
|
|
106
|
-
<
|
|
103
|
+
<fieldset className="pp-choices">
|
|
104
|
+
<legend>Token</legend>
|
|
107
105
|
{network.assets.map((each) => (
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
setSelectedAsset(each.asset);
|
|
116
|
-
}}
|
|
117
|
-
>
|
|
106
|
+
<label key={each.asset}>
|
|
107
|
+
<input
|
|
108
|
+
type="radio"
|
|
109
|
+
name={`${id}-token`}
|
|
110
|
+
checked={each.asset === token.asset}
|
|
111
|
+
onChange={() => setSelectedAsset(each.asset)}
|
|
112
|
+
/>
|
|
118
113
|
{each.asset.toUpperCase()}
|
|
119
|
-
</
|
|
114
|
+
</label>
|
|
120
115
|
))}
|
|
121
|
-
</
|
|
116
|
+
</fieldset>
|
|
122
117
|
)}
|
|
123
118
|
<div className="pp-qr-panel">
|
|
124
119
|
<QrCode value={token.payment_uri} label={`Deposit address for ${symbol} on ${name}`} />
|
|
@@ -161,13 +156,14 @@ function useClientView(
|
|
|
161
156
|
if (clientSecret === undefined || apiBase === undefined) {
|
|
162
157
|
return;
|
|
163
158
|
}
|
|
159
|
+
const controller = new AbortController();
|
|
164
160
|
let stopped = false;
|
|
165
161
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
166
162
|
let failures = 0;
|
|
167
163
|
const load = async () => {
|
|
168
164
|
let failure: CheckoutError | null = null;
|
|
169
165
|
try {
|
|
170
|
-
const view = await retrieveDepositAddress({ clientSecret, apiBase });
|
|
166
|
+
const view = await retrieveDepositAddress({ clientSecret, apiBase, signal: controller.signal });
|
|
171
167
|
failures = 0;
|
|
172
168
|
if (!stopped) {
|
|
173
169
|
setCurrent({ key, view });
|
|
@@ -187,6 +183,7 @@ function useClientView(
|
|
|
187
183
|
void load();
|
|
188
184
|
return () => {
|
|
189
185
|
stopped = true;
|
|
186
|
+
controller.abort();
|
|
190
187
|
clearTimeout(timer);
|
|
191
188
|
};
|
|
192
189
|
}, [key, clientSecret, apiBase, interval]);
|