payment-kit 1.18.25 → 1.18.26

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 (31) hide show
  1. package/api/src/libs/notification/template/aggregated-subscription-renewed.ts +165 -0
  2. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +2 -5
  3. package/api/src/libs/notification/template/subscription-canceled.ts +2 -3
  4. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +7 -4
  5. package/api/src/libs/notification/template/subscription-renew-failed.ts +3 -5
  6. package/api/src/libs/notification/template/subscription-renewed.ts +2 -2
  7. package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +2 -3
  8. package/api/src/libs/notification/template/subscription-succeeded.ts +2 -2
  9. package/api/src/libs/notification/template/subscription-upgraded.ts +5 -5
  10. package/api/src/libs/notification/template/subscription-will-renew.ts +2 -2
  11. package/api/src/libs/queue/index.ts +6 -0
  12. package/api/src/libs/queue/store.ts +13 -1
  13. package/api/src/libs/util.ts +22 -1
  14. package/api/src/locales/en.ts +5 -0
  15. package/api/src/locales/zh.ts +5 -0
  16. package/api/src/queues/notification.ts +353 -11
  17. package/api/src/routes/customers.ts +61 -0
  18. package/api/src/routes/subscriptions.ts +1 -1
  19. package/api/src/store/migrations/20250328-notification-preference.ts +29 -0
  20. package/api/src/store/models/customer.ts +15 -1
  21. package/api/src/store/models/types.ts +17 -1
  22. package/blocklet.yml +1 -1
  23. package/package.json +19 -19
  24. package/src/components/customer/form.tsx +21 -2
  25. package/src/components/customer/notification-preference.tsx +428 -0
  26. package/src/components/layout/user.tsx +1 -1
  27. package/src/locales/en.tsx +30 -0
  28. package/src/locales/zh.tsx +30 -0
  29. package/src/pages/customer/index.tsx +26 -22
  30. package/src/pages/customer/recharge/account.tsx +7 -7
  31. package/src/pages/customer/subscription/embed.tsx +1 -0
@@ -1,14 +1,29 @@
1
1
  import 'react-international-phone/style.css';
2
2
 
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
- import { FormInput, PhoneInput, CountrySelect, validatePhoneNumber } from '@blocklet/payment-react';
4
+ import {
5
+ FormInput,
6
+ PhoneInput,
7
+ CountrySelect,
8
+ validatePhoneNumber,
9
+ getPhoneUtil,
10
+ validatePostalCode,
11
+ } from '@blocklet/payment-react';
5
12
  import { FormLabel, Stack } from '@mui/material';
6
- import { Controller, useFormContext } from 'react-hook-form';
13
+ import { Controller, useFormContext, useWatch } from 'react-hook-form';
7
14
  import isEmail from 'validator/es/lib/isEmail';
15
+ import { useMount } from 'ahooks';
8
16
 
9
17
  export default function CustomerForm() {
10
18
  const { t } = useLocaleContext();
11
19
  const { control } = useFormContext();
20
+ useMount(() => {
21
+ getPhoneUtil().catch((err) => {
22
+ console.error('Failed to preload phone validator:', err);
23
+ });
24
+ });
25
+
26
+ const country = useWatch({ control, name: 'address.country' });
12
27
 
13
28
  return (
14
29
  <Stack
@@ -148,6 +163,10 @@ export default function CustomerForm() {
148
163
  value: 20,
149
164
  message: t('common.maxLength', { len: 20 }),
150
165
  },
166
+ validate: (x: string) => {
167
+ const isValid = validatePostalCode(x, country);
168
+ return isValid ? true : t('payment.checkout.invalid');
169
+ },
151
170
  }}
152
171
  />
153
172
  </Stack>
