payment-kit 1.18.17 → 1.18.19

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 (35) hide show
  1. package/api/src/libs/subscription.ts +116 -0
  2. package/api/src/routes/checkout-sessions.ts +28 -1
  3. package/api/src/routes/customers.ts +5 -1
  4. package/api/src/store/migrations/20250318-donate-invoice.ts +45 -0
  5. package/api/tests/libs/subscription.spec.ts +311 -0
  6. package/blocklet.yml +1 -1
  7. package/package.json +9 -9
  8. package/src/components/currency.tsx +11 -4
  9. package/src/components/customer/link.tsx +54 -14
  10. package/src/components/customer/overdraft-protection.tsx +36 -2
  11. package/src/components/info-card.tsx +55 -7
  12. package/src/components/info-row-group.tsx +122 -0
  13. package/src/components/info-row.tsx +14 -1
  14. package/src/components/payouts/portal/list.tsx +7 -2
  15. package/src/components/subscription/items/index.tsx +1 -1
  16. package/src/components/subscription/metrics.tsx +14 -6
  17. package/src/contexts/info-row.tsx +4 -0
  18. package/src/locales/en.tsx +1 -0
  19. package/src/locales/zh.tsx +1 -0
  20. package/src/pages/admin/billing/invoices/detail.tsx +54 -76
  21. package/src/pages/admin/billing/subscriptions/detail.tsx +34 -71
  22. package/src/pages/admin/customers/customers/detail.tsx +41 -64
  23. package/src/pages/admin/payments/intents/detail.tsx +28 -42
  24. package/src/pages/admin/payments/payouts/detail.tsx +27 -36
  25. package/src/pages/admin/payments/refunds/detail.tsx +27 -41
  26. package/src/pages/admin/products/links/detail.tsx +30 -55
  27. package/src/pages/admin/products/prices/detail.tsx +43 -50
  28. package/src/pages/admin/products/pricing-tables/detail.tsx +23 -25
  29. package/src/pages/admin/products/products/detail.tsx +52 -81
  30. package/src/pages/customer/index.tsx +183 -108
  31. package/src/pages/customer/invoice/detail.tsx +49 -50
  32. package/src/pages/customer/payout/detail.tsx +16 -22
  33. package/src/pages/customer/recharge/account.tsx +92 -34
  34. package/src/pages/customer/recharge/subscription.tsx +6 -0
  35. package/src/pages/customer/subscription/detail.tsx +176 -94
@@ -10,8 +10,26 @@ import {
10
10
  useMobile,
11
11
  } from '@blocklet/payment-react';
12
12
  import type { TPaymentCurrency, TSubscriptionExpanded } from '@blocklet/payment-types';
