payment-kit 1.21.16 → 1.22.0

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 (65) hide show
  1. package/api/src/index.ts +3 -1
  2. package/api/src/integrations/blocklet/user.ts +2 -2
  3. package/api/src/integrations/ethereum/token.ts +4 -5
  4. package/api/src/integrations/stripe/handlers/invoice.ts +31 -26
  5. package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -1
  6. package/api/src/integrations/stripe/handlers/setup-intent.ts +231 -0
  7. package/api/src/integrations/stripe/handlers/subscription.ts +31 -9
  8. package/api/src/integrations/stripe/resource.ts +30 -1
  9. package/api/src/integrations/stripe/setup.ts +1 -1
  10. package/api/src/libs/auth.ts +7 -6
  11. package/api/src/libs/env.ts +1 -1
  12. package/api/src/libs/notification/template/subscription-trial-will-end.ts +1 -0
  13. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  14. package/api/src/libs/payment.ts +11 -6
  15. package/api/src/libs/refund.ts +1 -1
  16. package/api/src/libs/remote-signer.ts +93 -0
  17. package/api/src/libs/security.ts +1 -1
  18. package/api/src/libs/subscription.ts +4 -7
  19. package/api/src/libs/util.ts +18 -1
  20. package/api/src/libs/vendor-util/adapters/didnames-adapter.ts +17 -9
  21. package/api/src/libs/vendor-util/adapters/launcher-adapter.ts +11 -6
  22. package/api/src/queues/payment.ts +2 -2
  23. package/api/src/queues/payout.ts +1 -1
  24. package/api/src/queues/refund.ts +2 -2
  25. package/api/src/queues/subscription.ts +1 -1
  26. package/api/src/queues/usage-record.ts +1 -1
  27. package/api/src/queues/vendors/status-check.ts +1 -1
  28. package/api/src/queues/webhook.ts +1 -1
  29. package/api/src/routes/auto-recharge-configs.ts +1 -1
  30. package/api/src/routes/checkout-sessions.ts +4 -6
  31. package/api/src/routes/connect/change-payer.ts +148 -0
  32. package/api/src/routes/connect/collect-batch.ts +1 -1
  33. package/api/src/routes/connect/collect.ts +1 -1
  34. package/api/src/routes/connect/pay.ts +1 -1
  35. package/api/src/routes/connect/recharge-account.ts +1 -1
  36. package/api/src/routes/connect/recharge.ts +1 -1
  37. package/api/src/routes/connect/shared.ts +62 -23
  38. package/api/src/routes/customers.ts +1 -1
  39. package/api/src/routes/integrations/stripe.ts +1 -1
  40. package/api/src/routes/invoices.ts +141 -2
  41. package/api/src/routes/meter-events.ts +9 -12
  42. package/api/src/routes/payment-currencies.ts +1 -1
  43. package/api/src/routes/payment-intents.ts +2 -2
  44. package/api/src/routes/payment-links.ts +2 -1
  45. package/api/src/routes/payouts.ts +1 -1
  46. package/api/src/routes/products.ts +1 -0
  47. package/api/src/routes/subscriptions.ts +130 -3
  48. package/api/src/store/models/types.ts +1 -1
  49. package/api/tests/setup.ts +11 -0
  50. package/api/third.d.ts +0 -2
  51. package/blocklet.yml +1 -1
  52. package/jest.config.js +2 -2
  53. package/package.json +26 -26
  54. package/src/components/invoice/table.tsx +2 -2
  55. package/src/components/invoice-pdf/template.tsx +30 -0
  56. package/src/components/subscription/payment-method-info.tsx +222 -0
  57. package/src/global.css +4 -0
  58. package/src/libs/util.ts +1 -1
  59. package/src/locales/en.tsx +13 -0
  60. package/src/locales/zh.tsx +13 -0
  61. package/src/pages/admin/billing/invoices/detail.tsx +5 -3
  62. package/src/pages/admin/billing/subscriptions/detail.tsx +16 -0
  63. package/src/pages/admin/overview.tsx +14 -14
  64. package/src/pages/customer/invoice/detail.tsx +59 -17
  65. package/src/pages/customer/subscription/detail.tsx +21 -2
@@ -15,13 +15,14 @@ import {
15
15
  usePaymentContext,
16
16
  PaymentBeneficiaries,
17
17
  useMobile,
18
+ StripePaymentAction,
18
19
  } from '@blocklet/payment-react';
