@blocklet/payment-react 1.18.34 → 1.18.36
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/country-select.d.ts +1 -0
- package/es/components/country-select.js +359 -276
- package/es/contexts/payment.js +21 -1
- package/es/libs/cached-request.d.ts +1 -1
- package/es/libs/util.d.ts +1 -0
- package/es/libs/util.js +13 -0
- package/es/libs/validator.d.ts +1 -0
- package/es/libs/validator.js +14 -0
- package/es/locales/en.js +2 -1
- package/es/locales/zh.js +2 -1
- package/es/payment/form/address.d.ts +5 -1
- package/es/payment/form/address.js +27 -14
- package/es/payment/form/index.js +43 -10
- package/es/payment/form/phone.js +2 -1
- package/es/payment/form/stripe/form.js +1 -0
- package/lib/components/country-select.d.ts +1 -0
- package/lib/components/country-select.js +188 -80
- package/lib/contexts/payment.js +21 -0
- package/lib/libs/cached-request.d.ts +1 -1
- package/lib/libs/util.d.ts +1 -0
- package/lib/libs/util.js +16 -1
- package/lib/libs/validator.d.ts +1 -0
- package/lib/libs/validator.js +14 -0
- package/lib/locales/en.js +2 -1
- package/lib/locales/zh.js +2 -1
- package/lib/payment/form/address.d.ts +5 -1
- package/lib/payment/form/address.js +23 -13
- package/lib/payment/form/index.js +39 -10
- package/lib/payment/form/phone.js +2 -1
- package/lib/payment/form/stripe/form.js +1 -0
- package/package.json +6 -6
- package/src/components/country-select.tsx +381 -290
- package/src/contexts/payment.tsx +29 -1
- package/src/libs/cached-request.ts +1 -1
- package/src/libs/util.ts +15 -0
- package/src/libs/validator.ts +14 -0
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/payment/form/address.tsx +26 -11
- package/src/payment/form/index.tsx +39 -7
- package/src/payment/form/phone.tsx +1 -0
- package/src/payment/form/stripe/form.tsx +1 -0
package/src/contexts/payment.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
1
2
|
import type { TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types';
|
|
2
3
|
import { Alert } from '@mui/material';
|
|
3
4
|
import { useLocalStorageState, useRequest } from 'ahooks';
|
|
4
5
|
import type { Axios } from 'axios';
|
|
5
|
-
import { createContext, useContext, useState } from 'react';
|
|
6
|
+
import { createContext, useContext, useEffect, useState } from 'react';
|
|
6
7
|
|
|
7
8
|
import api from '../libs/api';
|
|
8
9
|
import { getPrefix } from '../libs/util';
|
|
@@ -74,6 +75,19 @@ const getMethod = (methodId: string, methods: TPaymentMethodExpanded[]) => {
|
|
|
74
75
|
return methods.find((x) => x.id === methodId);
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
const syncToSpaceRequest = (userDid: string, spaceDid: string) => {
|
|
79
|
+
const cacheKey = `sync-space-${userDid}-${spaceDid}`;
|
|
80
|
+
const cachedRequest = new CachedRequest(cacheKey, () => api.post('/api/customers/sync-to-space'), {
|
|
81
|
+
ttl: 1000 * 60 * 60, // 1 hour
|
|
82
|
+
});
|
|
83
|
+
return cachedRequest.fetch(false).then((res) => {
|
|
84
|
+
if (!res.success) {
|
|
85
|
+
cachedRequest.clearCache();
|
|
86
|
+
}
|
|
87
|
+
return res;
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
77
91
|
function PaymentProvider({ session, connect, children, baseUrl, authToken }: PaymentContextProps) {
|
|
78
92
|
if (baseUrl) {
|
|
79
93
|
// This is hack but efficient to share
|
|
@@ -101,6 +115,20 @@ function PaymentProvider({ session, connect, children, baseUrl, authToken }: Pay
|
|
|
101
115
|
} = useRequest(getSettings, {
|
|
102
116
|
refreshDeps: [livemode],
|
|
103
117
|
});
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const didSpace = session?.user?.didSpace as
|
|
121
|
+
| {
|
|
122
|
+
endpoint: string;
|
|
123
|
+
did: string;
|
|
124
|
+
}
|
|
125
|
+
| undefined;
|
|
126
|
+
const userDid = session?.user?.did;
|
|
127
|
+
if (userDid && didSpace && didSpace.endpoint && didSpace.did) {
|
|
128
|
+
syncToSpaceRequest(userDid, didSpace.did);
|
|
129
|
+
}
|
|
130
|
+
}, [session?.user]);
|
|
131
|
+
|
|
104
132
|
const prefix = getPrefix();
|
|
105
133
|
const [payable, setPayable] = useState(true);
|
|
106
134
|
|
package/src/libs/util.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-nested-ternary */
|
|
2
2
|
/* eslint-disable @typescript-eslint/indent */
|
|
3
3
|
import type {
|
|
4
|
+
ChainType,
|
|
4
5
|
PaymentDetails,
|
|
5
6
|
PriceCurrency,
|
|
6
7
|
PriceRecurring,
|
|
@@ -1259,3 +1260,17 @@ export function parseMarkedText(text: string): Array<{
|
|
|
1259
1260
|
|
|
1260
1261
|
return result.filter((p) => p.content !== '');
|
|
1261
1262
|
}
|
|
1263
|
+
|
|
1264
|
+
export function getTokenBalanceLink(method: TPaymentMethod, address: string) {
|
|
1265
|
+
if (!method || !address) {
|
|
1266
|
+
return '';
|
|
1267
|
+
}
|
|
1268
|
+
const explorerHost = (method?.settings?.[method?.type as ChainType] as any)?.explorer_host || '';
|
|
1269
|
+
if (method.type === 'arcblock' && address) {
|
|
1270
|
+
return joinURL(explorerHost, 'accounts', address, 'tokens');
|
|
1271
|
+
}
|
|
1272
|
+
if (['ethereum', 'base'].includes(method.type) && address) {
|
|
1273
|
+
return joinURL(explorerHost, 'address', address);
|
|
1274
|
+
}
|
|
1275
|
+
return '';
|
|
1276
|
+
}
|
package/src/libs/validator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import isPostalCode, { PostalCodeLocale } from 'validator/lib/isPostalCode';
|
|
2
|
+
import { t } from '../locales';
|
|
2
3
|
|
|
3
4
|
const POSTAL_CODE_SUPPORTED_COUNTRIES: PostalCodeLocale[] = [
|
|
4
5
|
'AD',
|
|
@@ -68,3 +69,16 @@ export function validatePostalCode(postalCode: string, country?: string): boolea
|
|
|
68
69
|
return false;
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
|
|
73
|
+
export function getFieldValidation(fieldName: string, validations?: Record<string, any>, locale: string = 'en') {
|
|
74
|
+
if (!validations || !validations[fieldName]) return {};
|
|
75
|
+
const fieldValidation = validations[fieldName];
|
|
76
|
+
const rules: Record<string, any> = {};
|
|
77
|
+
if (fieldValidation.pattern) {
|
|
78
|
+
rules.pattern = {
|
|
79
|
+
value: new RegExp(fieldValidation.pattern),
|
|
80
|
+
message: fieldValidation.pattern_message?.[locale] || t('payment.checkout.invalid', locale),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return rules;
|
|
84
|
+
}
|
package/src/locales/en.tsx
CHANGED
package/src/locales/zh.tsx
CHANGED
|
@@ -4,20 +4,24 @@ import type { SxProps } from '@mui/material';
|
|
|
4
4
|
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
|
5
5
|
import FormInput from '../../components/input';
|
|
6
6
|
import CountrySelect from '../../components/country-select';
|
|
7
|
-
import { validatePostalCode } from '../../libs/validator';
|
|
7
|
+
import { getFieldValidation, validatePostalCode } from '../../libs/validator';
|
|
8
8
|
|
|
9
9
|
type Props = {
|
|
10
10
|
mode: string;
|
|
11
11
|
stripe: boolean;
|
|
12
12
|
sx?: SxProps;
|
|
13
|
+
fieldValidation?: Record<string, any>;
|
|
14
|
+
errorPosition?: 'right' | 'bottom';
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
AddressForm.defaultProps = {
|
|
16
18
|
sx: {},
|
|
19
|
+
fieldValidation: {},
|
|
20
|
+
errorPosition: 'right',
|
|
17
21
|
};
|
|
18
22
|
|
|
19
|
-
export default function AddressForm({ mode, stripe, sx = {} }: Props) {
|
|
20
|
-
const { t } = useLocaleContext();
|
|
23
|
+
export default function AddressForm({ mode, stripe, sx = {}, fieldValidation, errorPosition }: Props) {
|
|
24
|
+
const { t, locale } = useLocaleContext();
|
|
21
25
|
const { control } = useFormContext();
|
|
22
26
|
const country = useWatch({ control, name: 'billing_address.country' });
|
|
23
27
|
if (mode === 'required') {
|
|
@@ -28,24 +32,33 @@ export default function AddressForm({ mode, stripe, sx = {} }: Props) {
|
|
|
28
32
|
<FormLabel className="base-label">{t('payment.checkout.billing.line1')}</FormLabel>
|
|
29
33
|
<FormInput
|
|
30
34
|
name="billing_address.line1"
|
|
31
|
-
rules={{
|
|
32
|
-
|
|
35
|
+
rules={{
|
|
36
|
+
required: t('payment.checkout.required'),
|
|
37
|
+
...getFieldValidation('billing_address.line1', fieldValidation, locale),
|
|
38
|
+
}}
|
|
39
|
+
errorPosition={errorPosition}
|
|
33
40
|
variant="outlined"
|
|
34
41
|
placeholder={t('payment.checkout.billing.line1')}
|
|
35
42
|
/>
|
|
36
43
|
<FormLabel className="base-label">{t('payment.checkout.billing.city')}</FormLabel>
|
|
37
44
|
<FormInput
|
|
38
45
|
name="billing_address.city"
|
|
39
|
-
rules={{
|
|
40
|
-
|
|
46
|
+
rules={{
|
|
47
|
+
required: t('payment.checkout.required'),
|
|
48
|
+
...getFieldValidation('billing_address.city', fieldValidation, locale),
|
|
49
|
+
}}
|
|
50
|
+
errorPosition={errorPosition}
|
|
41
51
|
variant="outlined"
|
|
42
52
|
placeholder={t('payment.checkout.billing.city')}
|
|
43
53
|
/>
|
|
44
54
|
<FormLabel className="base-label">{t('payment.checkout.billing.state')}</FormLabel>
|
|
45
55
|
<FormInput
|
|
46
56
|
name="billing_address.state"
|
|
47
|
-
rules={{
|
|
48
|
-
|
|
57
|
+
rules={{
|
|
58
|
+
required: t('payment.checkout.required'),
|
|
59
|
+
...getFieldValidation('billing_address.state', fieldValidation, locale),
|
|
60
|
+
}}
|
|
61
|
+
errorPosition={errorPosition}
|
|
49
62
|
variant="outlined"
|
|
50
63
|
placeholder={t('payment.checkout.billing.state')}
|
|
51
64
|
/>
|
|
@@ -58,8 +71,9 @@ export default function AddressForm({ mode, stripe, sx = {} }: Props) {
|
|
|
58
71
|
const isValid = validatePostalCode(x, country);
|
|
59
72
|
return isValid ? true : t('payment.checkout.invalid');
|
|
60
73
|
},
|
|
74
|
+
...getFieldValidation('billing_address.postal_code', fieldValidation, locale),
|
|
61
75
|
}}
|
|
62
|
-
errorPosition=
|
|
76
|
+
errorPosition={errorPosition}
|
|
63
77
|
variant="outlined"
|
|
64
78
|
placeholder={t('payment.checkout.billing.postal_code')}
|
|
65
79
|
InputProps={{
|
|
@@ -104,8 +118,9 @@ export default function AddressForm({ mode, stripe, sx = {} }: Props) {
|
|
|
104
118
|
const isValid = validatePostalCode(x, country);
|
|
105
119
|
return isValid ? true : t('payment.checkout.invalid');
|
|
106
120
|
},
|
|
121
|
+
...getFieldValidation('billing_address.postal_code', fieldValidation, locale),
|
|
107
122
|
}}
|
|
108
|
-
errorPosition=
|
|
123
|
+
errorPosition={errorPosition}
|
|
109
124
|
variant="outlined"
|
|
110
125
|
placeholder={t('payment.checkout.billing.postal_code')}
|
|
111
126
|
wrapperStyle={{ height: '40px' }}
|
|
@@ -5,7 +5,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
5
5
|
// import { useTheme } from '@arcblock/ux/lib/Theme';
|
|
6
6
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
7
7
|
import type { TCheckoutSession, TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@blocklet/payment-types';
|
|
8
|
-
import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Typography } from '@mui/material';
|
|
8
|
+
import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Tooltip, Typography } from '@mui/material';
|
|
9
9
|
import { useMemoizedFn, useSetState } from 'ahooks';
|
|
10
10
|
import pWaitFor from 'p-wait-for';
|
|
11
11
|
import { useEffect, useMemo, useRef } from 'react';
|
|
@@ -17,7 +17,7 @@ import { fromUnitToToken } from '@ocap/util';
|
|
|
17
17
|
import DID from '@arcblock/ux/lib/DID';
|
|
18
18
|
|
|
19
19
|
import isEmpty from 'lodash/isEmpty';
|
|
20
|
-
import { HelpOutline } from '@mui/icons-material';
|
|
20
|
+
import { HelpOutline, OpenInNew } from '@mui/icons-material';
|
|
21
21
|
import FormInput from '../../components/input';
|
|
22
22
|
import { usePaymentContext } from '../../contexts/payment';
|
|
23
23
|
import { useSubscription } from '../../hooks/subscription';
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
formatQuantityInventory,
|
|
29
29
|
getPrefix,
|
|
30
30
|
getStatementDescriptor,
|
|
31
|
+
getTokenBalanceLink,
|
|
31
32
|
isCrossOrigin,
|
|
32
33
|
} from '../../libs/util';
|
|
33
34
|
import type { CheckoutCallbacks, CheckoutContext } from '../../types';
|
|
@@ -41,6 +42,7 @@ import LoadingButton from '../../components/loading-button';
|
|
|
41
42
|
import OverdueInvoicePayment from '../../components/over-due-invoice-payment';
|
|
42
43
|
import { saveCurrencyPreference } from '../../libs/currency';
|
|
43
44
|
import ConfirmDialog from '../../components/confirm';
|
|
45
|
+
import { getFieldValidation } from '../../libs/validator';
|
|
44
46
|
|
|
45
47
|
export const waitForCheckoutComplete = async (sessionId: string) => {
|
|
46
48
|
let result: CheckoutContext;
|
|
@@ -175,6 +177,7 @@ export default function PaymentForm({
|
|
|
175
177
|
const { isMobile } = useMobile();
|
|
176
178
|
const { session, connect, payable } = usePaymentContext();
|
|
177
179
|
const subscription = useSubscription('events');
|
|
180
|
+
const formErrorPosition = 'bottom';
|
|
178
181
|
const {
|
|
179
182
|
control,
|
|
180
183
|
getValues,
|
|
@@ -596,6 +599,7 @@ export default function PaymentForm({
|
|
|
596
599
|
};
|
|
597
600
|
}, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, payable]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
598
601
|
|
|
602
|
+
const balanceLink = getTokenBalanceLink(method, state.fastCheckoutInfo?.payer || '');
|
|
599
603
|
const FastCheckoutConfirmDialog = state.fastCheckoutInfo && (
|
|
600
604
|
<ConfirmDialog
|
|
601
605
|
onConfirm={handleFastCheckoutConfirm}
|
|
@@ -611,9 +615,24 @@ export default function PaymentForm({
|
|
|
611
615
|
<Typography color="text.primary" sx={{ whiteSpace: 'nowrap' }}>
|
|
612
616
|
{t('payment.checkout.fastPay.payer')}
|
|
613
617
|
</Typography>
|
|
614
|
-
<
|
|
618
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
615
619
|
<DID did={state.fastCheckoutInfo.payer || ''} compact responsive={false} />
|
|
616
|
-
|
|
620
|
+
{balanceLink && (
|
|
621
|
+
<Tooltip title={t('payment.checkout.fastPay.balanceLink')} placement="top">
|
|
622
|
+
<OpenInNew
|
|
623
|
+
sx={{
|
|
624
|
+
color: 'text.lighter',
|
|
625
|
+
fontSize: '0.85rem',
|
|
626
|
+
cursor: 'pointer',
|
|
627
|
+
'&:hover': { color: 'text.primary' },
|
|
628
|
+
}}
|
|
629
|
+
onClick={() => {
|
|
630
|
+
window.open(balanceLink, '_blank');
|
|
631
|
+
}}
|
|
632
|
+
/>
|
|
633
|
+
</Tooltip>
|
|
634
|
+
)}
|
|
635
|
+
</Box>
|
|
617
636
|
</Stack>
|
|
618
637
|
<Stack flexDirection="row" alignItems="center" justifyContent="space-between">
|
|
619
638
|
<Typography color="text.primary">{t('payment.checkout.fastPay.amount')}</Typography>
|
|
@@ -748,19 +767,25 @@ export default function PaymentForm({
|
|
|
748
767
|
<FormInput
|
|
749
768
|
name="customer_name"
|
|
750
769
|
variant="outlined"
|
|
751
|
-
errorPosition=
|
|
770
|
+
errorPosition={formErrorPosition}
|
|
752
771
|
rules={{
|
|
753
772
|
required: t('payment.checkout.required'),
|
|
773
|
+
...getFieldValidation('customer_name', checkoutSession.metadata?.page_info?.field_validation, locale),
|
|
754
774
|
}}
|
|
755
775
|
/>
|
|
756
776
|
<FormLabel className="base-label">{t('payment.checkout.customer.email')}</FormLabel>
|
|
757
777
|
<FormInput
|
|
758
778
|
name="customer_email"
|
|
759
779
|
variant="outlined"
|
|
760
|
-
errorPosition=
|
|
780
|
+
errorPosition={formErrorPosition}
|
|
761
781
|
rules={{
|
|
762
782
|
required: t('payment.checkout.required'),
|
|
763
783
|
validate: (x) => (isEmail(x) ? true : t('payment.checkout.invalid')),
|
|
784
|
+
...getFieldValidation(
|
|
785
|
+
'customer_email',
|
|
786
|
+
checkoutSession.metadata?.page_info?.field_validation,
|
|
787
|
+
locale
|
|
788
|
+
),
|
|
764
789
|
}}
|
|
765
790
|
/>
|
|
766
791
|
{checkoutSession.phone_number_collection?.enabled && (
|
|
@@ -769,7 +794,7 @@ export default function PaymentForm({
|
|
|
769
794
|
<PhoneInput
|
|
770
795
|
name="customer_phone"
|
|
771
796
|
variant="outlined"
|
|
772
|
-
errorPosition=
|
|
797
|
+
errorPosition={formErrorPosition}
|
|
773
798
|
placeholder="Phone number"
|
|
774
799
|
rules={{
|
|
775
800
|
required: t('payment.checkout.required'),
|
|
@@ -777,6 +802,11 @@ export default function PaymentForm({
|
|
|
777
802
|
const isValid = await validatePhoneNumber(x);
|
|
778
803
|
return isValid ? true : t('payment.checkout.invalid');
|
|
779
804
|
},
|
|
805
|
+
...getFieldValidation(
|
|
806
|
+
'customer_phone',
|
|
807
|
+
checkoutSession.metadata?.page_info?.field_validation,
|
|
808
|
+
locale
|
|
809
|
+
),
|
|
780
810
|
}}
|
|
781
811
|
/>
|
|
782
812
|
</>
|
|
@@ -785,6 +815,8 @@ export default function PaymentForm({
|
|
|
785
815
|
mode={checkoutSession.billing_address_collection as string}
|
|
786
816
|
stripe={method?.type === 'stripe'}
|
|
787
817
|
sx={{ marginTop: '0 !important' }}
|
|
818
|
+
fieldValidation={checkoutSession.metadata?.page_info?.field_validation}
|
|
819
|
+
errorPosition={formErrorPosition}
|
|
788
820
|
/>
|
|
789
821
|
</Stack>
|
|
790
822
|
)}
|