13
- import { ArrowBackOutlined, CheckCircle } from '@mui/icons-material';
14
- import { Alert, Avatar, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
13
+ import {
14
+ AccountBalanceWalletOutlined,
15
+ ArrowBackOutlined,
16
+ CheckCircle,
17
+ HelpOutline,
18
+ SettingsOutlined,
19
+ } from '@mui/icons-material';
20
+ import {
21
+ Alert,
22
+ Avatar,
23
+ Box,
24
+ Button,
25
+ CircularProgress,
26
+ Divider,
27
+ Stack,
28
+ Typography,
29
+ Link as MuiLink,
30
+ IconButton,
31
+ Tooltip,
32
+ } from '@mui/material';
15
33
  import { useRequest } from 'ahooks';
16
34
  import { Link, useNavigate, useParams } from 'react-router-dom';
17
35
  import { styled } from '@mui/system';
@@ -29,6 +47,7 @@ import { useSessionContext } from '../../../contexts/session';
29
47
  import InfoMetric from '../../../components/info-metric';
30
48
  import { useUnpaidInvoicesCheckForSubscription } from '../../../hooks/subscription';
31
49
  import { formatSmartDuration, TimeUnit } from '../../../libs/dayjs';
50
+ import InfoRowGroup from '../../../components/info-row-group';
32
51
 
33
52
  const fetchData = (id: string | undefined): Promise<TSubscriptionExpanded> => {
34
53
  return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
@@ -47,9 +66,6 @@ const fetchCycleAmount = (
47
66
  return api.get(`/api/subscriptions/${subscriptionId}/cycle-amount`, { params }).then((res) => res.data);
48
67
  };
49
68
 
50
- const InfoDirection = 'column';
51
- const InfoAlignItems = 'flex-start';
52
-
53
69
  export default function CustomerSubscriptionDetail() {
54
70
  const { id } = useParams() as { id: string };
55
71
  const navigate = useNavigate();
@@ -176,52 +192,79 @@ export default function CustomerSubscriptionDetail() {
176
192
  }
177
193
 
178
194
  return (
179
- <Stack direction="row" spacing={1} alignItems="center">
180
- <Stack direction="row" spacing={0.5} alignItems="center">
181
- <CheckCircle
195
+ <Stack direction="row" alignItems="center">
196
+ <Stack direction="row" spacing={1} alignItems="center">
197
+ <Stack direction="row" spacing={0.5} alignItems="center">
198
+ <CheckCircle
199
+ sx={{
200
+ fontSize: '16px',
201
+ color: 'success.main',
202
+ verticalAlign: 'middle',
203
+ }}
204
+ />
205
+ <Typography
206
+ sx={{
207
+ color: 'success.main',
208
+ fontWeight: 500,
209
+ }}>
210
+ {t('customer.overdraftProtection.enabled')}
211
+ </Typography>
212
+ </Stack>
213
+ <Divider
214
+ orientation="vertical"
215
+ flexItem
182
216
  sx={{
183
- fontSize: '16px',
184
- color: 'success.main',
185
- verticalAlign: 'middle',
217
+ mx: 1,
218
+ borderColor: 'divider',
186
219
  }}
187
220
  />
188
221
  <Typography
189
222
  sx={{
190
- color: 'success.main',
223
+ color: 'text.primary',
224
+ display: 'flex',
225
+ alignItems: 'center',
226
+ gap: 0.5,
191
227
  fontWeight: 500,
192
228
  }}>
193
- {t('customer.overdraftProtection.enabled')}
229
+ <Avatar
230
+ src={data.paymentCurrency?.logo}
231
+ sx={{ width: 16, height: 16 }}
232
+ alt={data.paymentCurrency?.symbol}
233
+ />
234
+ <Box display="flex" alignItems="baseline">
235
+ {formatBNStr(overdraftProtection?.unused, data.paymentCurrency.decimal)}
236
+ <Typography
237
+ sx={{
238
+ color: 'text.secondary',
239
+ fontSize: '14px',
240
+ ml: 0.5,
241
+ }}>
242
+ {data.paymentCurrency.symbol}({formatEstimatedDuration(Math.ceil(remainingStake / estimateAmount))})
243
+ </Typography>
244
+ </Box>
194
245
  </Typography>
195
246
  </Stack>
196
- <Divider
197
- orientation="vertical"
198
- flexItem
199
- sx={{
200
- mx: 1,
201
- borderColor: 'divider',
202
- }}
203
- />
204
- <Typography
205
- sx={{
206
- color: 'text.primary',
207
- display: 'flex',
208
- alignItems: 'center',
209
- gap: 0.5,
210
- fontWeight: 500,
211
- }}>
212
- <Avatar src={data.paymentCurrency?.logo} sx={{ width: 16, height: 16 }} alt={data.paymentCurrency?.symbol} />
213
- <Box display="flex" alignItems="baseline">
214
- {formatBNStr(overdraftProtection?.unused, data.paymentCurrency.decimal)}
215
- <Typography
247
+ {data?.overdraft_protection?.enabled && (
248
+ <Tooltip title={t('customer.overdraftProtection.setting')} placement="top">
249
+ <IconButton
250
+ size="small"
216
251
  sx={{
217
- color: 'text.secondary',
218
- fontSize: '14px',
219
- ml: 0.5,
220
- }}>
221
- {data.paymentCurrency.symbol}({formatEstimatedDuration(Math.ceil(remainingStake / estimateAmount))})
222
- </Typography>
223
- </Box>
224
- </Typography>
252
+ ml: -1,
253
+ color: 'text.disabled',
254
+ '&:hover': {
255
+ color: 'primary.main',
256
+ backgroundColor: 'transparent',
257
+ },
258
+ }}
259
+ onClick={() =>
260
+ actionRef.current?.openOverdraftProtection({
261
+ enabled: true,
262
+ })
263
+ }>
264
+ <SettingsOutlined fontSize="small" sx={{ fontSize: 16 }} />
265
+ </IconButton>
266
+ </Tooltip>
267
+ )}
225
268
  </Stack>
226
269
  );
227
270
  };
