@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.
Files changed (42) hide show
  1. package/es/components/country-select.d.ts +1 -0
  2. package/es/components/country-select.js +359 -276
  3. package/es/contexts/payment.js +21 -1
  4. package/es/libs/cached-request.d.ts +1 -1
  5. package/es/libs/util.d.ts +1 -0
  6. package/es/libs/util.js +13 -0
  7. package/es/libs/validator.d.ts +1 -0
  8. package/es/libs/validator.js +14 -0
  9. package/es/locales/en.js +2 -1
  10. package/es/locales/zh.js +2 -1
  11. package/es/payment/form/address.d.ts +5 -1
  12. package/es/payment/form/address.js +27 -14
  13. package/es/payment/form/index.js +43 -10
  14. package/es/payment/form/phone.js +2 -1
  15. package/es/payment/form/stripe/form.js +1 -0
  16. package/lib/components/country-select.d.ts +1 -0
  17. package/lib/components/country-select.js +188 -80
  18. package/lib/contexts/payment.js +21 -0
  19. package/lib/libs/cached-request.d.ts +1 -1
  20. package/lib/libs/util.d.ts +1 -0
  21. package/lib/libs/util.js +16 -1
  22. package/lib/libs/validator.d.ts +1 -0
  23. package/lib/libs/validator.js +14 -0
  24. package/lib/locales/en.js +2 -1
  25. package/lib/locales/zh.js +2 -1
  26. package/lib/payment/form/address.d.ts +5 -1
  27. package/lib/payment/form/address.js +23 -13
  28. package/lib/payment/form/index.js +39 -10
  29. package/lib/payment/form/phone.js +2 -1
  30. package/lib/payment/form/stripe/form.js +1 -0
  31. package/package.json +6 -6
  32. package/src/components/country-select.tsx +381 -290
  33. package/src/contexts/payment.tsx +29 -1
  34. package/src/libs/cached-request.ts +1 -1
  35. package/src/libs/util.ts +15 -0
  36. package/src/libs/validator.ts +14 -0
  37. package/src/locales/en.tsx +1 -0
  38. package/src/locales/zh.tsx +1 -0
  39. package/src/payment/form/address.tsx +26 -11
  40. package/src/payment/form/index.tsx +39 -7
  41. package/src/payment/form/phone.tsx +1 -0
  42. package/src/payment/form/stripe/form.tsx +1 -0
@@ -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
 
@@ -70,7 +70,7 @@ export class CachedRequest {
70
70
  }
71
71
  }
72
72
 
73
- private clearCache() {
73
+ clearCache() {
74
74
  const cache = this.getCache();
75
75
  if (this.options.strategy === 'memory') {
76
76
  cache.delete(this.cacheKey);
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
+ }
@@ -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
+ }
@@ -241,6 +241,7 @@ export default flat({
241
241
  payer: 'Account',
242
242
  amount: 'Amount',
243
243
  failed: 'Account changed, please pay manually.',
244
+ balanceLink: 'View Balance',
244
245
  },
245
246
  },
246
247
  customer: {
@@ -236,6 +236,7 @@ export default flat({
236
236
  payer: '账户地址',
237
237
  amount: '支付金额',
238
238
  failed: '账户发生变化,无法自动完成支付,请手动支付。',
239
+ balanceLink: '查看余额',
239
240
  },
240
241
  },
241
242
  customer: {
@@ -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={{ required: t('payment.checkout.required') }}
32
- errorPosition="right"
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={{ required: t('payment.checkout.required') }}
40
- errorPosition="right"
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={{ required: t('payment.checkout.required') }}
48
- errorPosition="right"
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="right"
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="right"
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
- <Typography>
618
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
615
619
  <DID did={state.fastCheckoutInfo.payer || ''} compact responsive={false} />
616
- </Typography>
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="right"
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="right"
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="right"
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
  )}
@@ -93,6 +93,7 @@ export default function PhoneInput({ ...props }) {
93
93
  borderColor: 'transparent',
94
94
  },
95
95
  }}
96
+ showDialCode
96
97
  />
97
98
  </InputAdornment>
98
99
  ),
@@ -23,6 +23,7 @@ export type StripeCheckoutFormProps = {
23
23
  };
24
24
 
25
25
  const PaymentElementContainer = styled('div')`
26
+ width: 100%;
26
27
  opacity: 0;
27
28
  transition: opacity 300ms ease;
28
29
  &.visible {