@blocklet/payment-react 1.18.23 → 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.
@@ -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
- return formatNumber(fromUnitToToken(str, decimals), precision, trim);
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: true,
100
+ thousandSeparated,
98
101
  ...(precision || precision === 0) && { mantissa: precision }
99
102
  };
100
103
  const result = num.format(options);
@@ -183,7 +183,7 @@ export default function PaymentForm({
183
183
  const values = getValues();
184
184
  let userInfo = session.user;
185
185
  try {
186
- const { data: customerInfo } = await api.get(`/api/customers/${userInfo.did}?skipSummary=1&fallback=1`);
186
+ const { data: customerInfo } = await api.get("/api/customers/me?skipSummary=1&fallback=1");
187
187
  userInfo = mergeUserInfo(customerInfo, userInfo);
188
188
  } catch (err) {
189
189
  userInfo = mergeUserInfo(customer || {}, userInfo);
@@ -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
- payment_method_data: {
66
- billing_details: {
67
- name: customer.name,
68
- phone: customer.phone,
69
- email: customer.email,
70
- address: customer.address
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: { billingDetails: "never" },
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,
@@ -52,12 +52,6 @@ export default function ProductDonation({
52
52
  const middleIndex = Math.floor(presets.length / 2);
53
53
  return presets[middleIndex];
54
54
  }
55
- if (settings?.amount?.preset) {
56
- return formatAmount(settings.amount.preset);
57
- }
58
- if (presets.length > 0) {
59
- return presets[0];
60
- }
61
55
  return "0";
62
56
  };
63
57
  const supportPreset = presets.length > 0;
@@ -106,26 +100,24 @@ export default function ProductDonation({
106
100
  containerRef
107
101
  });
108
102
  useEffect(() => {
109
- if (settings.amount.preset) {
110
- setState({ selected: settings.amount.preset, custom: false });
111
- onChange({ priceId: item.price_id, amount: settings.amount.preset });
112
- } else if (settings.amount.presets && settings.amount.presets.length > 0) {
113
- const isCustom = defaultPreset === "custom";
114
- setState({
115
- selected: isCustom ? "" : defaultPreset,
116
- custom: isCustom,
117
- input: isCustom ? getSavedCustomAmount() : ""
118
- });
119
- if (!isCustom) {
120
- onChange({ priceId: item.price_id, amount: defaultPreset });
121
- } else if (defaultCustomAmount) {
122
- onChange({ priceId: item.price_id, amount: defaultCustomAmount });
123
- setPayable(true);
124
- } else {
125
- setPayable(false);
126
- }
103
+ const currentPreset = getDefaultPreset();
104
+ const isCustom = currentPreset === "custom";
105
+ setState({
106
+ selected: isCustom ? "" : currentPreset,
107
+ custom: !supportPreset || currentPreset === "custom",
108
+ input: defaultCustomAmount,
109
+ error: ""
110
+ });
111
+ if (!isCustom) {
112
+ onChange({ priceId: item.price_id, amount: currentPreset });
113
+ setPayable(true);
114
+ } else if (defaultCustomAmount) {
115
+ onChange({ priceId: item.price_id, amount: defaultCustomAmount });
116
+ setPayable(true);
117
+ } else {
118
+ setPayable(false);
127
119
  }
128
- }, [settings.amount.preset, settings.amount.presets]);
120
+ }, [settings.amount.preset, settings.amount.presets, supportPreset]);
129
121
  useEffect(() => {
130
122
  if (containerRef.current) {
131
123
  containerRef.current.focus();
@@ -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]);
@@ -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
- return formatNumber((0, _util.fromUnitToToken)(str, decimals), precision, trim);
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: true,
171
+ thousandSeparated,
169
172
  ...((precision || precision === 0) && {
170
173
  mantissa: precision
171
174
  })
@@ -209,7 +209,7 @@ function PaymentForm({
209
209
  try {
210
210
  const {
211
211
  data: customerInfo
212
- } = await _api.default.get(`/api/customers/${userInfo.did}?skipSummary=1&fallback=1`);
212
+ } = await _api.default.get("/api/customers/me?skipSummary=1&fallback=1");
213
213
  userInfo = mergeUserInfo(customerInfo, userInfo);
214
214
  } catch (err) {
215
215
  userInfo = mergeUserInfo(customer || {}, userInfo);
@@ -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
- payment_method_data: {
92
- billing_details: {
93
- name: customer.name,
94
- phone: customer.phone,
95
- email: customer.email,
96
- address: customer.address
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)(PaymentElement, {
132
- options: {
133
- layout: "auto",
134
- fields: {
135
- billingDetails: "never"
136
- },
137
- readOnly: state.confirming,
138
- defaultValues: {
139
- billingDetails: {
140
- name: customer.name,
141
- phone: customer.phone,
142
- email: customer.email,
143
- address: customer.address
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
- onReady: () => setState({
148
- loaded: true
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",
@@ -64,12 +64,6 @@ function ProductDonation({
64
64
  const middleIndex = Math.floor(presets.length / 2);
65
65
  return presets[middleIndex];
66
66
  }
67
- if (settings?.amount?.preset) {
68
- return formatAmount(settings.amount.preset);
69
- }
70
- if (presets.length > 0) {
71
- return presets[0];
72
- }
73
67
  return "0";
74
68
  };
75
69
  const supportPreset = presets.length > 0;
@@ -136,38 +130,30 @@ function ProductDonation({
136
130
  containerRef
137
131
  });
138
132
  (0, _react.useEffect)(() => {
139
- if (settings.amount.preset) {
140
- setState({
141
- selected: settings.amount.preset,
142
- custom: false
143
- });
133
+ const currentPreset = getDefaultPreset();
134
+ const isCustom = currentPreset === "custom";
135
+ setState({
136
+ selected: isCustom ? "" : currentPreset,
137
+ custom: !supportPreset || currentPreset === "custom",
138
+ input: defaultCustomAmount,
139
+ error: ""
140
+ });
141
+ if (!isCustom) {
144
142
  onChange({
145
143
  priceId: item.price_id,
146
- amount: settings.amount.preset
144
+ amount: currentPreset
147
145
  });
148
- } else if (settings.amount.presets && settings.amount.presets.length > 0) {
149
- const isCustom = defaultPreset === "custom";
150
- setState({
151
- selected: isCustom ? "" : defaultPreset,
152
- custom: isCustom,
153
- input: isCustom ? getSavedCustomAmount() : ""
146
+ setPayable(true);
147
+ } else if (defaultCustomAmount) {
148
+ onChange({
149
+ priceId: item.price_id,
150
+ amount: defaultCustomAmount
154
151
  });
155
- if (!isCustom) {
156
- onChange({
157
- priceId: item.price_id,
158
- amount: defaultPreset
159
- });
160
- } else if (defaultCustomAmount) {
161
- onChange({
162
- priceId: item.price_id,
163
- amount: defaultCustomAmount
164
- });
165
- setPayable(true);
166
- } else {
167
- setPayable(false);
168
- }
152
+ setPayable(true);
153
+ } else {
154
+ setPayable(false);
169
155
  }
170
- }, [settings.amount.preset, settings.amount.presets]);
156
+ }, [settings.amount.preset, settings.amount.presets, supportPreset]);
171
157
  (0, _react.useEffect)(() => {
172
158
  if (containerRef.current) {
173
159
  containerRef.current.focus();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.18.23",
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.44",
58
- "@arcblock/ux": "^2.12.44",
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.44",
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.23",
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": "4ffd236baa345ecd8facab1e71a6ed2491332fd4"
127
+ "gitHead": "1665ec667d621a3e607650e486cb0967371b9771"
128
128
  }
@@ -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 forward
112
+ // Shift+Tab backward
111
113
  return currentIndex === 0 ? totalOptions - 1 : currentIndex - 1;
112
114
  }
113
- // Tab backward
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(str: string = '', decimals: number = 18, precision: number = 6, trim: boolean = true) {
134
- return formatNumber(fromUnitToToken(str, decimals), precision, trim);
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(n: number | string, precision: number = 6, trim: boolean = true) {
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: true,
157
+ thousandSeparated,
144
158
  ...((precision || precision === 0) && { mantissa: precision }),
145
159
  };
146
160
  const result = num.format(options);
@@ -272,7 +272,7 @@ export default function PaymentForm({
272
272
  const values = getValues();
273
273
  let userInfo = session.user;
274
274
  try {
275
- const { data: customerInfo } = await api.get(`/api/customers/${userInfo.did}?skipSummary=1&fallback=1`);
275
+ const { data: customerInfo } = await api.get('/api/customers/me?skipSummary=1&fallback=1');
276
276
  userInfo = mergeUserInfo(customerInfo, userInfo);
277
277
  } catch (err) {
278
278
  // @ts-ignore
@@ -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
- payment_method_data: {
87
- billing_details: {
88
- name: customer.name,
89
- phone: customer.phone,
90
- email: customer.email,
91
- address: customer.address,
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
- <PaymentElement
123
- options={{
124
- layout: 'auto',
125
- fields: { billingDetails: 'never' },
126
- readOnly: state.confirming,
127
- defaultValues: {
128
- billingDetails: {
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
- onReady={() => setState({ loaded: true })}
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 />
@@ -65,12 +65,6 @@ export default function ProductDonation({
65
65
  const middleIndex = Math.floor(presets.length / 2);
66
66
  return presets[middleIndex];
67
67
  }
68
- if (settings?.amount?.preset) {
69
- return formatAmount(settings.amount.preset);
70
- }
71
- if (presets.length > 0) {
72
- return presets[0];
73
- }
74
68
  return '0';
75
69
  };
76
70
 
@@ -128,27 +122,26 @@ export default function ProductDonation({
128
122
  });
129
123
 
130
124
  useEffect(() => {
131
- if (settings.amount.preset) {
132
- setState({ selected: settings.amount.preset, custom: false });
133
- onChange({ priceId: item.price_id, amount: settings.amount.preset });
134
- } else if (settings.amount.presets && settings.amount.presets.length > 0) {
135
- const isCustom = defaultPreset === 'custom';
136
- setState({
137
- selected: isCustom ? '' : defaultPreset,
138
- custom: isCustom,
139
- input: isCustom ? getSavedCustomAmount() : '',
140
- });
125
+ const currentPreset = getDefaultPreset();
126
+ const isCustom = currentPreset === 'custom';
141
127
 
142
- if (!isCustom) {
143
- onChange({ priceId: item.price_id, amount: defaultPreset });
144
- } else if (defaultCustomAmount) {
145
- onChange({ priceId: item.price_id, amount: defaultCustomAmount });
146
- setPayable(true);
147
- } else {
148
- setPayable(false);
149
- }
128
+ setState({
129
+ selected: isCustom ? '' : currentPreset,
130
+ custom: !supportPreset || currentPreset === 'custom',
131
+ input: defaultCustomAmount,
132
+ error: '',
133
+ });
134
+
135
+ if (!isCustom) {
136
+ onChange({ priceId: item.price_id, amount: currentPreset });
137
+ setPayable(true);
138
+ } else if (defaultCustomAmount) {
139
+ onChange({ priceId: item.price_id, amount: defaultCustomAmount });
140
+ setPayable(true);
141
+ } else {
142
+ setPayable(false);
150
143
  }
151
- }, [settings.amount.preset, settings.amount.presets]); // eslint-disable-line
144
+ }, [settings.amount.preset, settings.amount.presets, supportPreset]); // eslint-disable-line
152
145
 
153
146
  useEffect(() => {
154
147
  if (containerRef.current) {