19
20
  import type { TCheckoutSession, TCreditGrantExpanded, TInvoiceExpanded, TPaymentLink } from '@blocklet/payment-types';
20
21
  import { ArrowBackOutlined } from '@mui/icons-material';
21
22
  import { Alert, Box, Button, CircularProgress, Divider, Stack, Tooltip, Typography, useTheme } from '@mui/material';
22
23
  import { styled } from '@mui/system';
23
24
  import { useRequest, useSetState } from 'ahooks';
24
- import { useEffect } from 'react';
25
+ import { useEffect, useRef } from 'react';
25
26
  import { Link, useParams, useSearchParams } from 'react-router-dom';
26
27
 
27
28
  import { joinURL } from 'ufo';
@@ -48,7 +49,7 @@ const fetchData = (
48
49
  relatedCreditGrants?: TCreditGrantExpanded[];
49
50
  }
50
51
  > => {
51
- return api.get(`/api/invoices/${id}`).then((res) => res.data);
52
+ return api.get(`/api/invoices/${id}`).then((res: any) => res.data);
52
53
  };
53
54
 
54
55
  export default function CustomerInvoiceDetail() {
@@ -64,16 +65,19 @@ export default function CustomerInvoiceDetail() {
64
65
  });
65
66
 
66
67
  const { loading, error, data, runAsync } = useRequest(() => fetchData(params.id as string));
68
+
67
69
  const action = searchParams.get('action');
68
70
  const { session } = useSessionContext();
69
71
  const isDonation = data?.checkoutSession?.submit_type === 'donate';
72
+ const payHandlerRef = useRef<(() => void) | null>(null);
70
73
 
