@blocklet/payment-react 1.18.20 → 1.18.22
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/components/over-due-invoice-payment.d.ts +3 -1
- package/es/components/over-due-invoice-payment.js +15 -7
- package/es/libs/phone-validator.d.ts +7 -0
- package/es/libs/phone-validator.js +93 -2
- package/es/locales/en.js +5 -4
- package/es/locales/zh.js +5 -3
- package/es/payment/donation-form.js +11 -8
- package/es/payment/form/index.js +101 -65
- package/es/payment/form/phone.js +31 -10
- package/es/payment/index.js +11 -8
- package/es/payment/product-donation.js +7 -4
- package/lib/components/over-due-invoice-payment.d.ts +3 -1
- package/lib/components/over-due-invoice-payment.js +15 -7
- package/lib/libs/phone-validator.d.ts +7 -0
- package/lib/libs/phone-validator.js +97 -6
- package/lib/locales/en.js +5 -4
- package/lib/locales/zh.js +5 -3
- package/lib/payment/donation-form.js +9 -8
- package/lib/payment/form/index.js +106 -62
- package/lib/payment/form/phone.js +25 -9
- package/lib/payment/index.js +9 -8
- package/lib/payment/product-donation.js +7 -4
- package/package.json +6 -6
- package/src/components/over-due-invoice-payment.tsx +14 -6
- package/src/libs/phone-validator.ts +89 -2
- package/src/locales/en.tsx +4 -4
- package/src/locales/zh.tsx +4 -3
- package/src/payment/donation-form.tsx +13 -8
- package/src/payment/form/index.tsx +133 -69
- package/src/payment/form/phone.tsx +34 -12
- package/src/payment/index.tsx +13 -8
- package/src/payment/product-donation.tsx +7 -5
|
@@ -35,6 +35,7 @@ type Props = {
|
|
|
35
35
|
dialogProps?: DialogProps;
|
|
36
36
|
detailLinkOptions?: DetailLinkOptions;
|
|
37
37
|
successToast?: boolean;
|
|
38
|
+
alertMessage?: string; // only for customer
|
|
38
39
|
children?: (
|
|
39
40
|
handlePay: (item: SummaryItem) => void,
|
|
40
41
|
data: {
|
|
@@ -88,6 +89,7 @@ function OverdueInvoicePayment({
|
|
|
88
89
|
onPaid = () => {},
|
|
89
90
|
detailLinkOptions = { enabled: true },
|
|
90
91
|
successToast = true,
|
|
92
|
+
alertMessage = '',
|
|
91
93
|
}: Props) {
|
|
92
94
|
const { t } = useLocaleContext();
|
|
93
95
|
const { connect } = usePaymentContext();
|
|
@@ -99,7 +101,6 @@ function OverdueInvoicePayment({
|
|
|
99
101
|
|
|
100
102
|
const sourceType = subscriptionId ? 'subscription' : 'customer';
|
|
101
103
|
const sourceId = subscriptionId || customerId;
|
|
102
|
-
|
|
103
104
|
const {
|
|
104
105
|
data = {
|
|
105
106
|
summary: {},
|
|
@@ -340,19 +341,25 @@ function OverdueInvoicePayment({
|
|
|
340
341
|
});
|
|
341
342
|
}
|
|
342
343
|
if (customerId) {
|
|
344
|
+
let title = '';
|
|
343
345
|
if (summaryList.length === 1) {
|
|
344
|
-
|
|
346
|
+
title = t('payment.customer.overdue.title', {
|
|
345
347
|
subscriptionCount: data.subscriptionCount || 0,
|
|
346
348
|
count: data.invoices?.length,
|
|
347
349
|
total: formatAmount(summaryList[0]?.amount, summaryList[0]?.currency?.decimal),
|
|
348
350
|
symbol: summaryList[0]?.currency?.symbol,
|
|
349
351
|
method: getMethodText(summaryList[0]?.method),
|
|
350
352
|
});
|
|
353
|
+
} else {
|
|
354
|
+
title = t('payment.customer.overdue.simpleTitle', {
|
|
355
|
+
subscriptionCount: data.subscriptionCount || 0,
|
|
356
|
+
count: data.invoices?.length,
|
|
357
|
+
});
|
|
351
358
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
})
|
|
359
|
+
if (alertMessage) {
|
|
360
|
+
return `${title}${alertMessage}`;
|
|
361
|
+
}
|
|
362
|
+
return `${title}${t('payment.customer.overdue.defaultAlert')}`;
|
|
356
363
|
}
|
|
357
364
|
|
|
358
365
|
return '';
|
|
@@ -512,6 +519,7 @@ OverdueInvoicePayment.defaultProps = {
|
|
|
512
519
|
subscriptionId: undefined,
|
|
513
520
|
customerId: undefined,
|
|
514
521
|
successToast: true,
|
|
522
|
+
alertMessage: '',
|
|
515
523
|
};
|
|
516
524
|
|
|
517
525
|
export default OverdueInvoicePayment;
|
|
@@ -2,7 +2,11 @@ let phoneUtil: any = null;
|
|
|
2
2
|
|
|
3
3
|
export const getPhoneUtil = async () => {
|
|
4
4
|
if (!phoneUtil) {
|
|
5
|
-
const
|
|
5
|
+
const result = await import(/* webpackChunkName: "phone-util" */ 'google-libphonenumber');
|
|
6
|
+
const PhoneNumberUtil = (result.default || result)?.PhoneNumberUtil;
|
|
7
|
+
if (!PhoneNumberUtil) {
|
|
8
|
+
throw new Error('PhoneNumberUtil not found');
|
|
9
|
+
}
|
|
6
10
|
phoneUtil = PhoneNumberUtil.getInstance();
|
|
7
11
|
}
|
|
8
12
|
return phoneUtil;
|
|
@@ -10,7 +14,14 @@ export const getPhoneUtil = async () => {
|
|
|
10
14
|
|
|
11
15
|
export const validatePhoneNumber = async (phoneNumber: string) => {
|
|
12
16
|
try {
|
|
13
|
-
|
|
17
|
+
let util: any = null;
|
|
18
|
+
try {
|
|
19
|
+
util = await getPhoneUtil();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const pattern = /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4,6}$/im;
|
|
22
|
+
return pattern.test(phoneNumber);
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
const parsed = util.parseAndKeepRawInput(phoneNumber);
|
|
15
26
|
return util.isValidNumber(parsed);
|
|
16
27
|
} catch (err) {
|
|
@@ -18,3 +29,79 @@ export const validatePhoneNumber = async (phoneNumber: string) => {
|
|
|
18
29
|
return false;
|
|
19
30
|
}
|
|
20
31
|
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format a phone number to international format
|
|
35
|
+
* @param phoneNumber The original phone number
|
|
36
|
+
* @param defaultCountry Default country code (ISO 3166-1 alpha-2)
|
|
37
|
+
* @returns Formatted phone number
|
|
38
|
+
*/
|
|
39
|
+
export const formatPhone = (phoneNumber: string | undefined, defaultCountry = 'US') => {
|
|
40
|
+
if (!phoneNumber || phoneNumber.trim() === '') {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Remove all non-digit characters (preserve plus sign)
|
|
45
|
+
const cleanedNumber = phoneNumber.replace(/[^\d+]/g, '');
|
|
46
|
+
|
|
47
|
+
// If already in international format (starting with +), return cleaned number
|
|
48
|
+
if (cleanedNumber.startsWith('+')) {
|
|
49
|
+
return cleanedNumber;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Country code mapping
|
|
53
|
+
const COUNTRY_CODES: Record<string, string> = {
|
|
54
|
+
US: '1', // United States
|
|
55
|
+
CA: '1', // Canada
|
|
56
|
+
CN: '86', // China
|
|
57
|
+
HK: '852', // Hong Kong
|
|
58
|
+
IN: '91', // India
|
|
59
|
+
UK: '44', // United Kingdom
|
|
60
|
+
GB: '44', // United Kingdom
|
|
61
|
+
JP: '81', // Japan
|
|
62
|
+
KR: '82', // South Korea
|
|
63
|
+
AU: '61', // Australia
|
|
64
|
+
DE: '49', // Germany
|
|
65
|
+
FR: '33', // France
|
|
66
|
+
IT: '39', // Italy
|
|
67
|
+
ES: '34', // Spain
|
|
68
|
+
BR: '55', // Brazil
|
|
69
|
+
RU: '7', // Russia
|
|
70
|
+
MX: '52', // Mexico
|
|
71
|
+
SG: '65', // Singapore
|
|
72
|
+
AE: '971', // UAE
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Country-specific patterns
|
|
76
|
+
const COUNTRY_PATTERNS: [RegExp, string][] = [
|
|
77
|
+
[/^1[3-9]\d{9}$/, '86'], // China mobile: 11 digits, starts with 1
|
|
78
|
+
[/^\d{10}$/, '1'], // US/Canada: 10 digits
|
|
79
|
+
[/^[6-9]\d{9}$/, '91'], // India: 10 digits, starts with 6-9
|
|
80
|
+
[/^0[1-9]\d{8,9}$/, '81'], // Japan: 10-11 digits, starts with 0
|
|
81
|
+
[/^07\d{9}$/, '44'], // UK mobile: 11 digits, starts with 07
|
|
82
|
+
[/^04\d{8}$/, '61'], // Australia mobile: 10 digits, starts with 04
|
|
83
|
+
[/^01[0-9]\d{7,8}$/, '82'], // Korea mobile: 10-11 digits, starts with 01
|
|
84
|
+
[/^[0-9]\d{8}$/, '86'], // China landline: 9 digits
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Remove leading zero (common in many countries)
|
|
88
|
+
let numberToFormat = cleanedNumber;
|
|
89
|
+
if (numberToFormat.startsWith('0')) {
|
|
90
|
+
numberToFormat = numberToFormat.substring(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Try to match with country-specific patterns
|
|
94
|
+
for (const [pattern, countryCode] of COUNTRY_PATTERNS) {
|
|
95
|
+
if (pattern.test(cleanedNumber)) {
|
|
96
|
+
// For numbers starting with 0 that need the zero removed
|
|
97
|
+
if (cleanedNumber.startsWith('0') && !['1', '86'].includes(countryCode)) {
|
|
98
|
+
return `+${countryCode}${cleanedNumber.substring(1)}`;
|
|
99
|
+
}
|
|
100
|
+
return `+${countryCode}${cleanedNumber}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// If no pattern matched, use default country code
|
|
105
|
+
const countryCode = COUNTRY_CODES[defaultCountry.toUpperCase()] || defaultCountry;
|
|
106
|
+
return `+${countryCode}${numberToFormat}`;
|
|
107
|
+
};
|
package/src/locales/en.tsx
CHANGED
|
@@ -249,8 +249,7 @@ export default flat({
|
|
|
249
249
|
warning: 'Past due invoices need to be paid immediately, otherwise you can not make new purchases anymore.',
|
|
250
250
|
alert: {
|
|
251
251
|
title: 'You have unpaid invoices',
|
|
252
|
-
|
|
253
|
-
'Seems you have unpaid invoices from previous subscriptions, new purchases are not allowed unless you have paid all past due invoices.',
|
|
252
|
+
customMessage: 'Please pay immediately, otherwise new purchases or subscriptions will be prohibited.',
|
|
254
253
|
confirm: 'Pay Now',
|
|
255
254
|
},
|
|
256
255
|
view: 'View Due Invoices',
|
|
@@ -331,9 +330,10 @@ export default flat({
|
|
|
331
330
|
},
|
|
332
331
|
overdue: {
|
|
333
332
|
title:
|
|
334
|
-
'You have {count} due invoices for {subscriptionCount} subscriptions, totaling {total} {symbol}{method}.
|
|
335
|
-
simpleTitle: 'You have {count} due invoices.
|
|
333
|
+
'You have {count} due invoices for {subscriptionCount} subscriptions, totaling {total} {symbol}{method}. ',
|
|
334
|
+
simpleTitle: 'You have {count} due invoices. ',
|
|
336
335
|
empty: 'Great! You have no due invoices.',
|
|
336
|
+
defaultAlert: 'Please pay immediately to avoid service disruption.',
|
|
337
337
|
},
|
|
338
338
|
},
|
|
339
339
|
invoice: {
|
package/src/locales/zh.tsx
CHANGED
|
@@ -245,6 +245,7 @@ export default flat({
|
|
|
245
245
|
alert: {
|
|
246
246
|
title: '你有欠费账单',
|
|
247
247
|
description: '看起来你有欠费的账单,在你支付所有欠费账单之前,新的购买或者订阅将被禁止,请不要调皮。',
|
|
248
|
+
customMessage: '请立即支付,否则新的购买或者订阅将被禁止。',
|
|
248
249
|
confirm: '去支付',
|
|
249
250
|
},
|
|
250
251
|
view: '查看欠费明细',
|
|
@@ -321,10 +322,10 @@ export default flat({
|
|
|
321
322
|
owner: '订阅拥有者',
|
|
322
323
|
},
|
|
323
324
|
overdue: {
|
|
324
|
-
title:
|
|
325
|
-
|
|
326
|
-
simpleTitle: '您有 {count} 张欠费账单,请立即支付,以免影响您的使用。',
|
|
325
|
+
title: '您有 {count} 张欠费账单,涉及 {subscriptionCount} 个订阅,总金额 {total} {symbol}{method}。',
|
|
326
|
+
simpleTitle: '您有 {count} 张欠费账单,',
|
|
327
327
|
empty: '恭喜!您当前没有欠费账单。',
|
|
328
|
+
defaultAlert: '请立即支付,以免影响您的使用。',
|
|
328
329
|
},
|
|
329
330
|
},
|
|
330
331
|
invoice: {
|
|
@@ -36,6 +36,7 @@ import ProductDonation from './product-donation';
|
|
|
36
36
|
import ConfirmDialog from '../components/confirm';
|
|
37
37
|
import PaymentBeneficiaries, { TBeneficiary } from '../components/payment-beneficiaries';
|
|
38
38
|
import DonationSkeleton from './skeleton/donation';
|
|
39
|
+
import { formatPhone } from '../libs/phone-validator';
|
|
39
40
|
|
|
40
41
|
const getBenefits = async (id: string, url?: string) => {
|
|
41
42
|
const { data } = await api.get(`/api/payment-links/${id}/benefits?${url ? `url=${url}` : ''}`);
|
|
@@ -102,20 +103,24 @@ function PaymentInner({
|
|
|
102
103
|
defaultValues: {
|
|
103
104
|
customer_name: customer?.name || session?.user?.fullName || '',
|
|
104
105
|
customer_email: customer?.email || session?.user?.email || '',
|
|
105
|
-
customer_phone: customer?.phone || session?.user?.phone || '',
|
|
106
|
+
customer_phone: formatPhone(customer?.phone || session?.user?.phone || ''),
|
|
106
107
|
payment_method: defaultMethodId,
|
|
107
108
|
payment_currency: defaultCurrencyId,
|
|
108
109
|
billing_address: Object.assign(
|
|
109
110
|
{
|
|
110
|
-
country: '',
|
|
111
|
-
state: '',
|
|
112
|
-
city: '',
|
|
113
|
-
line1: '',
|
|
114
|
-
line2: '',
|
|
115
|
-
postal_code: '',
|
|
111
|
+
country: session?.user?.address?.country || '',
|
|
112
|
+
state: session?.user?.address?.province || '',
|
|
113
|
+
city: session?.user?.address?.city || '',
|
|
114
|
+
line1: session?.user?.address?.line1 || '',
|
|
115
|
+
line2: session?.user?.address?.line2 || '',
|
|
116
|
+
postal_code: session?.user?.address?.postalCode || '',
|
|
116
117
|
},
|
|
117
118
|
customer?.address || {},
|
|
118
|
-
{
|
|
119
|
+
{
|
|
120
|
+
country: isValidCountry(customer?.address?.country || session?.user?.address?.country || '')
|
|
121
|
+
? customer?.address?.country
|
|
122
|
+
: 'us',
|
|
123
|
+
}
|
|
119
124
|
),
|
|
120
125
|
},
|
|
121
126
|
});
|
|
@@ -21,7 +21,6 @@ import { dispatch } from 'use-bus';
|
|
|
21
21
|
import isEmail from 'validator/es/lib/isEmail';
|
|
22
22
|
|
|
23
23
|
import isEmpty from 'lodash/isEmpty';
|
|
24
|
-
import ConfirmDialog from '../../components/confirm';
|
|
25
24
|
import FormInput from '../../components/input';
|
|
26
25
|
import { usePaymentContext } from '../../contexts/payment';
|
|
27
26
|
import { useSubscription } from '../../hooks/subscription';
|
|
@@ -40,8 +39,9 @@ import CurrencySelector from './currency';
|
|
|
40
39
|
import PhoneInput from './phone';
|
|
41
40
|
import StripeCheckout from './stripe';
|
|
42
41
|
import { useMobile } from '../../hooks/mobile';
|
|
43
|
-
import { validatePhoneNumber } from '../../libs/phone-validator';
|
|
42
|
+
import { formatPhone, validatePhoneNumber } from '../../libs/phone-validator';
|
|
44
43
|
import LoadingButton from '../../components/loading-button';
|
|
44
|
+
import OverdueInvoicePayment from '../../components/over-due-invoice-payment';
|
|
45
45
|
|
|
46
46
|
export const waitForCheckoutComplete = async (sessionId: string) => {
|
|
47
47
|
let result: CheckoutContext;
|
|
@@ -83,6 +83,63 @@ type PageData = CheckoutContext &
|
|
|
83
83
|
isDonation?: boolean;
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
type UserInfo = {
|
|
87
|
+
name?: string;
|
|
88
|
+
fullName?: string;
|
|
89
|
+
email?: string;
|
|
90
|
+
phone?: string;
|
|
91
|
+
address?: {
|
|
92
|
+
country?: string;
|
|
93
|
+
state?: string;
|
|
94
|
+
province?: string;
|
|
95
|
+
line1?: string;
|
|
96
|
+
line2?: string;
|
|
97
|
+
city?: string;
|
|
98
|
+
postal_code?: string;
|
|
99
|
+
postalCode?: string;
|
|
100
|
+
};
|
|
101
|
+
metadata?: {
|
|
102
|
+
phone?: {
|
|
103
|
+
country?: string;
|
|
104
|
+
phoneNumber?: string;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const setUserFormValues = (
|
|
110
|
+
userInfo: UserInfo,
|
|
111
|
+
currentValues: any,
|
|
112
|
+
setValue: Function,
|
|
113
|
+
options: { preferExisting?: boolean; showPhone?: boolean } = {}
|
|
114
|
+
) => {
|
|
115
|
+
const { preferExisting = true } = options;
|
|
116
|
+
const basicFields = {
|
|
117
|
+
customer_name: userInfo.name || userInfo.fullName,
|
|
118
|
+
customer_email: userInfo.email,
|
|
119
|
+
customer_phone: formatPhone(userInfo.phone),
|
|
120
|
+
};
|
|
121
|
+
const addressFields: Record<string, any> = {
|
|
122
|
+
'billing_address.state': userInfo.address?.state || userInfo.address?.province,
|
|
123
|
+
'billing_address.line1': userInfo.address?.line1,
|
|
124
|
+
'billing_address.line2': userInfo.address?.line2,
|
|
125
|
+
'billing_address.city': userInfo.address?.city,
|
|
126
|
+
'billing_address.postal_code': userInfo.address?.postal_code || userInfo.address?.postalCode,
|
|
127
|
+
'billing_address.country': userInfo.address?.country,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (options.showPhone) {
|
|
131
|
+
addressFields['billing_address.country'] = userInfo.metadata?.phone?.country || userInfo.address?.country;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const allFields = { ...addressFields, ...basicFields };
|
|
135
|
+
|
|
136
|
+
Object.entries(allFields).forEach(([field, value]) => {
|
|
137
|
+
if (!preferExisting || !currentValues[field.split('.')[0]]) {
|
|
138
|
+
setValue(field, value);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
86
143
|
PaymentForm.defaultProps = {
|
|
87
144
|
onlyShowBtn: false,
|
|
88
145
|
isDonation: false,
|
|
@@ -185,22 +242,30 @@ export default function PaymentForm({
|
|
|
185
242
|
useEffect(() => {
|
|
186
243
|
if (session?.user) {
|
|
187
244
|
const values = getValues();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
245
|
+
setUserFormValues(session.user, values, setValue, {
|
|
246
|
+
preferExisting: false,
|
|
247
|
+
showPhone: checkoutSession.phone_number_collection?.enabled,
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
setUserFormValues(
|
|
251
|
+
{
|
|
252
|
+
name: '',
|
|
253
|
+
email: '',
|
|
254
|
+
phone: '',
|
|
255
|
+
address: {
|
|
256
|
+
state: '',
|
|
257
|
+
line1: '',
|
|
258
|
+
line2: '',
|
|
259
|
+
city: '',
|
|
260
|
+
postal_code: '',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{},
|
|
264
|
+
setValue,
|
|
265
|
+
{ preferExisting: false, showPhone: checkoutSession.phone_number_collection?.enabled }
|
|
266
|
+
);
|
|
202
267
|
}
|
|
203
|
-
}, [session?.user, getValues, setValue]);
|
|
268
|
+
}, [session?.user, getValues, setValue, checkoutSession.phone_number_collection?.enabled]);
|
|
204
269
|
|
|
205
270
|
useEffect(() => {
|
|
206
271
|
setValue('payment_method', (currencies[paymentCurrencyIndex] as any)?.method?.id);
|
|
@@ -248,7 +313,7 @@ export default function PaymentForm({
|
|
|
248
313
|
|
|
249
314
|
const method = paymentMethods.find((x) => x.id === paymentMethod) as TPaymentMethodExpanded;
|
|
250
315
|
const isDonationMode = checkoutSession?.submit_type === 'donate' && isDonation;
|
|
251
|
-
const showForm = session?.user;
|
|
316
|
+
const showForm = !!session?.user;
|
|
252
317
|
const skipBindWallet = method.type === 'stripe';
|
|
253
318
|
|
|
254
319
|
const handleConnected = async () => {
|
|
@@ -275,33 +340,9 @@ export default function PaymentForm({
|
|
|
275
340
|
const { data: profile } = await api.get('/api/customers/me?fallback=1');
|
|
276
341
|
if (profile) {
|
|
277
342
|
const values = getValues();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
if (!values.customer_email) {
|
|
282
|
-
setValue('customer_email', profile.email);
|
|
283
|
-
}
|
|
284
|
-
if (!values.customer_phone) {
|
|
285
|
-
setValue('customer_phone', profile.phone);
|
|
286
|
-
}
|
|
287
|
-
if (profile.address?.country) {
|
|
288
|
-
setValue('billing_address.country', profile.address.country);
|
|
289
|
-
}
|
|
290
|
-
if (profile.address?.state) {
|
|
291
|
-
setValue('billing_address.state', profile.address.state);
|
|
292
|
-
}
|
|
293
|
-
if (profile.address?.line1) {
|
|
294
|
-
setValue('billing_address.line1', profile.address.line1);
|
|
295
|
-
}
|
|
296
|
-
if (profile.address?.line2) {
|
|
297
|
-
setValue('billing_address.line2', profile.address.line2);
|
|
298
|
-
}
|
|
299
|
-
if (profile.address?.city) {
|
|
300
|
-
setValue('billing_address.city', profile.address.city);
|
|
301
|
-
}
|
|
302
|
-
if (profile.address?.postal_code) {
|
|
303
|
-
setValue('billing_address.postal_code', profile.address.postal_code);
|
|
304
|
-
}
|
|
343
|
+
setUserFormValues(profile, values, setValue, {
|
|
344
|
+
showPhone: checkoutSession.phone_number_collection?.enabled,
|
|
345
|
+
});
|
|
305
346
|
}
|
|
306
347
|
};
|
|
307
348
|
|
|
@@ -473,18 +514,31 @@ export default function PaymentForm({
|
|
|
473
514
|
</Button>
|
|
474
515
|
</Box>
|
|
475
516
|
{state.customerLimited && (
|
|
476
|
-
<
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
517
|
+
<OverdueInvoicePayment
|
|
518
|
+
customerId={customer?.id || session?.user?.did}
|
|
519
|
+
onPaid={() => {
|
|
520
|
+
setState({ customerLimited: false });
|
|
521
|
+
onAction();
|
|
522
|
+
}}
|
|
523
|
+
alertMessage={t('payment.customer.pastDue.alert.customMessage')}
|
|
524
|
+
detailLinkOptions={{
|
|
525
|
+
enabled: true,
|
|
526
|
+
onClick: () => {
|
|
527
|
+
setState({ customerLimited: false });
|
|
528
|
+
window.open(
|
|
529
|
+
joinURL(
|
|
530
|
+
getPrefix(),
|
|
531
|
+
`/customer/invoice/past-due?referer=${encodeURIComponent(window.location.href)}`
|
|
532
|
+
),
|
|
533
|
+
'_self'
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
}}
|
|
537
|
+
dialogProps={{
|
|
538
|
+
open: state.customerLimited,
|
|
539
|
+
onClose: () => setState({ customerLimited: false }),
|
|
540
|
+
title: t('payment.customer.pastDue.alert.title'),
|
|
541
|
+
}}
|
|
488
542
|
/>
|
|
489
543
|
)}
|
|
490
544
|
</>
|
|
@@ -619,18 +673,28 @@ export default function PaymentForm({
|
|
|
619
673
|
</Stack>
|
|
620
674
|
</Fade>
|
|
621
675
|
{state.customerLimited && (
|
|
622
|
-
<
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
676
|
+
<OverdueInvoicePayment
|
|
677
|
+
customerId={customer?.id || session?.user?.didssion?.user?.did}
|
|
678
|
+
onPaid={() => {
|
|
679
|
+
setState({ customerLimited: false });
|
|
680
|
+
onAction();
|
|
681
|
+
}}
|
|
682
|
+
alertMessage={t('payment.customer.pastDue.alert.customMessage')}
|
|
683
|
+
detailLinkOptions={{
|
|
684
|
+
enabled: true,
|
|
685
|
+
onClick: () => {
|
|
686
|
+
setState({ customerLimited: false });
|
|
687
|
+
window.open(
|
|
688
|
+
joinURL(getPrefix(), `/customer/invoice/past-due?referer=${encodeURIComponent(window.location.href)}`),
|
|
689
|
+
'_self'
|
|
690
|
+
);
|
|
691
|
+
},
|
|
692
|
+
}}
|
|
693
|
+
dialogProps={{
|
|
694
|
+
open: state.customerLimited,
|
|
695
|
+
onClose: () => setState({ customerLimited: false }),
|
|
696
|
+
title: t('payment.customer.pastDue.alert.title'),
|
|
697
|
+
}}
|
|
634
698
|
/>
|
|
635
699
|
)}
|
|
636
700
|
</>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react/prop-types */
|
|
2
2
|
import { InputAdornment } from '@mui/material';
|
|
3
3
|
import omit from 'lodash/omit';
|
|
4
|
-
import { useEffect } from 'react';
|
|
4
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
5
5
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
6
6
|
import { defaultCountries, usePhoneInput } from 'react-international-phone';
|
|
7
7
|
import type { CountryIso2 } from 'react-international-phone';
|
|
@@ -15,28 +15,50 @@ export default function PhoneInput({ ...props }) {
|
|
|
15
15
|
const { control, getValues, setValue } = useFormContext();
|
|
16
16
|
const values = getValues();
|
|
17
17
|
|
|
18
|
+
const isUpdatingRef = useRef(false);
|
|
19
|
+
|
|
20
|
+
const safeUpdate = useCallback((callback: () => void) => {
|
|
21
|
+
if (isUpdatingRef.current) return;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
isUpdatingRef.current = true;
|
|
25
|
+
callback();
|
|
26
|
+
} finally {
|
|
27
|
+
requestAnimationFrame(() => {
|
|
28
|
+
isUpdatingRef.current = false;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
18
33
|
const { phone, handlePhoneValueChange, inputRef, country, setCountry } = usePhoneInput({
|
|
19
34
|
defaultCountry: isValidCountry(values[countryFieldName]) ? values[countryFieldName] : 'us',
|
|
20
35
|
value: values[props.name] || '',
|
|
21
36
|
countries: defaultCountries,
|
|
22
|
-
onChange: (data
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
onChange: (data) => {
|
|
38
|
+
safeUpdate(() => {
|
|
39
|
+
setValue(props.name, data.phone);
|
|
40
|
+
setValue(countryFieldName, data.country);
|
|
41
|
+
});
|
|
25
42
|
},
|
|
26
43
|
});
|
|
27
44
|
|
|
28
45
|
const userCountry = useWatch({ control, name: countryFieldName });
|
|
29
46
|
useEffect(() => {
|
|
30
|
-
if (userCountry
|
|
47
|
+
if (!userCountry || userCountry === country) return;
|
|
48
|
+
|
|
49
|
+
safeUpdate(() => {
|
|
31
50
|
setCountry(userCountry);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
}, [userCountry]);
|
|
51
|
+
});
|
|
52
|
+
}, [userCountry, country, setCountry, safeUpdate]);
|
|
35
53
|
|
|
36
|
-
const onCountryChange = (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
const onCountryChange = useCallback(
|
|
55
|
+
(v: CountryIso2) => {
|
|
56
|
+
safeUpdate(() => {
|
|
57
|
+
setCountry(v);
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
[setCountry, safeUpdate]
|
|
61
|
+
);
|
|
40
62
|
|
|
41
63
|
return (
|
|
42
64
|
// @ts-ignore
|
package/src/payment/index.tsx
CHANGED
|
@@ -41,6 +41,7 @@ import PaymentSkeleton from './skeleton/payment';
|
|
|
41
41
|
import PaymentSuccess from './success';
|
|
42
42
|
import PaymentSummary from './summary';
|
|
43
43
|
import { useMobile } from '../hooks/mobile';
|
|
44
|
+
import { formatPhone } from '../libs/phone-validator';
|
|
44
45
|
|
|
45
46
|
// eslint-disable-next-line react/no-unused-prop-types
|
|
46
47
|
type Props = CheckoutContext & CheckoutCallbacks & { completed?: boolean; error?: any; showCheckoutSummary?: boolean };
|
|
@@ -78,20 +79,24 @@ function PaymentInner({
|
|
|
78
79
|
defaultValues: {
|
|
79
80
|
customer_name: customer?.name || session?.user?.fullName || '',
|
|
80
81
|
customer_email: customer?.email || session?.user?.email || '',
|
|
81
|
-
customer_phone: customer?.phone || session?.user?.phone || '',
|
|
82
|
+
customer_phone: formatPhone(customer?.phone || session?.user?.phone || ''),
|
|
82
83
|
payment_method: defaultMethodId,
|
|
83
84
|
payment_currency: defaultCurrencyId,
|
|
84
85
|
billing_address: Object.assign(
|
|
85
86
|
{
|
|
86
|
-
country: '',
|
|
87
|
-
state: '',
|
|
88
|
-
city: '',
|
|
89
|
-
line1: '',
|
|
90
|
-
line2: '',
|
|
91
|
-
postal_code: '',
|
|
87
|
+
country: session?.user?.address?.country || '',
|
|
88
|
+
state: session?.user?.address?.province || '',
|
|
89
|
+
city: session?.user?.address?.city || '',
|
|
90
|
+
line1: session?.user?.address?.line1 || '',
|
|
91
|
+
line2: session?.user?.address?.line2 || '',
|
|
92
|
+
postal_code: session?.user?.address?.postalCode || '',
|
|
92
93
|
},
|
|
93
94
|
customer?.address || {},
|
|
94
|
-
{
|
|
95
|
+
{
|
|
96
|
+
country: isValidCountry(customer?.address?.country || session?.user?.address?.country || '')
|
|
97
|
+
? customer?.address?.country
|
|
98
|
+
: 'us',
|
|
99
|
+
}
|
|
95
100
|
),
|
|
96
101
|
},
|
|
97
102
|
});
|
|
@@ -48,10 +48,6 @@ export default function ProductDonation({
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
const getDefaultPreset = () => {
|
|
51
|
-
if (settings?.amount?.preset) {
|
|
52
|
-
return formatAmount(settings.amount.preset);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
51
|
try {
|
|
56
52
|
const savedPreset = localStorage.getItem(getUserStorageKey(DONATION_PRESET_KEY_BASE));
|
|
57
53
|
if (savedPreset) {
|
|
@@ -67,7 +63,13 @@ export default function ProductDonation({
|
|
|
67
63
|
}
|
|
68
64
|
if (presets.length > 0) {
|
|
69
65
|
const middleIndex = Math.floor(presets.length / 2);
|
|
70
|
-
return presets[middleIndex]
|
|
66
|
+
return presets[middleIndex];
|
|
67
|
+
}
|
|
68
|
+
if (settings?.amount?.preset) {
|
|
69
|
+
return formatAmount(settings.amount.preset);
|
|
70
|
+
}
|
|
71
|
+
if (presets.length > 0) {
|
|
72
|
+
return presets[0];
|
|
71
73
|
}
|
|
72
74
|
return '0';
|
|
73
75
|
};
|