@@ -337,57 +380,112 @@ export default function CustomerSubscriptionDetail() {
337
380
  }}>
338
381
  <SubscriptionMetrics subscription={data} />
339
382
  {showOverdraftProtection && (
340
- <InfoMetric label={t('customer.overdraftProtection.title')} value={renderOverdraftProtectionLabel()} />
383
+ <InfoMetric
384
+ label={
385
+ <Stack direction="row" alignItems="center" spacing={0.5}>
386
+ <Typography variant="body2" component="span">
387
+ {t('customer.overdraftProtection.title')}
388
+ </Typography>
389
+ <MuiLink
390
+ href="https://www.arcblock.io/content/blog/en/payment-kit-v117-sub-guard#listen-to-the-audio-overview"
391
+ target="_blank"
392
+ rel="noopener noreferrer"
393
+ sx={{
394
+ display: 'flex',
395
+ alignItems: 'center',
396
+ }}>
397
+ <Tooltip title={t('customer.overdraftProtection.learnMore')} placement="top">
398
+ <HelpOutline
399
+ fontSize="small"
400
+ sx={{
401
+ fontSize: '14px',
402
+ ml: -0.2,
403
+ color: 'text.secondary',
404
+ cursor: 'pointer',
405
+ opacity: 0.8,
406
+ '&:hover': { color: 'primary.main' },
407
+ }}
408
+ />
409
+ </Tooltip>
410
+ </MuiLink>
411
+ {data.overdraft_protection?.payment_details?.arcblock?.staking?.address && (
412
+ <Tooltip
413
+ title={
414
+ <Typography sx={{ fontFamily: 'monospace', fontSize: '13px' }}>
415
+ {t('customer.overdraftProtection.stakingAddress')}:
416
+ {data.overdraft_protection?.payment_details?.arcblock?.staking?.address}
417
+ </Typography>
418
+ }
419
+ arrow
420
+ placement="top">
421
+ <AccountBalanceWalletOutlined
422
+ sx={{
423
+ fontSize: '16px',
424
+ color: 'text.secondary',
425
+ cursor: 'pointer',
426
+ ml: 1,
427
+ '&:hover': { color: 'primary.main' },
428
+ display: {
429
+ xs: 'none',
430
+ md: 'block',
431
+ },
432
+ }}
433
+ />
434
+ </Tooltip>
435
+ )}
436
+ </Stack>
437
+ }
438
+ value={renderOverdraftProtectionLabel()}
439
+ />
341
440
  )}
342
441
  </Stack>
343
442
  </Box>
344
443
  </Box>
345
444
  <Divider />
346
- <Box className="section">
445
+ <Box className="section" sx={{ containerType: 'inline-size' }}>
347
446
  <Typography variant="h3" mb={3} className="section-header">
348
447
  {t('admin.details')}
349
448
  </Typography>
