@blocklet/payment-react 1.18.24 → 1.18.25
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/hooks/keyboard.js +3 -0
- package/es/libs/util.d.ts +2 -2
- package/es/libs/util.js +7 -4
- package/es/payment/form/stripe/form.js +59 -11
- package/lib/hooks/keyboard.js +3 -0
- package/lib/libs/util.d.ts +2 -2
- package/lib/libs/util.js +7 -4
- package/lib/payment/form/stripe/form.js +78 -25
- package/package.json +6 -6
- package/src/hooks/keyboard.ts +5 -3
- package/src/libs/util.ts +18 -4
- package/src/payment/form/stripe/form.tsx +85 -24
package/es/hooks/keyboard.js
CHANGED
|
@@ -39,6 +39,9 @@ export const useTabNavigation = (items, onSelect, options) => {
|
|
|
39
39
|
return i;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
if (includeCustom && navigableElements.length > 0) {
|
|
43
|
+
return navigableElements.length - 1;
|
|
44
|
+
}
|
|
42
45
|
}
|
|
43
46
|
return -1;
|
|
44
47
|
}, [items, includeCustom, isCustomSelected, currentValue, valueType, compareValue, findNavigableElements]);
|
package/es/libs/util.d.ts
CHANGED
|
@@ -10,8 +10,8 @@ export declare function formatDateTime(date: Date | string | number, locale?: st
|
|
|
10
10
|
export declare const formatLocale: (locale?: string) => string;
|
|
11
11
|
export declare const formatPrettyMsLocale: (locale: string) => "zh_CN" | "en_US";
|
|
12
12
|
export declare const formatError: (err: any) => any;
|
|
13
|
-
export declare function formatBNStr(str?: string, decimals?: number, precision?: number, trim?: boolean): string;
|
|
14
|
-
export declare function formatNumber(n: number | string, precision?: number, trim?: boolean): string;
|
|
13
|
+
export declare function formatBNStr(str?: string, decimals?: number, precision?: number, trim?: boolean, thousandSeparated?: boolean): string;
|
|
14
|
+
export declare function formatNumber(n: number | string, precision?: number, trim?: boolean, thousandSeparated?: boolean): string;
|
|
15
15
|
export declare const formatPrice: (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity?: number, bn?: boolean, locale?: string) => string;
|
|
16
16
|
export declare const formatPriceAmount: (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity?: number, bn?: boolean) => string;
|
|
17
17
|
export declare function getStatementDescriptor(items: any[]): any;
|
package/es/libs/util.js
CHANGED
|
@@ -85,16 +85,19 @@ export const formatError = (err) => {
|
|
|
85
85
|
}
|
|
86
86
|
return err.message;
|
|
87
87
|
};
|
|
88
|
-
export function formatBNStr(str = "", decimals = 18, precision = 6, trim = true) {
|
|
89
|
-
|
|
88
|
+
export function formatBNStr(str = "", decimals = 18, precision = 6, trim = true, thousandSeparated = true) {
|
|
89
|
+
if (!str) {
|
|
90
|
+
return "0";
|
|
91
|
+
}
|
|
92
|
+
return formatNumber(fromUnitToToken(str, decimals), precision, trim, thousandSeparated);
|
|
90
93
|
}
|
|
91
|
-
export function formatNumber(n, precision = 6, trim = true) {
|
|
94
|
+
export function formatNumber(n, precision = 6, trim = true, thousandSeparated = true) {
|
|
92
95
|
if (!n || n === "0") {
|
|
93
96
|
return "0";
|
|
94
97
|
}
|
|
95
98
|
const num = numbro(n);
|
|
96
99
|
const options = {
|
|
97
|
-
thousandSeparated
|
|
100
|
+
thousandSeparated,
|
|
98
101
|
...(precision || precision === 0) && { mantissa: precision }
|
|
99
102
|
};
|
|
100
103
|
const result = num.format(options);
|
|
@@ -9,6 +9,13 @@ import { useEffect, useCallback } from "react";
|
|
|
9
9
|
import { useMobile } from "../../../hooks/mobile.js";
|
|
10
10
|
import LoadingButton from "../../../components/loading-button.js";
|
|
11
11
|
const { Elements, PaymentElement, useElements, useStripe, loadStripe, LinkAuthenticationElement } = globalThis.__STRIPE_COMPONENTS__;
|
|
12
|
+
const PaymentElementContainer = styled("div")`
|
|
13
|
+
opacity: 0;
|
|
14
|
+
transition: opacity 300ms ease;
|
|
15
|
+
&.visible {
|
|
16
|
+
opacity: 1;
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
12
19
|
function StripeCheckoutForm({
|
|
13
20
|
clientSecret,
|
|
14
21
|
intentType,
|
|
@@ -23,8 +30,32 @@ function StripeCheckoutForm({
|
|
|
23
30
|
const [state, setState] = useSetState({
|
|
24
31
|
message: "",
|
|
25
32
|
confirming: false,
|
|
26
|
-
loaded: false
|
|
33
|
+
loaded: false,
|
|
34
|
+
showBillingForm: false,
|
|
35
|
+
isTransitioning: false
|
|
27
36
|
});
|
|
37
|
+
const handlePaymentMethodChange = (event) => {
|
|
38
|
+
const method = event.value?.type;
|
|
39
|
+
const needsBillingInfo = method === "google_pay" || method === "apple_pay";
|
|
40
|
+
const shouldShowForm = needsBillingInfo && !isCompleteBillingAddress(customer.address);
|
|
41
|
+
if (shouldShowForm && !state.showBillingForm) {
|
|
42
|
+
setState({ isTransitioning: true });
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
setState({
|
|
45
|
+
isTransitioning: false,
|
|
46
|
+
showBillingForm: true
|
|
47
|
+
});
|
|
48
|
+
}, 300);
|
|
49
|
+
} else {
|
|
50
|
+
setState({
|
|
51
|
+
showBillingForm: false,
|
|
52
|
+
isTransitioning: false
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const isCompleteBillingAddress = (address) => {
|
|
57
|
+
return address && address.line1 && address.city && address.state && address.postal_code && address.country;
|
|
58
|
+
};
|
|
28
59
|
useEffect(() => {
|
|
29
60
|
if (!stripe) {
|
|
30
61
|
return;
|
|
@@ -57,19 +88,33 @@ function StripeCheckoutForm({
|
|
|
57
88
|
try {
|
|
58
89
|
setState({ confirming: true });
|
|
59
90
|
const method = intentType === "payment_intent" ? "confirmPayment" : "confirmSetup";
|
|
91
|
+
const { error: submitError } = await elements.submit();
|
|
92
|
+
if (submitError) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
60
95
|
const { error } = await stripe[method]({
|
|
61
96
|
elements,
|
|
62
97
|
redirect: "if_required",
|
|
63
98
|
confirmParams: {
|
|
64
99
|
return_url: returnUrl || window.location.href,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
100
|
+
...!state.showBillingForm ? {
|
|
101
|
+
payment_method_data: {
|
|
102
|
+
billing_details: {
|
|
103
|
+
name: customer.name,
|
|
104
|
+
phone: customer.phone,
|
|
105
|
+
email: customer.email,
|
|
106
|
+
address: {
|
|
107
|
+
...customer.address || {},
|
|
108
|
+
country: customer.address?.country || "us",
|
|
109
|
+
line1: customer.address?.line1 || "",
|
|
110
|
+
line2: customer.address?.line2 || "",
|
|
111
|
+
city: customer.address?.city || "",
|
|
112
|
+
state: customer.address?.state || "",
|
|
113
|
+
postal_code: customer.address?.postal_code || "00000"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
71
116
|
}
|
|
72
|
-
}
|
|
117
|
+
} : {}
|
|
73
118
|
}
|
|
74
119
|
});
|
|
75
120
|
setState({ confirming: false });
|
|
@@ -98,12 +143,14 @@ function StripeCheckoutForm({
|
|
|
98
143
|
}
|
|
99
144
|
}
|
|
100
145
|
),
|
|
101
|
-
/* @__PURE__ */ jsx(
|
|
146
|
+
/* @__PURE__ */ jsx(PaymentElementContainer, { className: !state.isTransitioning ? "visible" : "", children: /* @__PURE__ */ jsx(
|
|
102
147
|
PaymentElement,
|
|
103
148
|
{
|
|
104
149
|
options: {
|
|
105
150
|
layout: "auto",
|
|
106
|
-
fields: {
|
|
151
|
+
fields: {
|
|
152
|
+
billingDetails: state.showBillingForm ? "auto" : "never"
|
|
153
|
+
},
|
|
107
154
|
readOnly: state.confirming,
|
|
108
155
|
defaultValues: {
|
|
109
156
|
billingDetails: {
|
|
@@ -114,9 +161,10 @@ function StripeCheckoutForm({
|
|
|
114
161
|
}
|
|
115
162
|
}
|
|
116
163
|
},
|
|
164
|
+
onChange: handlePaymentMethodChange,
|
|
117
165
|
onReady: () => setState({ loaded: true })
|
|
118
166
|
}
|
|
119
|
-
),
|
|
167
|
+
) }),
|
|
120
168
|
(!stripe || !elements || !state.loaded) && /* @__PURE__ */ jsx(Center, { relative: "parent", children: /* @__PURE__ */ jsx(CircularProgress, {}) }),
|
|
121
169
|
stripe && elements && state.loaded && /* @__PURE__ */ jsx(
|
|
122
170
|
LoadingButton,
|
package/lib/hooks/keyboard.js
CHANGED
|
@@ -44,6 +44,9 @@ const useTabNavigation = (items, onSelect, options) => {
|
|
|
44
44
|
return i;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
if (includeCustom && navigableElements.length > 0) {
|
|
48
|
+
return navigableElements.length - 1;
|
|
49
|
+
}
|
|
47
50
|
}
|
|
48
51
|
return -1;
|
|
49
52
|
}, [items, includeCustom, isCustomSelected, currentValue, valueType, compareValue, findNavigableElements]);
|
package/lib/libs/util.d.ts
CHANGED
|
@@ -10,8 +10,8 @@ export declare function formatDateTime(date: Date | string | number, locale?: st
|
|
|
10
10
|
export declare const formatLocale: (locale?: string) => string;
|
|
11
11
|
export declare const formatPrettyMsLocale: (locale: string) => "zh_CN" | "en_US";
|
|
12
12
|
export declare const formatError: (err: any) => any;
|
|
13
|
-
export declare function formatBNStr(str?: string, decimals?: number, precision?: number, trim?: boolean): string;
|
|
14
|
-
export declare function formatNumber(n: number | string, precision?: number, trim?: boolean): string;
|
|
13
|
+
export declare function formatBNStr(str?: string, decimals?: number, precision?: number, trim?: boolean, thousandSeparated?: boolean): string;
|
|
14
|
+
export declare function formatNumber(n: number | string, precision?: number, trim?: boolean, thousandSeparated?: boolean): string;
|
|
15
15
|
export declare const formatPrice: (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity?: number, bn?: boolean, locale?: string) => string;
|
|
16
16
|
export declare const formatPriceAmount: (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity?: number, bn?: boolean) => string;
|
|
17
17
|
export declare function getStatementDescriptor(items: any[]): any;
|
package/lib/libs/util.js
CHANGED
|
@@ -156,16 +156,19 @@ const formatError = err => {
|
|
|
156
156
|
return err.message;
|
|
157
157
|
};
|
|
158
158
|
exports.formatError = formatError;
|
|
159
|
-
function formatBNStr(str = "", decimals = 18, precision = 6, trim = true) {
|
|
160
|
-
|
|
159
|
+
function formatBNStr(str = "", decimals = 18, precision = 6, trim = true, thousandSeparated = true) {
|
|
160
|
+
if (!str) {
|
|
161
|
+
return "0";
|
|
162
|
+
}
|
|
163
|
+
return formatNumber((0, _util.fromUnitToToken)(str, decimals), precision, trim, thousandSeparated);
|
|
161
164
|
}
|
|
162
|
-
function formatNumber(n, precision = 6, trim = true) {
|
|
165
|
+
function formatNumber(n, precision = 6, trim = true, thousandSeparated = true) {
|
|
163
166
|
if (!n || n === "0") {
|
|
164
167
|
return "0";
|
|
165
168
|
}
|
|
166
169
|
const num = (0, _numbro.default)(n);
|
|
167
170
|
const options = {
|
|
168
|
-
thousandSeparated
|
|
171
|
+
thousandSeparated,
|
|
169
172
|
...((precision || precision === 0) && {
|
|
170
173
|
mantissa: precision
|
|
171
174
|
})
|
|
@@ -23,6 +23,13 @@ const {
|
|
|
23
23
|
loadStripe,
|
|
24
24
|
LinkAuthenticationElement
|
|
25
25
|
} = globalThis.__STRIPE_COMPONENTS__;
|
|
26
|
+
const PaymentElementContainer = (0, _system.styled)("div")`
|
|
27
|
+
opacity: 0;
|
|
28
|
+
transition: opacity 300ms ease;
|
|
29
|
+
&.visible {
|
|
30
|
+
opacity: 1;
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
26
33
|
function StripeCheckoutForm({
|
|
27
34
|
clientSecret,
|
|
28
35
|
intentType,
|
|
@@ -39,8 +46,34 @@ function StripeCheckoutForm({
|
|
|
39
46
|
const [state, setState] = (0, _ahooks.useSetState)({
|
|
40
47
|
message: "",
|
|
41
48
|
confirming: false,
|
|
42
|
-
loaded: false
|
|
49
|
+
loaded: false,
|
|
50
|
+
showBillingForm: false,
|
|
51
|
+
isTransitioning: false
|
|
43
52
|
});
|
|
53
|
+
const handlePaymentMethodChange = event => {
|
|
54
|
+
const method = event.value?.type;
|
|
55
|
+
const needsBillingInfo = method === "google_pay" || method === "apple_pay";
|
|
56
|
+
const shouldShowForm = needsBillingInfo && !isCompleteBillingAddress(customer.address);
|
|
57
|
+
if (shouldShowForm && !state.showBillingForm) {
|
|
58
|
+
setState({
|
|
59
|
+
isTransitioning: true
|
|
60
|
+
});
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
setState({
|
|
63
|
+
isTransitioning: false,
|
|
64
|
+
showBillingForm: true
|
|
65
|
+
});
|
|
66
|
+
}, 300);
|
|
67
|
+
} else {
|
|
68
|
+
setState({
|
|
69
|
+
showBillingForm: false,
|
|
70
|
+
isTransitioning: false
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const isCompleteBillingAddress = address => {
|
|
75
|
+
return address && address.line1 && address.city && address.state && address.postal_code && address.country;
|
|
76
|
+
};
|
|
44
77
|
(0, _react.useEffect)(() => {
|
|
45
78
|
if (!stripe) {
|
|
46
79
|
return;
|
|
@@ -81,6 +114,12 @@ function StripeCheckoutForm({
|
|
|
81
114
|
confirming: true
|
|
82
115
|
});
|
|
83
116
|
const method = intentType === "payment_intent" ? "confirmPayment" : "confirmSetup";
|
|
117
|
+
const {
|
|
118
|
+
error: submitError
|
|
119
|
+
} = await elements.submit();
|
|
120
|
+
if (submitError) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
84
123
|
const {
|
|
85
124
|
error
|
|
86
125
|
} = await stripe[method]({
|
|
@@ -88,14 +127,24 @@ function StripeCheckoutForm({
|
|
|
88
127
|
redirect: "if_required",
|
|
89
128
|
confirmParams: {
|
|
90
129
|
return_url: returnUrl || window.location.href,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
...(!state.showBillingForm ? {
|
|
131
|
+
payment_method_data: {
|
|
132
|
+
billing_details: {
|
|
133
|
+
name: customer.name,
|
|
134
|
+
phone: customer.phone,
|
|
135
|
+
email: customer.email,
|
|
136
|
+
address: {
|
|
137
|
+
...(customer.address || {}),
|
|
138
|
+
country: customer.address?.country || "us",
|
|
139
|
+
line1: customer.address?.line1 || "",
|
|
140
|
+
line2: customer.address?.line2 || "",
|
|
141
|
+
city: customer.address?.city || "",
|
|
142
|
+
state: customer.address?.state || "",
|
|
143
|
+
postal_code: customer.address?.postal_code || "00000"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
97
146
|
}
|
|
98
|
-
}
|
|
147
|
+
} : {})
|
|
99
148
|
}
|
|
100
149
|
});
|
|
101
150
|
setState({
|
|
@@ -128,24 +177,28 @@ function StripeCheckoutForm({
|
|
|
128
177
|
options: {
|
|
129
178
|
defaultEmail: customer.email
|
|
130
179
|
}
|
|
131
|
-
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
180
|
+
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(PaymentElementContainer, {
|
|
181
|
+
className: !state.isTransitioning ? "visible" : "",
|
|
182
|
+
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(PaymentElement, {
|
|
183
|
+
options: {
|
|
184
|
+
layout: "auto",
|
|
185
|
+
fields: {
|
|
186
|
+
billingDetails: state.showBillingForm ? "auto" : "never"
|
|
187
|
+
},
|
|
188
|
+
readOnly: state.confirming,
|
|
189
|
+
defaultValues: {
|
|
190
|
+
billingDetails: {
|
|
191
|
+
name: customer.name,
|
|
192
|
+
phone: customer.phone,
|
|
193
|
+
email: customer.email,
|
|
194
|
+
address: customer.address
|
|
195
|
+
}
|
|
144
196
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
197
|
+
},
|
|
198
|
+
onChange: handlePaymentMethodChange,
|
|
199
|
+
onReady: () => setState({
|
|
200
|
+
loaded: true
|
|
201
|
+
})
|
|
149
202
|
})
|
|
150
203
|
}), (!stripe || !elements || !state.loaded) && /* @__PURE__ */(0, _jsxRuntime.jsx)(_Center.default, {
|
|
151
204
|
relative: "parent",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.25",
|
|
4
4
|
"description": "Reusable react components for payment kit v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@arcblock/did-connect": "^2.12.
|
|
58
|
-
"@arcblock/ux": "^2.12.
|
|
57
|
+
"@arcblock/did-connect": "^2.12.52",
|
|
58
|
+
"@arcblock/ux": "^2.12.52",
|
|
59
59
|
"@arcblock/ws": "^1.19.15",
|
|
60
|
-
"@blocklet/ui-react": "^2.12.
|
|
60
|
+
"@blocklet/ui-react": "^2.12.52",
|
|
61
61
|
"@mui/icons-material": "^5.16.6",
|
|
62
62
|
"@mui/lab": "^5.0.0-alpha.173",
|
|
63
63
|
"@mui/material": "^5.16.6",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"@babel/core": "^7.25.2",
|
|
94
94
|
"@babel/preset-env": "^7.25.2",
|
|
95
95
|
"@babel/preset-react": "^7.24.7",
|
|
96
|
-
"@blocklet/payment-types": "1.18.
|
|
96
|
+
"@blocklet/payment-types": "1.18.25",
|
|
97
97
|
"@storybook/addon-essentials": "^7.6.20",
|
|
98
98
|
"@storybook/addon-interactions": "^7.6.20",
|
|
99
99
|
"@storybook/addon-links": "^7.6.20",
|
|
@@ -124,5 +124,5 @@
|
|
|
124
124
|
"vite-plugin-babel": "^1.2.0",
|
|
125
125
|
"vite-plugin-node-polyfills": "^0.21.0"
|
|
126
126
|
},
|
|
127
|
-
"gitHead": "
|
|
127
|
+
"gitHead": "1665ec667d621a3e607650e486cb0967371b9771"
|
|
128
128
|
}
|
package/src/hooks/keyboard.ts
CHANGED
|
@@ -86,12 +86,14 @@ export const useTabNavigation = <T>(
|
|
|
86
86
|
// if tabbed, find current focused element
|
|
87
87
|
const focusedElement = document.activeElement;
|
|
88
88
|
const navigableElements = findNavigableElements();
|
|
89
|
-
|
|
90
89
|
for (let i = 0; i < navigableElements.length; i++) {
|
|
91
90
|
if (navigableElements[i] === focusedElement) {
|
|
92
91
|
return i;
|
|
93
92
|
}
|
|
94
93
|
}
|
|
94
|
+
if (includeCustom && navigableElements.length > 0) {
|
|
95
|
+
return navigableElements.length - 1;
|
|
96
|
+
}
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
return -1;
|
|
@@ -107,10 +109,10 @@ export const useTabNavigation = <T>(
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
if (isShiftKey) {
|
|
110
|
-
// Shift+Tab
|
|
112
|
+
// Shift+Tab backward
|
|
111
113
|
return currentIndex === 0 ? totalOptions - 1 : currentIndex - 1;
|
|
112
114
|
}
|
|
113
|
-
// Tab
|
|
115
|
+
// Tab next
|
|
114
116
|
return currentIndex === totalOptions - 1 ? 0 : currentIndex + 1;
|
|
115
117
|
},
|
|
116
118
|
[items, includeCustom]
|
package/src/libs/util.ts
CHANGED
|
@@ -130,17 +130,31 @@ export const formatError = (err: any) => {
|
|
|
130
130
|
return err.message;
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
export function formatBNStr(
|
|
134
|
-
|
|
133
|
+
export function formatBNStr(
|
|
134
|
+
str: string = '',
|
|
135
|
+
decimals: number = 18,
|
|
136
|
+
precision: number = 6,
|
|
137
|
+
trim: boolean = true,
|
|
138
|
+
thousandSeparated: boolean = true
|
|
139
|
+
) {
|
|
140
|
+
if (!str) {
|
|
141
|
+
return '0';
|
|
142
|
+
}
|
|
143
|
+
return formatNumber(fromUnitToToken(str, decimals), precision, trim, thousandSeparated);
|
|
135
144
|
}
|
|
136
145
|
|
|
137
|
-
export function formatNumber(
|
|
146
|
+
export function formatNumber(
|
|
147
|
+
n: number | string,
|
|
148
|
+
precision: number = 6,
|
|
149
|
+
trim: boolean = true,
|
|
150
|
+
thousandSeparated: boolean = true
|
|
151
|
+
) {
|
|
138
152
|
if (!n || n === '0') {
|
|
139
153
|
return '0';
|
|
140
154
|
}
|
|
141
155
|
const num = numbro(n);
|
|
142
156
|
const options = {
|
|
143
|
-
thousandSeparated
|
|
157
|
+
thousandSeparated,
|
|
144
158
|
...((precision || precision === 0) && { mantissa: precision }),
|
|
145
159
|
};
|
|
146
160
|
const result = num.format(options);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
1
2
|
import Center from '@arcblock/ux/lib/Center';
|
|
2
3
|
import Dialog from '@arcblock/ux/lib/Dialog';
|
|
3
4
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
@@ -21,6 +22,14 @@ export type StripeCheckoutFormProps = {
|
|
|
21
22
|
returnUrl: string;
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
const PaymentElementContainer = styled('div')`
|
|
26
|
+
opacity: 0;
|
|
27
|
+
transition: opacity 300ms ease;
|
|
28
|
+
&.visible {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
24
33
|
// @doc https://stripe.com/docs/js/elements_object/create_payment_element
|
|
25
34
|
function StripeCheckoutForm({
|
|
26
35
|
clientSecret,
|
|
@@ -38,8 +47,36 @@ function StripeCheckoutForm({
|
|
|
38
47
|
message: '',
|
|
39
48
|
confirming: false,
|
|
40
49
|
loaded: false,
|
|
50
|
+
showBillingForm: false,
|
|
51
|
+
isTransitioning: false,
|
|
41
52
|
});
|
|
42
53
|
|
|
54
|
+
const handlePaymentMethodChange = (event: any) => {
|
|
55
|
+
const method = event.value?.type;
|
|
56
|
+
const needsBillingInfo = method === 'google_pay' || method === 'apple_pay';
|
|
57
|
+
const shouldShowForm = needsBillingInfo && !isCompleteBillingAddress(customer.address);
|
|
58
|
+
|
|
59
|
+
if (shouldShowForm && !state.showBillingForm) {
|
|
60
|
+
setState({ isTransitioning: true });
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
setState({
|
|
63
|
+
isTransitioning: false,
|
|
64
|
+
showBillingForm: true,
|
|
65
|
+
});
|
|
66
|
+
}, 300);
|
|
67
|
+
} else {
|
|
68
|
+
// if shouldShowForm is false, set showBillingForm to false immediately
|
|
69
|
+
setState({
|
|
70
|
+
showBillingForm: false,
|
|
71
|
+
isTransitioning: false,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const isCompleteBillingAddress = (address: any) => {
|
|
77
|
+
return address && address.line1 && address.city && address.state && address.postal_code && address.country;
|
|
78
|
+
};
|
|
79
|
+
|
|
43
80
|
useEffect(() => {
|
|
44
81
|
if (!stripe) {
|
|
45
82
|
return;
|
|
@@ -78,19 +115,37 @@ function StripeCheckoutForm({
|
|
|
78
115
|
try {
|
|
79
116
|
setState({ confirming: true });
|
|
80
117
|
const method = intentType === 'payment_intent' ? 'confirmPayment' : 'confirmSetup';
|
|
118
|
+
|
|
119
|
+
const { error: submitError } = await elements.submit();
|
|
120
|
+
if (submitError) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
81
124
|
const { error } = await stripe[method]({
|
|
82
125
|
elements,
|
|
83
126
|
redirect: 'if_required',
|
|
84
127
|
confirmParams: {
|
|
85
128
|
return_url: returnUrl || window.location.href,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
129
|
+
...(!state.showBillingForm
|
|
130
|
+
? {
|
|
131
|
+
payment_method_data: {
|
|
132
|
+
billing_details: {
|
|
133
|
+
name: customer.name,
|
|
134
|
+
phone: customer.phone,
|
|
135
|
+
email: customer.email,
|
|
136
|
+
address: {
|
|
137
|
+
...(customer.address || {}),
|
|
138
|
+
country: customer.address?.country || 'us',
|
|
139
|
+
line1: customer.address?.line1 || '',
|
|
140
|
+
line2: customer.address?.line2 || '',
|
|
141
|
+
city: customer.address?.city || '',
|
|
142
|
+
state: customer.address?.state || '',
|
|
143
|
+
postal_code: customer.address?.postal_code || '00000',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
: {}),
|
|
94
149
|
},
|
|
95
150
|
});
|
|
96
151
|
setState({ confirming: false });
|
|
@@ -98,7 +153,6 @@ function StripeCheckoutForm({
|
|
|
98
153
|
if (error.type === 'validation_error') {
|
|
99
154
|
return;
|
|
100
155
|
}
|
|
101
|
-
|
|
102
156
|
setState({ message: error.message as string });
|
|
103
157
|
return;
|
|
104
158
|
}
|
|
@@ -119,22 +173,29 @@ function StripeCheckoutForm({
|
|
|
119
173
|
defaultEmail: customer.email,
|
|
120
174
|
}}
|
|
121
175
|
/>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
name: customer.name,
|
|
130
|
-
phone: customer.phone,
|
|
131
|
-
email: customer.email,
|
|
132
|
-
address: customer.address,
|
|
176
|
+
|
|
177
|
+
<PaymentElementContainer className={!state.isTransitioning ? 'visible' : ''}>
|
|
178
|
+
<PaymentElement
|
|
179
|
+
options={{
|
|
180
|
+
layout: 'auto',
|
|
181
|
+
fields: {
|
|
182
|
+
billingDetails: state.showBillingForm ? 'auto' : 'never',
|
|
133
183
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
184
|
+
readOnly: state.confirming,
|
|
185
|
+
defaultValues: {
|
|
186
|
+
billingDetails: {
|
|
187
|
+
name: customer.name,
|
|
188
|
+
phone: customer.phone,
|
|
189
|
+
email: customer.email,
|
|
190
|
+
address: customer.address,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
}}
|
|
194
|
+
onChange={handlePaymentMethodChange}
|
|
195
|
+
onReady={() => setState({ loaded: true })}
|
|
196
|
+
/>
|
|
197
|
+
</PaymentElementContainer>
|
|
198
|
+
|
|
138
199
|
{(!stripe || !elements || !state.loaded) && (
|
|
139
200
|
<Center relative="parent">
|
|
140
201
|
<CircularProgress />
|