@@ -0,0 +1,428 @@
1
+ import React, { useEffect, useState, useRef } from 'react';
2
+ import Dialog from '@arcblock/ux/lib/Dialog';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import { NotificationsOutlined, DateRangeOutlined } from '@mui/icons-material';
5
+ import {
6
+ Button,
7
+ CircularProgress,
8
+ Stack,
9
+ Typography,
10
+ FormControl,
11
+ Select,
12
+ MenuItem,
13
+ TextField,
14
+ Box,
15
+ Paper,
16
+ Alert,
17
+ Popper,
18
+ ClickAwayListener,
19
+ } from '@mui/material';
20
+ import { useRequest } from 'ahooks';
21
+ import { FormProvider, useForm, Controller, Control } from 'react-hook-form';
22
+ import { useMobile } from '@blocklet/payment-react';
23
+ import api from '../../libs/api';
24
+
25
+ export type NotificationFrequency = 'default' | 'daily' | 'weekly' | 'monthly';
26
+
27
+ export interface NotificationPreferences {
28
+ notification: {
29
+ frequency: NotificationFrequency;
30
+ schedule?: {
31
+ time: string;
32
+ date?: number;
33
+ };
34
+ };
35
+ }
36
+
37
+ interface NotificationPreferenceDialogProps {
38
+ open: boolean;
39
+ onClose: () => void;
40
+ }
41
+
42
+ const fetchPreferences = async () => {
43
+ const { data } = await api.get('/api/customers/me');
44
+ return data.preference || { notification: { frequency: 'default' } };
45
+ };
46
+
47
+ const updatePreferences = async (preferences: NotificationPreferences) => {
48
+ const { data } = await api.put('/api/customers/preference', preferences);
49
+ return data;
50
+ };
51
+
52
+ const validateTimeFormat = (time: string) => {
53
+ const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/;
54
+ return timeRegex.test(time);
55
+ };
56
+
57
+ function TimeSelector({
58
+ name,
59
+ control,
60
+ required,
61
+ timeFormatErrorMessage,
62
+ }: {
63
+ name: string;
64
+ control: Control<any>;
65
+ required: boolean;
66
+ timeFormatErrorMessage: string;
67
+ }) {
68
+ return (
69
+ <Controller
70
+ name={name}
71
+ control={control}
72
+ rules={{
73
+ required,
74
+ validate: (value) => validateTimeFormat(value) || timeFormatErrorMessage,
75
+ }}
76
+ render={({ field, fieldState }) => (
77
+ <TextField
78
+ type="time"
79
+ size="small"
80
+ sx={{
81
+ minWidth: 'fit-content',
82
+ }}
83
+ {...field}
84
+ error={!!fieldState.error}
85
+ helperText={fieldState.error?.message}
86
+ />
87
+ )}
88
+ />
89
+ );
90
+ }
91
+
92
+ DaySelector.defaultProps = {
93
+ onBlur: () => {},
94
+ };
95
+
96
+ function DaySelector({
97
+ value,
98
+ onChange,
99
+ onBlur,
100
+ }: {
101
+ value: number;
102
+ onChange: (day: number) => void;
103
+ onBlur?: () => void;
104
+ }) {
105
+ const { t } = useLocaleContext();
106
+ const [open, setOpen] = useState(false);
107
+ const anchorRef = useRef<HTMLButtonElement>(null);
108
+
109
+ const handleClickAway = () => {
110
+ setOpen(false);
111
+ onBlur?.();
112
+ };
113
+
114
+ return (
115
+ <Box>
116
+ <Button
117
+ ref={anchorRef}
118
+ variant="outlined"
119
+ onClick={() => setOpen(!open)}
120
+ sx={{
121
+ minWidth: 'fit-content',
122
+ display: 'flex',
123
+ alignItems: 'center',
124
+ justifyContent: 'space-between',
125
+ backgroundColor: 'var(--backgrounds-bg-field)',
126
+ '&:hover, &:focus': {
127
+ borderColor: 'primary.main',
128
+ },
129
+ }}
130
+ size="large"
131
+ startIcon={<DateRangeOutlined fontSize="small" color="action" sx={{ fontSize: '1rem !important' }} />}
132
+ endIcon={
133
+ <Typography
134
+ variant="caption"
135
+ color="text.secondary"
136
+ sx={{ fontSize: '0.75rem !important', lineHeight: 'normal' }}>
137
+ {t('notification.preferences.day')}
138
+ </Typography>
139
+ }>
140
+ {value || 1}
141
+ </Button>
142
+
143
+ <Popper
144
+ open={open}
145
+ anchorEl={anchorRef.current}
146
+ placement="bottom-start"
147
+ style={{ zIndex: 1300 }}
148
+ modifiers={[
149
+ {
150
+ name: 'offset',
151
+ options: {
152
+ offset: [0, 8],
153
+ },
154
+ },
155
+ ]}>
156
+ <ClickAwayListener onClickAway={handleClickAway}>
157
+ <Paper
158
+ variant="outlined"
159
+ sx={{
160
+ p: 1,
161
+ borderRadius: 1,
162
+ width: '280px',
163
+ boxShadow: 3,
164
+ }}>
165
+ <Box
166
+ sx={{
167
+ display: 'grid',
168
+ gridTemplateColumns: 'repeat(7, 1fr)',
169
+ gap: 0.5,
170
+ }}>
171
+ {Array.from({ length: 31 }, (_, i) => i + 1).map((day) => (
172
+ <Button
173
+ key={day}
174
+ size="small"
175
+ variant={value === day ? 'contained' : 'text'}
176
+ onClick={() => {
177
+ onChange(day);
178
+ setOpen(false);
179
+ }}
180
+ sx={{
181
+ minWidth: 30,
182
+ height: 30,
183
+ p: 0,
184
+ }}>
185
+ {day}
186
+ </Button>
187
+ ))}
188
+ </Box>
189
+ </Paper>
190
+ </ClickAwayListener>
191
+ </Popper>
192
+ </Box>
193
+ );
194
+ }
195
+
196
+ export function NotificationPreferenceDialog({ open, onClose }: NotificationPreferenceDialogProps) {
197
+ const { t } = useLocaleContext();
198
+ const [selectedDate, setSelectedDate] = useState<number>(1);
199
+
200
+ const { data, loading } = useRequest(fetchPreferences, {
201
+ manual: false,
202
+ refreshDeps: [open],
203
+ });
204
+
205
+ const methods = useForm<NotificationPreferences>({
206
+ mode: 'onChange',
207
+ defaultValues: {
208
+ notification: {
209
+ frequency: 'default',
210
+ schedule: {
211
+ time: '10:00',
212
+ date: 1,
213
+ },
214
+ },
215
+ },
216
+ });
217
+
218
+ const {
219
+ handleSubmit,
220
+ watch,
221
+ reset,
222
+ control,
223
+ setValue,
224
+ formState: { isValid },
225
+ } = methods;
226
+
227
+ const frequency = watch('notification.frequency');
228
+ const dateValue = watch('notification.schedule.date');
229
+ const needsSchedule = frequency !== 'default';
230
+
231
+ useEffect(() => {
232
+ if (data && open) {
233
+ reset(data);
234
+ if (data.notification?.schedule?.date) {
235
+ setSelectedDate(data.notification.schedule.date);
236
+ }
237
+ }
238
+ }, [data, reset, open]);
239
+
240
+ useEffect(() => {
241
+ if (dateValue) {
242
+ setSelectedDate(dateValue);
243
+ }
244
+ }, [dateValue]);
245
+
246
+ const { loading: updating, runAsync: runUpdate } = useRequest(updatePreferences, {
247
+ manual: true,
248
+ onSuccess: () => {
249
+ onClose();
250
+ },
251
+ });
252
+
253
+ const onSubmit = (formData: NotificationPreferences) => {
254
+ runUpdate(formData);
255
+ };
256
+
257
+ const handleMonthDaySelect = (day: number) => {
258
+ setValue('notification.schedule.date', day, { shouldValidate: true });
259
+ setSelectedDate(day);
260
+ };
261
+
262
+ const getDayName = (dayIndex: number) => {
263
+ const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
264
+ return t(`common.days.${days[dayIndex]}`);
265
+ };
266
+
267
+ const handleFrequencyChange = (event: React.ChangeEvent<HTMLInputElement> | any) => {
268
+ setValue('notification.frequency', event.target.value as NotificationFrequency);
269
+ };
270
+
271
+ return (
272
+ <Dialog
273
+ open={open}
274
+ onClose={onClose}
275
+ disableEscapeKeyDown
276
+ title={
277
+ <Stack direction="row" alignItems="center" spacing={1}>
278
+ <NotificationsOutlined fontSize="small" />
279
+ <Typography variant="h6" component="span">
280
+ {t('notification.preferences.title')}
281
+ </Typography>
282
+ </Stack>
283
+ }
284
+ maxWidth="sm"
285
+ fullWidth
286
+ className="base-dialog"
287
+ actions={
288
+ <Stack direction="row" spacing={2}>
289
+ <Button variant="outlined" color="inherit" onClick={onClose}>
290
+ {t('common.cancel')}
291
+ </Button>
292
+ <Button
293
+ variant="contained"
294
+ color="primary"
295
+ disabled={loading || updating || !isValid}
296
+ onClick={handleSubmit(onSubmit)}>
297
+ {updating && <CircularProgress size={20} sx={{ mr: 1 }} />}
298
+ {t('common.save')}
299
+ </Button>
300
+ </Stack>
301
+ }>
302
+ {loading ? (
303
+ <Stack alignItems="center" justifyContent="center" height={200}>
304
+ <CircularProgress />
305
+ </Stack>
306
+ ) : (
307
+ <FormProvider {...methods}>
308
+ <Box>
309
+ <Alert severity="info">{t('notification.preferences.subscriptionRenewalNote')}</Alert>
310
+ <Typography id="frequency-label" sx={{ color: 'text.primary', mt: 2, mb: 1 }}>
311
+ {t('notification.preferences.frequency.label')}
312
+ </Typography>
313
+ <Stack flexDirection="row">
314
+ <Box
315
+ sx={{
316
+ display: 'flex',
317
+ alignItems: 'center',
318
+ flexWrap: 'nowrap',
319
+ gap: { xs: 0.5, sm: 1, md: 2 },
320
+ width: '100%',
321
+ overflow: 'auto',
322
+ pb: 1,
323
+ }}>
324
+ <FormControl sx={{ width: { xs: '100px', sm: '120px' }, minWidth: 'fit-content' }}>
325
+ <Select
326
+ value={frequency}
327
+ onChange={handleFrequencyChange}
328
+ size="small"
329
+ displayEmpty
330
+ sx={{
331
+ fontSize: { xs: '0.85rem', sm: '0.9rem' },
332
+ }}>
333
+ <MenuItem value="default">{t('notification.preferences.frequency.default')}</MenuItem>
334
+ <MenuItem value="daily">{t('notification.preferences.frequency.daily')}</MenuItem>
335
+ <MenuItem value="weekly">{t('notification.preferences.frequency.weekly')}</MenuItem>
336
+ <MenuItem value="monthly">{t('notification.preferences.frequency.monthly')}</MenuItem>
337
+ </Select>
338
+ </FormControl>
339
+
340
+ {frequency !== 'default' && (
341
+ <>
342
+ {frequency === 'weekly' && (
343
+ <Controller
344
+ name="notification.schedule.date"
345
+ control={control}
346
+ render={({ field }) => (
347
+ <Select size="small" sx={{ minWidth: 120 }} value={field.value} onChange={field.onChange}>
348
+ {[0, 1, 2, 3, 4, 5, 6].map((day) => (
349
+ <MenuItem key={day} value={day}>
350
+ {getDayName(day)}
351
+ </MenuItem>
352
+ ))}
353
+ </Select>
354
+ )}
355
+ />
356
+ )}
357
+
358
+ {frequency === 'monthly' && <DaySelector value={dateValue || 1} onChange={handleMonthDaySelect} />}
359
+
360
+ <TimeSelector
361
+ name="notification.schedule.time"
362
+ control={control}
363
+ required={needsSchedule}
364
+ timeFormatErrorMessage={t('notification.preferences.timeFormatError')}
365
+ />
366
+ </>
367
+ )}
368
+ </Box>
369
+ </Stack>
370
+ {selectedDate > 28 && frequency === 'monthly' && (
371
+ <Typography
372
+ variant="caption"
373
+ color="text.secondary"
374
+ sx={{
375
+ fontStyle: 'italic',
376
+ display: 'flex',
377
+ alignItems: 'center',
378
+ gap: 0.5,
379
+ }}>
380
+ <Box component="span" sx={{ fontSize: '0.8rem' }}>
381
+ ⚠️
382
+ </Box>
383
+ {t('notification.preferences.monthlyHelp')}
384
+ </Typography>
385
+ )}
386
+ </Box>
387
+ </FormProvider>
388
+ )}
389
+ </Dialog>
390
+ );
391
+ }
392
+
393
+ export default function NotificationPreference() {
394
+ const { t } = useLocaleContext();
395
+ const { isMobile } = useMobile();
396
+ const [open, setOpen] = React.useState(false);
397
+
398
+ return (
399
+ <>
400
+ {isMobile ? (
401
+ <Button
402
+ size="small"
403
+ variant="outlined"
404
+ sx={{
405
+ minWidth: 0,
406
+ width: 32,
407
+ height: 32,
408
+ borderRadius: '50%',
409
+ }}
410
+ onClick={() => setOpen(true)}>
411
+ <NotificationsOutlined fontSize="small" />
412
+ </Button>
413
+ ) : (
414
+ <Button
415
+ startIcon={<NotificationsOutlined />}
416
+ size="small"
417
+ onClick={() => setOpen(true)}
418
+ variant="outlined"
419
+ sx={{
420
+ whiteSpace: 'nowrap',
421
+ }}>
422
+ {t('notification.preferences.button')}
423
+ </Button>
424
+ )}
425
+ <NotificationPreferenceDialog open={open} onClose={() => setOpen(false)} />
426
+ </>
427
+ );
428
+ }
@@ -19,7 +19,7 @@ export default function UserLayout(props: any) {
19
19
 
20
20
  useEffect(() => {
21
21
  events.once('logout', () => {
22
- window.location.href = `${window.location.origin}/.well-known/service/user`;
22
+ session.login(() => {}, { openMode: 'redirect', redirect: window.location.href });
23
23
  });
24
24
  }, []);
25
25
 
@@ -37,6 +37,36 @@ export default flat({
37
37
  copySuccess: 'Copy Success',
38
38
  copyFailed: 'Copy Failed',
39
39
  copyTip: 'Please copy manually',
40
+ save: 'Save',
41
+ cancel: 'Cancel',
42
+ know: 'I Know',
43
+ confirm: 'Confirm',
44
+ days: {
45
+ sunday: 'Sunday',
46
+ monday: 'Monday',
47
+ tuesday: 'Tuesday',
48
+ wednesday: 'Wednesday',
49
+ thursday: 'Thursday',
50
+ friday: 'Friday',
51
+ saturday: 'Saturday',
52
+ },
53
+ },
54
+ notification: {
55
+ preferences: {
56
+ title: 'Email Notification Settings',
57
+ button: 'Email Settings',
58
+ frequency: {
59
+ label: 'Notification Frequency',
60
+ default: 'Instant Notifications (Default)',
61
+ daily: 'Daily',
62
+ weekly: 'Weekly',
63
+ monthly: 'Monthly',
64
+ },
65
+ day: 'Day',
66
+ timeFormatError: 'Please enter a valid time in 24-hour format (HH:MM)',
67
+ monthlyHelp: 'If day is not available in a month, the last day will be used',
68
+ subscriptionRenewalNote: 'This setting applies to subscription renewal notifications.',
69
+ },
40
70
  },
41
71
  admin: {
42
72
  balances: 'Balances',
@@ -36,6 +36,36 @@ export default flat({
36
36
  copySuccess: '复制成功',
37
37
  copyFailed: '复制失败',
38
38
  copyTip: '请手动复制',
39
+ save: '保存',
40
+ cancel: '取消',
41
+ know: '知道了',
42
+ confirm: '确认',
43
+ days: {
44
+ sunday: '星期日',
45
+ monday: '星期一',
46
+ tuesday: '星期二',
47
+ wednesday: '星期三',
48
+ thursday: '星期四',
49
+ friday: '星期五',
50
+ saturday: '星期六',
51
+ },
52
+ },
53
+ notification: {
54
+ preferences: {
55
+ title: '邮件通知设置',
56
+ button: '邮件设置',
57
+ frequency: {
58
+ label: '通知频率',
59
+ default: '即时通知 (默认)',
60
+ daily: '每日',
61
+ weekly: '每周',
62
+ monthly: '每月',
63
+ },
64
+ day: '日',
65
+ timeFormatError: '请输入有效的时间,格式为24小时制 (HH:MM)',
66
+ monthlyHelp: '如果所选日期在某月不存在,将使用该月的最后一天',
67
+ subscriptionRenewalNote: '此设置适用于订阅续费通知。',
68
+ },
39
69
  },
40
70
  admin: {
41
71
  balances: '余额',
@@ -40,6 +40,7 @@ import { joinURL } from 'ufo';
40
40
 
41
41
  import { useTransitionContext } from '../../components/progress-bar';
42
42
  import CurrentSubscriptions from '../../components/subscription/portal/list';
43
+ import NotificationPreference from '../../components/customer/notification-preference';
43
44
  import { useSessionContext } from '../../contexts/session';
44
45
  import api from '../../libs/api';
45
46
  import CustomerRevenueList from '../../components/payouts/portal/list';
@@ -339,28 +340,31 @@ export default function CustomerHome() {
339
340
  <Box className="base-card section section-subscription">
340
341
  <Box className="section-header">
341
342
  <Typography variant="h3">{t('admin.subscription.name')}</Typography>
342
- {subscriptionStatus && (
343
- <FormControl
344
- sx={{
345
- '.MuiInputBase-root': {
346
- background: 'none',
347
- border: 'none',
348
- },
349
- '.MuiOutlinedInput-notchedOutline': {
350
- border: 'none',
351
- },
352
- }}>
353
- <Select
354
- value={state.onlyActive ? 'active' : ''}
355
- onChange={onToggleActive}
356
- displayEmpty
357
- IconComponent={ExpandMore}
358
- inputProps={{ 'aria-label': 'Without label' }}>
359
- <MenuItem value="">All</MenuItem>
360
- <MenuItem value="active">Active</MenuItem>
361
- </Select>
362
- </FormControl>
363
- )}
343
+ <Stack direction="row" spacing={1} alignItems="center">
344
+ <NotificationPreference />
345
+ {subscriptionStatus && (
346
+ <FormControl
347
+ sx={{
348
+ '.MuiInputBase-root': {
349
+ background: 'none',
350
+ border: 'none',
351
+ },
352
+ '.MuiOutlinedInput-notchedOutline': {
353
+ border: 'none',
354
+ },
355
+ }}>
356
+ <Select
357
+ value={state.onlyActive ? 'active' : ''}
358
+ onChange={onToggleActive}
359
+ displayEmpty
360
+ IconComponent={ExpandMore}
361
+ inputProps={{ 'aria-label': 'Without label' }}>
362
+ <MenuItem value="">All</MenuItem>
363
+ <MenuItem value="active">Active</MenuItem>
364
+ </Select>
365
+ </FormControl>
366
+ )}
367
+ </Stack>
364
368
  </Box>
365
369
  <Box className="section-body">
366
370
  {subscriptionLoading ? (
@@ -131,7 +131,7 @@ export default function BalanceRechargePage() {
131
131
  cycle: data.recommendedRecharge.cycle,
132
132
  });
133
133
 
134
- setPresetAmounts([
134
+ const newPresetAmounts = [
135
135
  {
136
136
  amount: calcCycleAmount(1),
137
137
  multiplier: data.recommendedRecharge.cycle,
@@ -159,13 +159,13 @@ export default function BalanceRechargePage() {
159
159
  }),
160
160
  }),
161
161
  },
162
- ]);
162
+ ];
163
163
 
164
- setAmount(
165
- Math.ceil(
166
- parseFloat(formatBNStr(new BN(baseAmount).mul(new BN('4')).toString(), decimal, 6, true))
167
- ).toString()
168
- );
164
+ setPresetAmounts(newPresetAmounts);
165
+ const midAmount = calcCycleAmount(4);
166
+ if (!customAmount && !newPresetAmounts.find((item) => item.amount === midAmount)) {
167
+ setAmount(midAmount);
168
+ }
169
169
  } else {
170
170
  setPresetAmounts([
171
171
  { amount: '10', multiplier: 0, label: '' },
@@ -357,6 +357,7 @@ export default function SubscriptionEmbed() {
357
357
  open: state.batchPay,
358
358
  onClose: () => setState({ batchPay: false }),
359
359
  }}
360
+ authToken={authToken}
360
361
  />
361
362
  )}
362
363
  </PaymentProvider>