350
- <Box
449
+ <InfoRowGroup
351
450
  sx={{
352
451
  display: 'grid',
353
452
  gridTemplateColumns: {
354
453
  xs: 'repeat(1, 1fr)',
355
- sm: 'repeat(1, 1fr)',
356
- md: 'repeat(2, 1fr)',
357
- lg: 'repeat(3, 1fr)',
454
+ xl: 'repeat(2, 1fr)',
358
455
  },
359
- gap: {
360
- xs: 0,
361
- md: 2,
456
+ '@container (min-width: 980px)': {
457
+ gridTemplateColumns: 'repeat(2, 1fr)',
458
+ },
459
+ '.info-row-wrapper': {
460
+ gap: 1,
461
+ flexDirection: {
462
+ xs: 'column',
463
+ xl: 'row',
464
+ },
465
+ alignItems: {
466
+ xs: 'flex-start',
467
+ xl: 'center',
468
+ },
469
+ '@container (min-width: 980px)': {
470
+ flexDirection: 'row',
471
+ alignItems: 'center',
472
+ },
473
+ },
474
+ '.currency-name': {
475
+ color: 'text.secondary',
362
476
  },
363
477
  }}>
364
478
  <InfoRow
365
479
  label={t('common.customer')}
366
- value={<CustomerLink customer={data.customer} linked={false} />}
367
- direction={InfoDirection}
368
- alignItems={InfoAlignItems}
480
+ value={<CustomerLink customer={data.customer} linked={false} size={isMobile ? 'default' : 'small'} />}
369
481
  />
370
- <InfoRow
371
- label={t('common.createdAt')}
372
- value={formatTime(data.created_at)}
373
- direction={InfoDirection}
374
- alignItems={InfoAlignItems}
375
- />
376
- {data.status === 'paused' && !!data.pause_collection?.resumes_at && (
377
- <InfoRow
378
- label={t('common.resumesAt')}
379
- value={formatTime(data.pause_collection.resumes_at * 1000)}
380
- direction={InfoDirection}
381
- alignItems={InfoAlignItems}
382
- />
383
- )}
482
+ <InfoRow label={t('common.createdAt')} value={formatTime(data.created_at)} />
483
+
384
484
  <InfoRow
385
485
  label={t('admin.subscription.currentPeriod')}
386
486
  value={[formatTime(data.current_period_start * 1000), formatTime(data.current_period_end * 1000)].join(
387
487
  ' ~ '
388
488
  )}
389
- direction={InfoDirection}
390
- alignItems={InfoAlignItems}
391
489
  />
392
490
  <InfoRow
393
491
  label={t('admin.subscription.trialingPeriod')}
@@ -396,26 +494,17 @@ export default function CustomerSubscriptionDetail() {
396
494
  ? [formatTime(data.trial_start * 1000), formatTime(data.trial_end * 1000)].join(' ~ ')
397
495
  : ''
398
496
  }
399
- direction={InfoDirection}
400
- alignItems={InfoAlignItems}
401
- />
402
- <InfoRow
403
- label={t('admin.subscription.discount')}
404
- value={data.discount_id ? data.discount_id : ''}
405
- direction={InfoDirection}
406
- alignItems={InfoAlignItems}
407
- />
408
- <InfoRow
409
- label={t('admin.subscription.collectionMethod')}
410
- value={data.collection_method}
411
- direction={InfoDirection}
412
- alignItems={InfoAlignItems}
413
497
  />
498
+ {data.status === 'paused' && !!data.pause_collection?.resumes_at && (
499
+ <InfoRow label={t('common.resumesAt')} value={formatTime(data.pause_collection.resumes_at * 1000)} />
500
+ )}
501
+
502
+ <InfoRow label={t('admin.subscription.collectionMethod')} value={data.collection_method} />
503
+ <InfoRow label={t('admin.subscription.discount')} value={data.discount_id ? data.discount_id : ''} />
504
+
414
505
  <InfoRow
415
506
  label={t('admin.paymentMethod._name')}
416
507
  value={<Currency logo={data.paymentMethod?.logo} name={data.paymentMethod?.name} />}
417
- alignItems={InfoAlignItems}
418
- direction={InfoDirection}
419
508
  />
420
509
  <InfoRow
421
510
  label={t('admin.paymentCurrency.name')}
@@ -439,15 +528,12 @@ export default function CustomerSubscriptionDetail() {
439
528
  )}
440
529
  </Stack>
441
530
  }
442
- direction={InfoDirection}
443
- alignItems={InfoAlignItems}
444
531
  />
532
+
445
533
  {data.payment_details && hasDelegateTxHash(data.payment_details, data.paymentMethod) && (
446
534
  <InfoRow
447
535
  label={t('common.delegateTxHash')}
448
536
  value={<TxLink details={data.payment_details} method={data.paymentMethod} />}
449
- direction={InfoDirection}
450
- alignItems={InfoAlignItems}
451
537
  />
452
538
  )}
453
539
  {data.paymentMethod?.type === 'arcblock' && data.payment_details?.arcblock?.staking?.tx_hash && (
@@ -466,8 +552,6 @@ export default function CustomerSubscriptionDetail() {
466
552
  />
467
553
  </Box>
468
554
  }
469
- direction={InfoDirection}
470
- alignItems={InfoAlignItems}
471
555
  />
472
556
  )}
473
557
  {!!data.recovered_from && (
@@ -478,11 +562,9 @@ export default function CustomerSubscriptionDetail() {
478
562
  {data.recovered_from}
479
563
  </Link>
480
564
  }
481
- direction={InfoDirection}
482
- alignItems={InfoAlignItems}
483
565
  />
484
566
  )}
485
- </Box>
567
+ </InfoRowGroup>
486
568
  </Box>
487
569
  <Divider />
488
570
  <Box className="section">