@blocklet/payment-react 1.18.25 → 1.18.27
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/es/checkout/donate.js +11 -1
- package/es/components/country-select.js +243 -21
- package/es/components/over-due-invoice-payment.d.ts +3 -1
- package/es/components/over-due-invoice-payment.js +6 -4
- package/es/contexts/payment.d.ts +2 -1
- package/es/contexts/payment.js +8 -1
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/libs/api.js +4 -0
- package/es/libs/currency.d.ts +3 -0
- package/es/libs/currency.js +22 -0
- package/es/libs/phone-validator.js +2 -0
- package/es/libs/validator.d.ts +1 -0
- package/es/libs/validator.js +70 -0
- package/es/payment/form/address.js +17 -3
- package/es/payment/form/index.js +10 -1
- package/es/payment/form/phone.js +12 -1
- package/es/payment/form/stripe/form.js +14 -5
- package/es/payment/index.js +33 -11
- package/es/payment/product-donation.js +110 -12
- package/es/types/shims.d.ts +2 -0
- package/lib/checkout/donate.js +11 -1
- package/lib/components/country-select.js +243 -39
- package/lib/components/over-due-invoice-payment.d.ts +3 -1
- package/lib/components/over-due-invoice-payment.js +7 -4
- package/lib/contexts/payment.d.ts +2 -1
- package/lib/contexts/payment.js +9 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/libs/api.js +4 -0
- package/lib/libs/currency.d.ts +3 -0
- package/lib/libs/currency.js +31 -0
- package/lib/libs/phone-validator.js +1 -0
- package/lib/libs/validator.d.ts +1 -0
- package/lib/libs/validator.js +20 -0
- package/lib/payment/form/address.js +15 -2
- package/lib/payment/form/index.js +12 -1
- package/lib/payment/form/phone.js +13 -1
- package/lib/payment/form/stripe/form.js +21 -5
- package/lib/payment/index.js +34 -10
- package/lib/payment/product-donation.js +106 -15
- package/lib/types/shims.d.ts +2 -0
- package/package.json +8 -8
- package/src/checkout/donate.tsx +11 -1
- package/src/components/country-select.tsx +265 -20
- package/src/components/over-due-invoice-payment.tsx +6 -2
- package/src/contexts/payment.tsx +11 -1
- package/src/index.ts +1 -0
- package/src/libs/api.ts +4 -1
- package/src/libs/currency.ts +25 -0
- package/src/libs/phone-validator.ts +1 -0
- package/src/libs/validator.ts +70 -0
- package/src/payment/form/address.tsx +17 -4
- package/src/payment/form/index.tsx +11 -1
- package/src/payment/form/phone.tsx +15 -1
- package/src/payment/form/stripe/form.tsx +20 -9
- package/src/payment/index.tsx +45 -14
- package/src/payment/product-donation.tsx +129 -10
- package/src/types/shims.d.ts +2 -0
package/es/checkout/donate.js
CHANGED
|
@@ -65,6 +65,7 @@ export function DonateDetails({ supporters = [], currency, method }) {
|
|
|
65
65
|
return /* @__PURE__ */ jsx(
|
|
66
66
|
Stack,
|
|
67
67
|
{
|
|
68
|
+
className: "cko-donate-details",
|
|
68
69
|
sx: {
|
|
69
70
|
width: "100%",
|
|
70
71
|
minWidth: "256px",
|
|
@@ -246,8 +247,17 @@ function SupporterAvatar({
|
|
|
246
247
|
onClose: () => setOpen(false),
|
|
247
248
|
sx: {
|
|
248
249
|
".MuiDialogContent-root": {
|
|
249
|
-
width:
|
|
250
|
+
width: {
|
|
251
|
+
xs: "100%",
|
|
252
|
+
md: "450px"
|
|
253
|
+
},
|
|
250
254
|
padding: "8px"
|
|
255
|
+
},
|
|
256
|
+
".cko-donate-details": {
|
|
257
|
+
maxHeight: {
|
|
258
|
+
xs: "100%",
|
|
259
|
+
md: "300px"
|
|
260
|
+
}
|
|
251
261
|
}
|
|
252
262
|
},
|
|
253
263
|
title: `${customersNum} supporter${customersNum > 1 ? "s" : ""}`,
|
|
@@ -1,10 +1,110 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, forwardRef } from "react";
|
|
3
|
-
import { Box, MenuItem, Select, Typography } from "@mui/material";
|
|
2
|
+
import { useMemo, forwardRef, useState, useEffect, useRef } from "react";
|
|
3
|
+
import { Box, MenuItem, Select, Typography, TextField } from "@mui/material";
|
|
4
4
|
import { useFormContext } from "react-hook-form";
|
|
5
5
|
import { FlagEmoji, defaultCountries, parseCountry } from "react-international-phone";
|
|
6
|
+
import { useMobile } from "../hooks/mobile.js";
|
|
6
7
|
const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
|
|
7
8
|
const { setValue } = useFormContext();
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
const [searchText, setSearchText] = useState("");
|
|
11
|
+
const inputRef = useRef(null);
|
|
12
|
+
const menuRef = useRef(null);
|
|
13
|
+
const listRef = useRef(null);
|
|
14
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
15
|
+
const itemHeightRef = useRef(40);
|
|
16
|
+
const { isMobile } = useMobile();
|
|
17
|
+
const measuredRef = useRef(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!open)
|
|
20
|
+
return () => {
|
|
21
|
+
};
|
|
22
|
+
const handleResize = () => {
|
|
23
|
+
measuredRef.current = false;
|
|
24
|
+
};
|
|
25
|
+
window.addEventListener("resize", handleResize);
|
|
26
|
+
return () => {
|
|
27
|
+
window.removeEventListener("resize", handleResize);
|
|
28
|
+
};
|
|
29
|
+
}, [open]);
|
|
30
|
+
const scrollToTop = () => {
|
|
31
|
+
if (listRef.current) {
|
|
32
|
+
listRef.current.scrollTop = 0;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const measureItemHeight = () => {
|
|
36
|
+
if (!listRef.current || !open)
|
|
37
|
+
return;
|
|
38
|
+
const items = listRef.current.querySelectorAll(".MuiMenuItem-root");
|
|
39
|
+
if (items.length > 0) {
|
|
40
|
+
const firstItem = items[0];
|
|
41
|
+
if (firstItem.offsetHeight > 0) {
|
|
42
|
+
itemHeightRef.current = firstItem.offsetHeight;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const controlScrollPosition = (index) => {
|
|
47
|
+
if (!listRef.current)
|
|
48
|
+
return;
|
|
49
|
+
if (open && !measuredRef.current) {
|
|
50
|
+
measureItemHeight();
|
|
51
|
+
measuredRef.current = true;
|
|
52
|
+
}
|
|
53
|
+
const listHeight = listRef.current.clientHeight;
|
|
54
|
+
const targetPosition = index * itemHeightRef.current;
|
|
55
|
+
if (index < 2) {
|
|
56
|
+
listRef.current.scrollTop = 0;
|
|
57
|
+
} else if (index > filteredCountries.length - 3) {
|
|
58
|
+
listRef.current.scrollTop = listRef.current.scrollHeight - listHeight;
|
|
59
|
+
} else {
|
|
60
|
+
const scrollPosition = targetPosition - listHeight / 2 + itemHeightRef.current / 2;
|
|
61
|
+
listRef.current.scrollTop = Math.max(0, scrollPosition);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
let timeout = null;
|
|
66
|
+
if (open) {
|
|
67
|
+
timeout = setTimeout(() => {
|
|
68
|
+
scrollToTop();
|
|
69
|
+
if (!isMobile && inputRef.current) {
|
|
70
|
+
inputRef.current.focus();
|
|
71
|
+
}
|
|
72
|
+
}, 100);
|
|
73
|
+
} else {
|
|
74
|
+
setSearchText("");
|
|
75
|
+
setFocusedIndex(-1);
|
|
76
|
+
}
|
|
77
|
+
return () => {
|
|
78
|
+
if (timeout) {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}, [open, isMobile]);
|
|
83
|
+
const filteredCountries = useMemo(() => {
|
|
84
|
+
if (!searchText)
|
|
85
|
+
return defaultCountries;
|
|
86
|
+
return defaultCountries.filter((c) => {
|
|
87
|
+
const parsed = parseCountry(c);
|
|
88
|
+
return parsed.name.toLowerCase().includes(searchText.toLowerCase()) || parsed.iso2.toLowerCase().includes(searchText.toLowerCase()) || `+${parsed.dialCode}`.includes(searchText);
|
|
89
|
+
});
|
|
90
|
+
}, [searchText]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
scrollToTop();
|
|
93
|
+
setFocusedIndex(-1);
|
|
94
|
+
}, [searchText]);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
let timeout = null;
|
|
97
|
+
if (focusedIndex >= 0) {
|
|
98
|
+
timeout = setTimeout(() => {
|
|
99
|
+
controlScrollPosition(focusedIndex);
|
|
100
|
+
}, 10);
|
|
101
|
+
}
|
|
102
|
+
return () => {
|
|
103
|
+
if (timeout) {
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}, [focusedIndex, filteredCountries.length]);
|
|
8
108
|
const countryDetail = useMemo(() => {
|
|
9
109
|
const item = defaultCountries.find((v) => v[1] === value);
|
|
10
110
|
return value && item ? parseCountry(item) : { name: "" };
|
|
@@ -13,13 +113,62 @@ const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
|
|
|
13
113
|
onChange(e.target.value);
|
|
14
114
|
setValue(name, e.target.value);
|
|
15
115
|
};
|
|
16
|
-
|
|
116
|
+
const handleCountryClick = (code) => {
|
|
117
|
+
onChange(code);
|
|
118
|
+
setValue(name, code);
|
|
119
|
+
setOpen(false);
|
|
120
|
+
};
|
|
121
|
+
const handleSearchChange = (e) => {
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
setSearchText(e.target.value);
|
|
124
|
+
};
|
|
125
|
+
const handleKeyDown = (e) => {
|
|
126
|
+
e.stopPropagation();
|
|
127
|
+
if (e.key === "Escape") {
|
|
128
|
+
setOpen(false);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const handleNavigation = (direction) => {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
setFocusedIndex((prev) => {
|
|
134
|
+
if (direction === "next") {
|
|
135
|
+
if (prev === -1)
|
|
136
|
+
return 0;
|
|
137
|
+
return prev >= filteredCountries.length - 1 ? 0 : prev + 1;
|
|
138
|
+
}
|
|
139
|
+
if (prev === -1)
|
|
140
|
+
return filteredCountries.length - 1;
|
|
141
|
+
return prev <= 0 ? filteredCountries.length - 1 : prev - 1;
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
if (e.key === "Tab") {
|
|
145
|
+
handleNavigation(e.shiftKey ? "prev" : "next");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (e.key === "ArrowDown") {
|
|
149
|
+
handleNavigation("next");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (e.key === "ArrowUp") {
|
|
153
|
+
handleNavigation("prev");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (e.key === "Enter" && focusedIndex >= 0 && focusedIndex < filteredCountries.length) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
const country = parseCountry(filteredCountries[focusedIndex]);
|
|
159
|
+
handleCountryClick(country.iso2);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
return /* @__PURE__ */ jsxs(
|
|
17
163
|
Select,
|
|
18
164
|
{
|
|
19
165
|
ref,
|
|
166
|
+
open,
|
|
167
|
+
onOpen: () => setOpen(true),
|
|
168
|
+
onClose: () => setOpen(false),
|
|
20
169
|
MenuProps: {
|
|
21
170
|
style: {
|
|
22
|
-
|
|
171
|
+
maxHeight: "300px",
|
|
23
172
|
top: "10px"
|
|
24
173
|
},
|
|
25
174
|
anchorOrigin: {
|
|
@@ -29,6 +178,18 @@ const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
|
|
|
29
178
|
transformOrigin: {
|
|
30
179
|
vertical: "top",
|
|
31
180
|
horizontal: "left"
|
|
181
|
+
},
|
|
182
|
+
PaperProps: {
|
|
183
|
+
ref: menuRef,
|
|
184
|
+
sx: {
|
|
185
|
+
display: "flex",
|
|
186
|
+
"& .MuiList-root": {
|
|
187
|
+
pt: 0,
|
|
188
|
+
display: "flex",
|
|
189
|
+
flexDirection: "column",
|
|
190
|
+
overflowY: "hidden"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
32
193
|
}
|
|
33
194
|
},
|
|
34
195
|
sx: {
|
|
@@ -55,23 +216,84 @@ const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
|
|
|
55
216
|
},
|
|
56
217
|
value,
|
|
57
218
|
onChange: onCountryChange,
|
|
58
|
-
renderValue: (code) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
219
|
+
renderValue: (code) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", flexWrap: "nowrap", gap: 0.5, sx: { cursor: "pointer" }, children: [
|
|
220
|
+
/* @__PURE__ */ jsx(FlagEmoji, { iso2: code, style: { display: "flex" } }),
|
|
221
|
+
/* @__PURE__ */ jsx(Typography, { children: countryDetail?.name })
|
|
222
|
+
] }),
|
|
223
|
+
children: [
|
|
224
|
+
/* @__PURE__ */ jsx(
|
|
225
|
+
Box,
|
|
226
|
+
{
|
|
227
|
+
sx: {
|
|
228
|
+
position: "sticky",
|
|
229
|
+
top: 0,
|
|
230
|
+
zIndex: 1,
|
|
231
|
+
bgcolor: "background.paper",
|
|
232
|
+
p: 1
|
|
233
|
+
},
|
|
234
|
+
onClick: (e) => {
|
|
235
|
+
e.stopPropagation();
|
|
236
|
+
},
|
|
237
|
+
children: /* @__PURE__ */ jsx(
|
|
238
|
+
TextField,
|
|
239
|
+
{
|
|
240
|
+
inputRef,
|
|
241
|
+
autoFocus: !isMobile,
|
|
242
|
+
fullWidth: true,
|
|
243
|
+
placeholder: "Search country...",
|
|
244
|
+
value: searchText,
|
|
245
|
+
onChange: handleSearchChange,
|
|
246
|
+
onKeyDown: handleKeyDown,
|
|
247
|
+
onClick: (e) => e.stopPropagation(),
|
|
248
|
+
size: "small",
|
|
249
|
+
variant: "outlined"
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
/* @__PURE__ */ jsx(
|
|
255
|
+
Box,
|
|
256
|
+
{
|
|
257
|
+
ref: listRef,
|
|
258
|
+
sx: {
|
|
259
|
+
flex: 1,
|
|
260
|
+
overflowY: "auto",
|
|
261
|
+
overflowX: "hidden",
|
|
262
|
+
maxHeight: "calc(300px - 65px)",
|
|
263
|
+
scrollBehavior: "smooth"
|
|
264
|
+
},
|
|
265
|
+
children: filteredCountries.length > 0 ? filteredCountries.map((c, index) => {
|
|
266
|
+
const parsed = parseCountry(c);
|
|
267
|
+
const isFocused = index === focusedIndex;
|
|
268
|
+
return /* @__PURE__ */ jsxs(
|
|
269
|
+
MenuItem,
|
|
270
|
+
{
|
|
271
|
+
value: parsed.iso2,
|
|
272
|
+
selected: parsed.iso2 === value,
|
|
273
|
+
onClick: () => handleCountryClick(parsed.iso2),
|
|
274
|
+
sx: {
|
|
275
|
+
"&.Mui-selected": {
|
|
276
|
+
backgroundColor: "rgba(0, 0, 0, 0.04)"
|
|
277
|
+
},
|
|
278
|
+
"&:hover": {
|
|
279
|
+
backgroundColor: "var(--backgrounds-bg-highlight, #eff6ff)"
|
|
280
|
+
},
|
|
281
|
+
...isFocused ? {
|
|
282
|
+
backgroundColor: "var(--backgrounds-bg-highlight, #eff6ff)",
|
|
283
|
+
outline: "none"
|
|
284
|
+
} : {}
|
|
285
|
+
},
|
|
286
|
+
children: [
|
|
287
|
+
/* @__PURE__ */ jsx(FlagEmoji, { iso2: parsed.iso2, style: { marginRight: "8px" } }),
|
|
288
|
+
/* @__PURE__ */ jsx(Typography, { children: parsed.name })
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
parsed.iso2
|
|
292
|
+
);
|
|
293
|
+
}) : /* @__PURE__ */ jsx(MenuItem, { disabled: true, children: /* @__PURE__ */ jsx(Typography, { color: "text.secondary", children: "No countries found" }) })
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
]
|
|
75
297
|
}
|
|
76
298
|
);
|
|
77
299
|
});
|
|
@@ -27,13 +27,14 @@ type Props = {
|
|
|
27
27
|
subscriptionCount?: number;
|
|
28
28
|
detailUrl: string;
|
|
29
29
|
}) => React.ReactNode;
|
|
30
|
+
authToken?: string;
|
|
30
31
|
};
|
|
31
32
|
type SummaryItem = {
|
|
32
33
|
amount: string;
|
|
33
34
|
currency: PaymentCurrency;
|
|
34
35
|
method: PaymentMethod;
|
|
35
36
|
};
|
|
36
|
-
declare function OverdueInvoicePayment({ subscriptionId, customerId, mode, dialogProps, children, onPaid, detailLinkOptions, successToast, alertMessage, }: Props): import("react").JSX.Element | null;
|
|
37
|
+
declare function OverdueInvoicePayment({ subscriptionId, customerId, mode, dialogProps, children, onPaid, detailLinkOptions, successToast, alertMessage, authToken, }: Props): import("react").JSX.Element | null;
|
|
37
38
|
declare namespace OverdueInvoicePayment {
|
|
38
39
|
var defaultProps: {
|
|
39
40
|
mode: string;
|
|
@@ -49,6 +50,7 @@ declare namespace OverdueInvoicePayment {
|
|
|
49
50
|
customerId: undefined;
|
|
50
51
|
successToast: boolean;
|
|
51
52
|
alertMessage: string;
|
|
53
|
+
authToken: undefined;
|
|
52
54
|
};
|
|
53
55
|
}
|
|
54
56
|
export default OverdueInvoicePayment;
|
|
@@ -23,7 +23,7 @@ const fetchOverdueInvoices = async (params) => {
|
|
|
23
23
|
} else {
|
|
24
24
|
url = `/api/customers/${params.customerId}/overdue/invoices`;
|
|
25
25
|
}
|
|
26
|
-
const res = await api.get(url);
|
|
26
|
+
const res = await api.get(params.authToken ? joinURL(url, `?authToken=${params.authToken}`) : url);
|
|
27
27
|
return res.data;
|
|
28
28
|
};
|
|
29
29
|
function OverdueInvoicePayment({
|
|
@@ -36,7 +36,8 @@ function OverdueInvoicePayment({
|
|
|
36
36
|
},
|
|
37
37
|
detailLinkOptions = { enabled: true },
|
|
38
38
|
successToast = true,
|
|
39
|
-
alertMessage = ""
|
|
39
|
+
alertMessage = "",
|
|
40
|
+
authToken
|
|
40
41
|
}) {
|
|
41
42
|
const { t } = useLocaleContext();
|
|
42
43
|
const { connect } = usePaymentContext();
|
|
@@ -55,7 +56,7 @@ function OverdueInvoicePayment({
|
|
|
55
56
|
error,
|
|
56
57
|
loading,
|
|
57
58
|
runAsync: refresh
|
|
58
|
-
} = useRequest(() => fetchOverdueInvoices({ subscriptionId, customerId }), {
|
|
59
|
+
} = useRequest(() => fetchOverdueInvoices({ subscriptionId, customerId, authToken }), {
|
|
59
60
|
ready: !!subscriptionId || !!customerId
|
|
60
61
|
});
|
|
61
62
|
const detailUrl = useMemo(() => {
|
|
@@ -396,6 +397,7 @@ OverdueInvoicePayment.defaultProps = {
|
|
|
396
397
|
subscriptionId: void 0,
|
|
397
398
|
customerId: void 0,
|
|
398
399
|
successToast: true,
|
|
399
|
-
alertMessage: ""
|
|
400
|
+
alertMessage: "",
|
|
401
|
+
authToken: void 0
|
|
400
402
|
};
|
|
401
403
|
export default OverdueInvoicePayment;
|
package/es/contexts/payment.d.ts
CHANGED
|
@@ -25,10 +25,11 @@ export type PaymentContextProps = {
|
|
|
25
25
|
connect: import('@arcblock/did-connect/lib/types').SessionContext['connectApi'];
|
|
26
26
|
children: any;
|
|
27
27
|
baseUrl?: string;
|
|
28
|
+
authToken?: string;
|
|
28
29
|
};
|
|
29
30
|
declare const PaymentContext: import("react").Context<PaymentContextType>;
|
|
30
31
|
declare const Consumer: import("react").Consumer<PaymentContextType>;
|
|
31
|
-
declare function PaymentProvider({ session, connect, children, baseUrl }: PaymentContextProps): import("react").JSX.Element | null;
|
|
32
|
+
declare function PaymentProvider({ session, connect, children, baseUrl, authToken }: PaymentContextProps): import("react").JSX.Element | null;
|
|
32
33
|
declare namespace PaymentProvider {
|
|
33
34
|
var defaultProps: {};
|
|
34
35
|
}
|
package/es/contexts/payment.js
CHANGED
|
@@ -33,9 +33,16 @@ const getCurrency = (currencyId, methods) => {
|
|
|
33
33
|
const getMethod = (methodId, methods) => {
|
|
34
34
|
return methods.find((x) => x.id === methodId);
|
|
35
35
|
};
|
|
36
|
-
function PaymentProvider({ session, connect, children, baseUrl }) {
|
|
36
|
+
function PaymentProvider({ session, connect, children, baseUrl, authToken }) {
|
|
37
37
|
if (baseUrl) {
|
|
38
38
|
window.__PAYMENT_KIT_BASE_URL = baseUrl;
|
|
39
|
+
} else {
|
|
40
|
+
window.__PAYMENT_KIT_BASE_URL = "";
|
|
41
|
+
}
|
|
42
|
+
if (authToken) {
|
|
43
|
+
window.__PAYMENT_KIT_AUTH_TOKEN = authToken;
|
|
44
|
+
} else {
|
|
45
|
+
window.__PAYMENT_KIT_AUTH_TOKEN = "";
|
|
39
46
|
}
|
|
40
47
|
const [livemode, setLivemode] = useLocalStorageState("livemode", { defaultValue: true });
|
|
41
48
|
const {
|
package/es/index.d.ts
CHANGED
|
@@ -43,5 +43,6 @@ export * from './hooks/mobile';
|
|
|
43
43
|
export * from './hooks/table';
|
|
44
44
|
export * from './hooks/scroll';
|
|
45
45
|
export * from './hooks/keyboard';
|
|
46
|
+
export * from './libs/validator';
|
|
46
47
|
export { translations, createTranslator } from './locales';
|
|
47
48
|
export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, PaymentBeneficiaries, LoadingButton, DonateDetails, };
|
package/es/index.js
CHANGED
|
@@ -43,6 +43,7 @@ export * from "./hooks/mobile.js";
|
|
|
43
43
|
export * from "./hooks/table.js";
|
|
44
44
|
export * from "./hooks/scroll.js";
|
|
45
45
|
export * from "./hooks/keyboard.js";
|
|
46
|
+
export * from "./libs/validator.js";
|
|
46
47
|
export { translations, createTranslator } from "./locales/index.js";
|
|
47
48
|
export {
|
|
48
49
|
createLazyComponent,
|
package/es/libs/api.js
CHANGED
|
@@ -10,6 +10,10 @@ api.interceptors.request.use(
|
|
|
10
10
|
const locale = getLocale(window.blocklet?.languages);
|
|
11
11
|
const query = new URLSearchParams(config.url?.split("?").pop());
|
|
12
12
|
config.params = { ...config.params || {}, locale };
|
|
13
|
+
const authToken = window.__PAYMENT_KIT_AUTH_TOKEN;
|
|
14
|
+
if (authToken && typeof config.params.authToken === "undefined" && !query.has("authToken")) {
|
|
15
|
+
config.params.authToken = authToken;
|
|
16
|
+
}
|
|
13
17
|
if (typeof config.params.livemode === "undefined" && query.has("livemode") === false) {
|
|
14
18
|
const livemode = localStorage.getItem("livemode");
|
|
15
19
|
config.params.livemode = isNull(livemode) ? true : JSON.parse(livemode);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const getUserStorageKey: (base: string, did?: string) => string;
|
|
2
|
+
export declare const saveCurrencyPreference: (currencyId: string, did?: string) => void;
|
|
3
|
+
export declare const getCurrencyPreference: (did?: string, availableCurrencyIds?: string[]) => string | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const CURRENCY_PREFERENCE_KEY_BASE = "payment-currency-preference";
|
|
2
|
+
export const getUserStorageKey = (base, did) => {
|
|
3
|
+
return did ? `${base}:${did}` : base;
|
|
4
|
+
};
|
|
5
|
+
export const saveCurrencyPreference = (currencyId, did) => {
|
|
6
|
+
try {
|
|
7
|
+
localStorage.setItem(getUserStorageKey(CURRENCY_PREFERENCE_KEY_BASE, did), currencyId);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.warn("Failed to save currency preference", e);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const getCurrencyPreference = (did, availableCurrencyIds) => {
|
|
13
|
+
try {
|
|
14
|
+
const saved = localStorage.getItem(getUserStorageKey(CURRENCY_PREFERENCE_KEY_BASE, did));
|
|
15
|
+
if (saved && (!availableCurrencyIds || availableCurrencyIds.includes(saved))) {
|
|
16
|
+
return saved;
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.warn("Failed to access localStorage", e);
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function validatePostalCode(postalCode: string, country?: string): boolean;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import isPostalCode from "validator/lib/isPostalCode";
|
|
2
|
+
const POSTAL_CODE_SUPPORTED_COUNTRIES = [
|
|
3
|
+
"AD",
|
|
4
|
+
"AT",
|
|
5
|
+
"AU",
|
|
6
|
+
"BE",
|
|
7
|
+
"BG",
|
|
8
|
+
"BR",
|
|
9
|
+
"CA",
|
|
10
|
+
"CH",
|
|
11
|
+
"CN",
|
|
12
|
+
"CZ",
|
|
13
|
+
"DE",
|
|
14
|
+
"DK",
|
|
15
|
+
"DZ",
|
|
16
|
+
"EE",
|
|
17
|
+
"ES",
|
|
18
|
+
"FI",
|
|
19
|
+
"FR",
|
|
20
|
+
"GB",
|
|
21
|
+
"GR",
|
|
22
|
+
"HR",
|
|
23
|
+
"HU",
|
|
24
|
+
"ID",
|
|
25
|
+
"IE",
|
|
26
|
+
"IL",
|
|
27
|
+
"IN",
|
|
28
|
+
"IR",
|
|
29
|
+
"IS",
|
|
30
|
+
"IT",
|
|
31
|
+
"JP",
|
|
32
|
+
"KE",
|
|
33
|
+
"KR",
|
|
34
|
+
"LI",
|
|
35
|
+
"LT",
|
|
36
|
+
"LU",
|
|
37
|
+
"LV",
|
|
38
|
+
"MX",
|
|
39
|
+
"MT",
|
|
40
|
+
"NL",
|
|
41
|
+
"NO",
|
|
42
|
+
"NZ",
|
|
43
|
+
"PL",
|
|
44
|
+
"PR",
|
|
45
|
+
"PT",
|
|
46
|
+
"RO",
|
|
47
|
+
"RU",
|
|
48
|
+
"SA",
|
|
49
|
+
"SE",
|
|
50
|
+
"SI",
|
|
51
|
+
"SK",
|
|
52
|
+
"TN",
|
|
53
|
+
"TW",
|
|
54
|
+
"UA",
|
|
55
|
+
"US",
|
|
56
|
+
"ZA",
|
|
57
|
+
"ZM"
|
|
58
|
+
];
|
|
59
|
+
export function validatePostalCode(postalCode, country) {
|
|
60
|
+
if (!postalCode)
|
|
61
|
+
return true;
|
|
62
|
+
const countryUpper = country?.toUpperCase();
|
|
63
|
+
const isSupported = country && POSTAL_CODE_SUPPORTED_COUNTRIES.includes(countryUpper);
|
|
64
|
+
try {
|
|
65
|
+
return isPostalCode(postalCode, isSupported ? countryUpper : "any");
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(error);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
3
3
|
import { Fade, FormLabel, InputAdornment, Stack } from "@mui/material";
|
|
4
|
-
import { Controller, useFormContext } from "react-hook-form";
|
|
4
|
+
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
|
5
5
|
import FormInput from "../../components/input.js";
|
|
6
6
|
import CountrySelect from "../../components/country-select.js";
|
|
7
|
+
import { validatePostalCode } from "../../libs/validator.js";
|
|
7
8
|
AddressForm.defaultProps = {
|
|
8
9
|
sx: {}
|
|
9
10
|
};
|
|
10
11
|
export default function AddressForm({ mode, stripe, sx = {} }) {
|
|
11
12
|
const { t } = useLocaleContext();
|
|
12
13
|
const { control } = useFormContext();
|
|
14
|
+
const country = useWatch({ control, name: "billing_address.country" });
|
|
13
15
|
if (mode === "required") {
|
|
14
16
|
return /* @__PURE__ */ jsx(Fade, { in: true, children: /* @__PURE__ */ jsx(Stack, { className: "cko-payment-address cko-payment-form", sx, children: /* @__PURE__ */ jsxs(Stack, { direction: "column", className: "cko-payment-form", spacing: 0, children: [
|
|
15
17
|
/* @__PURE__ */ jsx(FormLabel, { className: "base-label", children: t("payment.checkout.billing.line1") }),
|
|
@@ -50,7 +52,13 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
|
|
|
50
52
|
FormInput,
|
|
51
53
|
{
|
|
52
54
|
name: "billing_address.postal_code",
|
|
53
|
-
rules: {
|
|
55
|
+
rules: {
|
|
56
|
+
required: t("payment.checkout.required"),
|
|
57
|
+
validate: (x) => {
|
|
58
|
+
const isValid = validatePostalCode(x, country);
|
|
59
|
+
return isValid ? true : t("payment.checkout.invalid");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
54
62
|
errorPosition: "right",
|
|
55
63
|
variant: "outlined",
|
|
56
64
|
placeholder: t("payment.checkout.billing.postal_code"),
|
|
@@ -85,7 +93,13 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
|
|
|
85
93
|
FormInput,
|
|
86
94
|
{
|
|
87
95
|
name: "billing_address.postal_code",
|
|
88
|
-
rules: {
|
|
96
|
+
rules: {
|
|
97
|
+
required: t("payment.checkout.required"),
|
|
98
|
+
validate: (x) => {
|
|
99
|
+
const isValid = validatePostalCode(x, country);
|
|
100
|
+
return isValid ? true : t("payment.checkout.invalid");
|
|
101
|
+
}
|
|
102
|
+
},
|
|
89
103
|
errorPosition: "right",
|
|
90
104
|
variant: "outlined",
|
|
91
105
|
placeholder: t("payment.checkout.billing.postal_code"),
|
package/es/payment/form/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { useMobile } from "../../hooks/mobile.js";
|
|
|
31
31
|
import { formatPhone, validatePhoneNumber } from "../../libs/phone-validator.js";
|
|
32
32
|
import LoadingButton from "../../components/loading-button.js";
|
|
33
33
|
import OverdueInvoicePayment from "../../components/over-due-invoice-payment.js";
|
|
34
|
+
import { saveCurrencyPreference } from "../../libs/currency.js";
|
|
34
35
|
export const waitForCheckoutComplete = async (sessionId) => {
|
|
35
36
|
let result;
|
|
36
37
|
await pWaitFor(
|
|
@@ -135,6 +136,13 @@ export default function PaymentForm({
|
|
|
135
136
|
const index = currencies.findIndex((x) => x.id === queryCurrencyId);
|
|
136
137
|
return index >= 0 ? index : 0;
|
|
137
138
|
});
|
|
139
|
+
const handleCurrencyChange = (index) => {
|
|
140
|
+
setPaymentCurrencyIndex(index);
|
|
141
|
+
const selectedCurrencyId = currencies[index]?.id;
|
|
142
|
+
if (selectedCurrencyId) {
|
|
143
|
+
saveCurrencyPreference(selectedCurrencyId, session?.user?.did);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
138
146
|
const onCheckoutComplete = useMemoizedFn(async ({ response }) => {
|
|
139
147
|
if (response.id === checkoutSession.id && state.paid === false) {
|
|
140
148
|
await handleConnected();
|
|
@@ -248,6 +256,7 @@ export default function PaymentForm({
|
|
|
248
256
|
const showForm = !!session?.user;
|
|
249
257
|
const skipBindWallet = method.type === "stripe";
|
|
250
258
|
const handleConnected = async () => {
|
|
259
|
+
setState({ paying: true });
|
|
251
260
|
try {
|
|
252
261
|
const result = await waitForCheckoutComplete(checkoutSession.id);
|
|
253
262
|
if (state.paid === false) {
|
|
@@ -485,7 +494,7 @@ export default function PaymentForm({
|
|
|
485
494
|
{
|
|
486
495
|
value: paymentCurrencyIndex,
|
|
487
496
|
currencies,
|
|
488
|
-
onChange:
|
|
497
|
+
onChange: handleCurrencyChange
|
|
489
498
|
}
|
|
490
499
|
)
|
|
491
500
|
}
|