@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.
@@ -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);
@@ -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,
@@ -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
  })
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.18.24",
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.48",
58
- "@arcblock/ux": "^2.12.48",
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.48",
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.24",
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": "6c36b088743e696ce250cda11f8502b3886df0ef"
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);
@@ -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 />