71
- const onPay = () => {
74
+ const handleExternalPayment = () => {
72
75
  setState({ paying: true });
73
76
  connect.open({
74
77
  action: 'collect',
75
78
  saveConnect: false,
76
79
  locale: locale as 'en' | 'zh',
80
+ extraParams: { invoiceId: params.id, action },
77
81
  messages: {
78
82
  scan: '',
79
83
  title: t(`payment.customer.invoice.${action || 'pay'}`),
@@ -81,9 +85,9 @@ export default function CustomerInvoiceDetail() {
81
85
  error: t(`payment.customer.invoice.${action || 'pay'}Error`),
82
86
  confirm: '',
83
87
  } as any,
84
- extraParams: { invoiceId: params.id, action },
85
88
  onSuccess: async () => {
86
89
  connect.close();
90
+ setState({ paying: false });
87
91
  await runAsync();
88
92
  },
89
93
  onClose: () => {
@@ -110,7 +114,7 @@ export default function CustomerInvoiceDetail() {
110
114
  ['pay', 'renew'].includes(action as string) &&
111
115
  ['open', 'uncollectible'].includes(data?.status as string)
112
116
  ) {
113
- onPay();
117
+ handleExternalPayment();
114
118
  }
115
119
  // eslint-disable-next-line react-hooks/exhaustive-deps
116
120
  }, [error]);
@@ -133,6 +137,9 @@ export default function CustomerInvoiceDetail() {
133
137
 
134
138
  const hidePayButton = data.billing_reason === 'overdraft_protection';
135
139
  const paymentDetails = data.paymentIntent?.payment_details || data.metadata?.payment_details;
140
+
141
+ const showDownloadButton =
142
+ ['open', 'paid', 'uncollectible'].includes(data.status) && !isDonation && !isEmpty(data.lines);
136
143
  return (
137
144
  <InvoiceDetailRoot direction="column" spacing={3}>
138
145
  <Stack
@@ -161,7 +168,7 @@ export default function CustomerInvoiceDetail() {
161
168
  justifyContent: 'flex-end',
162
169
  alignItems: 'center',
163
170
  }}>
164
- {['open', 'paid', 'uncollectible'].includes(data.status) && !isDonation && <Download data={data} />}
171
+ {showDownloadButton && <Download data={data} />}
165
172
  {data?.paymentLink?.donation_settings?.reference && (
166
173
  <Button
167
174
  variant="outlined"
@@ -171,17 +178,52 @@ export default function CustomerInvoiceDetail() {
171
178
  {t('customer.payout.viewReference')}
172
179
  </Button>
173
180
  )}
174
- {['open', 'uncollectible'].includes(data.status) && !hidePayButton && (
175
- <Button
176
- variant="outlined"
177
- color="primary"
178
- size="small"
179
- sx={{ borderColor: 'divider' }}
180
- disabled={state.paying}
181
- onClick={onPay}>
182
- {t('payment.customer.invoice.pay')}
183
- </Button>
184
- )}
181
+ {['open', 'uncollectible'].includes(data.status) &&
182
+ !hidePayButton &&
183
+ (data.paymentMethod?.type === 'stripe' ? (
184
+ <StripePaymentAction
185
+ invoice={data}
186
+ paymentMethod={data.paymentMethod}
187
+ onSuccess={async () => {
188
+ Toast.close();
189
+ setState({ paying: false });
190
+ await runAsync();
191
+ }}
192
+ onError={() => {
193
+ setState({ paying: false });
194
+ }}
195
+ onClose={() => {
196
+ setState({ paying: false });
197
+ }}>
198
+ {(onPay: () => void, paying: boolean) => {
199
+ payHandlerRef.current = onPay;
200
+ return (
201
+ <Button
202
+ variant="outlined"
203
+ color="primary"
204
+ size="small"
205
+ sx={{ borderColor: 'divider' }}
206
+ disabled={state.paying || paying}
207
+ onClick={() => {
208
+ setState({ paying: true });
209
+ onPay();
210
+ }}>
211
+ {t('payment.customer.invoice.pay')}
212
+ </Button>
213
+ );
214
+ }}
215
+ </StripePaymentAction>
216
+ ) : (
217
+ <Button
218
+ variant="outlined"
219
+ color="primary"
220
+ size="small"
221
+ sx={{ borderColor: 'divider' }}
222
+ disabled={state.paying}
223
+ onClick={handleExternalPayment}>
224
+ {t('payment.customer.invoice.pay')}
225
+ </Button>
226
+ ))}
185
227
  </Stack>
186
228
  </Stack>
187
229
  <Box
@@ -52,6 +52,7 @@ import { useSessionContext } from '../../../contexts/session';
52
52
  import { usePendingAmountForSubscription, useUnpaidInvoicesCheckForSubscription } from '../../../hooks/subscription';
53
53
  import { formatSmartDuration, TimeUnit } from '../../../libs/dayjs';
54
54
  import { canChangePaymentMethod } from '../../../libs/util';
55
+ import PaymentMethodInfo from '../../../components/subscription/payment-method-info';
55
56
 
56
57
  const fetchData = (id: string | undefined): Promise<TSubscriptionExpanded> => {
57
58
  return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
@@ -144,7 +145,7 @@ export default function CustomerSubscriptionDetail() {
144
145
  );
145
146
  const estimateAmount = +fromUnitToToken(cycleAmount.amount, data.paymentCurrency?.decimal);
146
147
 
147
- const remainingStake = +fromUnitToToken(overdraftProtection?.unused, data.paymentCurrency?.decimal);
148
+ const remainingStake = +fromUnitToToken(overdraftProtection?.unused || '0', data.paymentCurrency?.decimal);
148
149
  const formatEstimatedDuration = (cycles: number) => {
149
150
  if (!data?.pending_invoice_item_interval) return '';
150
151
  const { interval, interval_count: intervalCount } = data.pending_invoice_item_interval;
@@ -622,7 +623,7 @@ export default function CustomerSubscriptionDetail() {
622
623
  {canChangePaymentMethod(data) && (
623
624
  <Button
624
625
  variant="text"
625
- sx={{ color: 'text.link', fontSize: 14 }}
626
+ sx={{ color: 'text.link' }}
626
627
  size="small"
627
628
  onClick={async () => {
628
629
  // only check unpaid invoices when overdraft protection is enabled
@@ -640,6 +641,24 @@ export default function CustomerSubscriptionDetail() {
640
641
  </Stack>
641
642
  }
642
643
  />
644
+ {(data as any).paymentMethodDetails && (
645
+ <InfoRow
646
+ label={t('admin.subscription.payerAddress')}
647
+ value={
648
+ <PaymentMethodInfo
649
+ subscriptionId={id}
650
+ customer={data.customer}
651
+ paymentMethodDetails={(data as any).paymentMethodDetails}
652
+ editable={['active', 'trialing', 'past_due'].includes(data.status)}
653
+ onUpdate={() => {
654
+ refresh();
655
+ checkUnpaidInvoices();
656
+ }}
657
+ paymentMethodType={data.paymentMethod?.type}
658
+ />
659
+ }
660
+ />
661
+ )}
643
662
 
644
663
  {data.payment_details && hasDelegateTxHash(data.payment_details, data.paymentMethod) && (
645
664
  <InfoRow