payment-kit 1.18.11 → 1.18.13

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 (37) hide show
  1. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +5 -3
  2. package/api/src/libs/notification/template/subscription-canceled.ts +3 -3
  3. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +4 -3
  4. package/api/src/libs/notification/template/subscription-renew-failed.ts +5 -4
  5. package/api/src/libs/notification/template/subscription-renewed.ts +2 -1
  6. package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +3 -4
  7. package/api/src/libs/notification/template/subscription-succeeded.ts +2 -1
  8. package/api/src/libs/notification/template/subscription-upgraded.ts +6 -4
  9. package/api/src/libs/notification/template/subscription-will-canceled.ts +6 -3
  10. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  11. package/api/src/routes/connect/change-payment.ts +1 -0
  12. package/api/src/routes/connect/change-plan.ts +1 -0
  13. package/api/src/routes/connect/setup.ts +1 -0
  14. package/api/src/routes/connect/shared.ts +39 -33
  15. package/api/src/routes/connect/subscribe.ts +14 -8
  16. package/api/src/routes/customers.ts +79 -5
  17. package/api/src/routes/subscriptions.ts +13 -1
  18. package/api/src/store/models/invoice.ts +4 -2
  19. package/blocklet.yml +3 -3
  20. package/package.json +15 -15
  21. package/src/app.tsx +17 -17
  22. package/src/components/actions.tsx +32 -9
  23. package/src/components/copyable.tsx +2 -2
  24. package/src/components/layout/user.tsx +37 -0
  25. package/src/components/subscription/portal/actions.tsx +26 -5
  26. package/src/components/subscription/portal/list.tsx +24 -6
  27. package/src/components/subscription/status.tsx +2 -2
  28. package/src/libs/util.ts +15 -0
  29. package/src/pages/admin/payments/payouts/detail.tsx +6 -1
  30. package/src/pages/customer/index.tsx +247 -154
  31. package/src/pages/customer/invoice/detail.tsx +1 -1
  32. package/src/pages/customer/payout/detail.tsx +9 -2
  33. package/src/pages/customer/recharge.tsx +6 -2
  34. package/src/pages/customer/subscription/change-payment.tsx +1 -1
  35. package/src/pages/customer/subscription/change-plan.tsx +1 -1
  36. package/src/pages/customer/subscription/detail.tsx +8 -3
  37. package/src/pages/customer/subscription/embed.tsx +142 -84
@@ -87,9 +87,6 @@ export class OneTimePaymentSucceededEmailTemplate
87
87
  const productName = await getMainProductNameByCheckoutSession(checkoutSession);
88
88
  const at: string = formatTime(checkoutSession.created_at);
89
89
 
90
- const paymentInfo: string = `${fromUnitToToken(checkoutSession?.amount_total, paymentCurrency.decimal)} ${
91
- paymentCurrency.symbol
92
- }`;
93
90
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
94
91
  const nftMintItem: NftMintItem | undefined = hasNft
95
92
  ? // @ts-expect-error
@@ -98,6 +95,11 @@ export class OneTimePaymentSucceededEmailTemplate
98
95
 
99
96
  const paymentIntent = await PaymentIntent.findByPk(checkoutSession!.payment_intent_id);
100
97
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentIntent!.payment_method_id);
98
+
99
+ const paymentInfo: string = `${fromUnitToToken(checkoutSession?.amount_total, paymentCurrency.decimal)} ${
100
+ paymentCurrency.symbol
101
+ }${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
102
+
101
103
  // @ts-expect-error
102
104
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
103
105
  const viewSubscriptionLink = '';
@@ -76,9 +76,6 @@ export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<Subs
76
76
  const locale = await getUserLocale(userDid);
77
77
  const productName = await getMainProductName(subscription.id);
78
78
  const at: string = formatTime(subscription.canceled_at * 1000);
79
-
80
- // @ts-ignore
81
- const paymentInfo: string = `${fromUnitToToken(invoice.total, invoice?.paymentCurrency?.decimal)} ${invoice?.paymentCurrency?.symbol}`;
82
79
  const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
83
80
  const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
84
81
  const duration: string = prettyMsI18n(
@@ -89,6 +86,9 @@ export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<Subs
89
86
  );
90
87
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
91
88
 
89
+ // @ts-ignore
90
+ const paymentInfo: string = `${fromUnitToToken(invoice.total, invoice?.paymentCurrency?.decimal)} ${invoice?.paymentCurrency?.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
91
+
92
92
  const customerCancelRequest = subscription.cancelation_details?.reason === 'cancellation_requested';
93
93
  let cancellationReason = '';
94
94
  const { stakeEnough, stakeReturn, stakeSlash, hasStake } = await getSubscriptionStakeCancellation(
@@ -75,9 +75,6 @@ export class SubscriptionRefundSucceededEmailTemplate
75
75
  const productName = await getMainProductName(refund.subscription_id!);
76
76
  const at: string = formatTime(refund.created_at);
77
77
 
78
- const paymentInfo: string = `${fromUnitToToken(paymentIntent?.amount_received || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
79
- const refundInfo: string = `${fromUnitToToken(refund.amount, paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
80
-
81
78
  let invoice = null;
82
79
  let currentPeriodStart;
83
80
  let currentPeriodEnd;
@@ -105,6 +102,10 @@ export class SubscriptionRefundSucceededEmailTemplate
105
102
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(
106
103
  refund.payment_method_id || invoice?.default_payment_method_id
107
104
  );
105
+
106
+ const paymentInfo: string = `${fromUnitToToken(paymentIntent?.amount_received || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
107
+ const refundInfo: string = `${fromUnitToToken(refund.amount, paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
108
+
108
109
  // @ts-expect-error
109
110
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
110
111
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
@@ -112,9 +112,6 @@ export class SubscriptionRenewFailedEmailTemplate
112
112
  const nftMintItem: NftMintItem | undefined = hasNft
113
113
  ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
114
114
  : undefined;
115
- const paymentInfo: string = `${fromUnitToToken(invoice.amount_remaining, paymentCurrency.decimal)} ${
116
- paymentCurrency.symbol
117
- }`;
118
115
 
119
116
  const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
120
117
  const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
@@ -124,8 +121,12 @@ export class SubscriptionRenewFailedEmailTemplate
124
121
  locale: getPrettyMsI18nLocale(locale),
125
122
  }
126
123
  );
127
-
128
124
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
125
+
126
+ const paymentInfo: string = `${fromUnitToToken(invoice.amount_remaining, paymentCurrency.decimal)} ${
127
+ paymentCurrency.symbol
128
+ }${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
129
+
129
130
  const chainHost: string | undefined =
130
131
  paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
131
132
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
@@ -102,7 +102,6 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
102
102
  ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
103
103
  : undefined;
104
104
  const amountPaid = +fromUnitToToken(invoice.total, paymentCurrency.decimal);
105
- const paymentInfo: string = `${fromUnitToToken(invoice.total, paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
106
105
  const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
107
106
  const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
108
107
  const duration: string = prettyMsI18n(
@@ -113,6 +112,8 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
113
112
  );
114
113
 
115
114
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
115
+ const paymentInfo: string = `${fromUnitToToken(invoice.total, paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
116
+
116
117
  const chainHost: string | undefined =
117
118
  paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
118
119
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
@@ -10,7 +10,6 @@ import { getCustomerSubscriptionPageUrl } from '../../subscription';
10
10
  import { formatTime } from '../../time';
11
11
  import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
12
12
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
13
- import logger from '../../logger';
14
13
 
15
14
  export interface SubscriptionStakeSlashSucceededEmailTemplateOptions {
16
15
  paymentIntentId: string;
@@ -83,9 +82,10 @@ export class SubscriptionStakeSlashSucceededEmailTemplate
83
82
  const productName = await getMainProductName(this.options.subscriptionId);
84
83
  const at: string = formatTime(paymentIntent.created_at);
85
84
 
86
- const slashInfo: string = `${fromUnitToToken(paymentIntent?.amount_received || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
87
-
88
85
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentIntent.payment_method_id);
86
+
87
+ const slashInfo: string = `${fromUnitToToken(paymentIntent?.amount_received || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
88
+
89
89
  // @ts-expect-error
90
90
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
91
91
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
@@ -140,7 +140,6 @@ export class SubscriptionStakeSlashSucceededEmailTemplate
140
140
  customActions,
141
141
  } = await this.getContext();
142
142
 
143
- logger.info('SubscriptionStakeSlashSucceededEmailTemplate getTemplate', { productName, at, userDid, slashInfo, viewSubscriptionLink, viewTxHashLink });
144
143
  const template: BaseEmailTemplateType = {
145
144
  title: `${translate('notification.subscriptionStakeSlashSucceeded.title', locale, {
146
145
  productName,
@@ -120,7 +120,6 @@ export class SubscriptionSucceededEmailTemplate
120
120
  const oneTimeProductInfo = await getOneTimeProductInfo(subscription.latest_invoice_id as string, paymentCurrency);
121
121
  const paymentAmount = await getPaymentAmountForCycleSubscription(subscription, paymentCurrency);
122
122
 
123
- const paymentInfo: string = `${paymentAmount} ${paymentCurrency.symbol}`;
124
123
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
125
124
  const nftMintItem: NftMintItem | undefined = hasNft
126
125
  ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
@@ -135,6 +134,8 @@ export class SubscriptionSucceededEmailTemplate
135
134
  );
136
135
 
137
136
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
137
+ const paymentInfo: string = `${paymentAmount} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
138
+
138
139
  // @FIXME: 获取 chainHost 困难的一批?
139
140
  const chainHost: string | undefined =
140
141
  paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
@@ -88,10 +88,6 @@ export class SubscriptionUpgradedEmailTemplate implements BaseEmailTemplate<Subs
88
88
  const productName = await getMainProductName(subscription.id);
89
89
  const at: string = formatTime(subscription.created_at);
90
90
 
91
- const paymentInfo: string = `${fromUnitToToken(
92
- paymentIntent?.amount || invoice.amount_paid,
93
- paymentCurrency.decimal
94
- )} ${paymentCurrency.symbol}`;
95
91
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
96
92
  const nftMintItem: NftMintItem | undefined = hasNft
97
93
  ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
@@ -110,6 +106,12 @@ export class SubscriptionUpgradedEmailTemplate implements BaseEmailTemplate<Subs
110
106
  );
111
107
 
112
108
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
109
+
110
+ const paymentInfo: string = `${fromUnitToToken(
111
+ paymentIntent?.amount || invoice.amount_paid,
112
+ paymentCurrency.decimal
113
+ )} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
114
+
113
115
  // @ts-expect-error
114
116
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
115
117
  const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
@@ -6,7 +6,7 @@ import type { ManipulateType } from 'dayjs';
6
6
  import dayjs from '../../dayjs';
7
7
  import { getUserLocale } from '../../../integrations/blocklet/notification';
8
8
  import { translate } from '../../../locales';
9
- import { Customer, Invoice, Subscription } from '../../../store/models';
9
+ import { Customer, Invoice, PaymentMethod, Subscription } from '../../../store/models';
10
10
  import { PaymentCurrency } from '../../../store/models/payment-currency';
11
11
  import { getCustomerInvoicePageUrl } from '../../invoice';
12
12
  import logger from '../../logger';
@@ -82,7 +82,10 @@ export class SubscriptionWillCanceledEmailTemplate
82
82
  where: {
83
83
  id: subscription.latest_invoice_id,
84
84
  },
85
- include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
85
+ include: [
86
+ { model: PaymentCurrency, as: 'paymentCurrency' },
87
+ { model: PaymentMethod, as: 'paymentMethod' },
88
+ ],
86
89
  });
87
90
  if (!invoice) {
88
91
  throw new Error(`Invoice(${subscription.latest_invoice_id}) not found`);
@@ -94,7 +97,7 @@ export class SubscriptionWillCanceledEmailTemplate
94
97
  const at: string = formatTime(cancelAt * 1000);
95
98
  const willCancelDuration: string = getSimplifyDuration((cancelAt - now) * 1000, locale);
96
99
  // @ts-ignore
97
- const paymentInfo: string = `${fromUnitToToken(+invoice.total, invoice?.paymentCurrency?.decimal)} ${invoice?.paymentCurrency?.symbol}`;
100
+ const paymentInfo: string = `${fromUnitToToken(+invoice.total, invoice?.paymentCurrency?.decimal)} ${invoice?.paymentCurrency?.symbol}${invoice?.paymentMethod ? `(${invoice?.paymentMethod.name})` : ''}`;
98
101
 
99
102
  let body: string = translate('notification.subscriptWillCanceled.body', locale, {
100
103
  productName,
@@ -117,7 +117,7 @@ export class SubscriptionWillRenewEmailTemplate
117
117
  const paidType: string = isPrePaid
118
118
  ? translate('notification.common.prepaid', locale)
119
119
  : translate('notification.common.postpaid', locale);
120
- const paymentInfo: string = `${paymentDetail?.price || '0'} ${paymentCurrency.symbol}`;
120
+ const paymentInfo: string = `${paymentDetail?.price || '0'} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
121
121
  const currentPeriodStart: string = isPrePaid
122
122
  ? formatTime(invoice.period_end * 1000)
123
123
  : formatTime(invoice.period_start * 1000);
@@ -17,6 +17,7 @@ import logger from '../../libs/logger';
17
17
  export default {
18
18
  action: 'change-payment',
19
19
  authPrincipal: false,
20
+ persistentDynamicClaims: true,
20
21
  claims: {
21
22
  authPrincipal: async ({ extraParams }: CallbackArgs) => {
22
23
  const { paymentMethod } = await ensureChangePaymentContext(extraParams.subscriptionId);
@@ -21,6 +21,7 @@ import logger from '../../libs/logger';
21
21
  export default {
22
22
  action: 'change-plan',
23
23
  authPrincipal: false,
24
+ persistentDynamicClaims: true,
24
25
  claims: {
25
26
  authPrincipal: async ({ extraParams }: CallbackArgs) => {
26
27
  const { paymentMethod } = await ensureSubscription(extraParams.subscriptionId);
@@ -22,6 +22,7 @@ import { EVM_CHAIN_TYPES } from '../../libs/constants';
22
22
  export default {
23
23
  action: 'setup',
24
24
  authPrincipal: false,
25
+ persistentDynamicClaims: true,
25
26
  claims: {
26
27
  authPrincipal: async ({ extraParams }: CallbackArgs) => {
27
28
  const { paymentMethod } = await ensureSetupIntent(extraParams.checkoutSessionId);
@@ -1011,6 +1011,7 @@ export async function executeOcapTransactions(
1011
1011
  nonce?: string
1012
1012
  ) {
1013
1013
  const client = paymentMethod.getOcapClient();
1014
+ logger.info('start executeOcapTransactions', claims);
1014
1015
  const delegation = claims.find((x) => x.type === 'signature' && x.meta?.purpose === 'delegation');
1015
1016
  const staking = claims.find((x) => x.type === 'prepareTx' && x.meta?.purpose === 'staking');
1016
1017
  const transactions = [
@@ -1021,40 +1022,45 @@ export async function executeOcapTransactions(
1021
1022
  const stakingAmount =
1022
1023
  staking?.requirement?.tokens?.find((x: any) => x.address === paymentCurrencyContract)?.value || '0';
1023
1024
 
1024
- const [delegationTxHash, stakingTxHash] = await Promise.all(
1025
- transactions.map(async ([claim, type]) => {
1026
- if (!claim) {
1027
- return '';
1028
- }
1029
-
1030
- const tx: Partial<Transaction> = client.decodeTx(claim.finalTx || claim.origin);
1031
- if (claim.sig) {
1032
- tx.signature = claim.sig;
1033
- }
1034
-
1035
- // @ts-ignore
1036
- const { buffer } = await client[`encode${type}Tx`]({ tx });
1037
- // @ts-ignore
1038
- const txHash = await client[`send${type}Tx`](
1039
- // @ts-ignore
1040
- { tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
1041
- getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
1025
+ try {
1026
+ const [delegationTxHash, stakingTxHash] = await Promise.all(
1027
+ transactions.map(async ([claim, type]) => {
1028
+ if (!claim) {
1029
+ return '';
1030
+ }
1031
+
1032
+ const tx: Partial<Transaction> = client.decodeTx(claim.finalTx || claim.origin);
1033
+ if (claim.sig) {
1034
+ tx.signature = claim.sig;
1035
+ }
1036
+
1037
+ // @ts-ignore
1038
+ const { buffer } = await client[`encode${type}Tx`]({ tx });
1039
+ // @ts-ignore
1040
+ const txHash = await client[`send${type}Tx`](
1041
+ // @ts-ignore
1042
+ { tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
1043
+ getGasPayerExtra(buffer, client.pickGasPayerHeaders(request))
1044
+ );
1045
+
1046
+ return txHash;
1047
+ })
1042
1048
  );
1043
-
1044
- return txHash;
1045
- })
1046
- );
1047
-
1048
- return {
1049
- tx_hash: delegationTxHash,
1050
- payer: userDid,
1051
- type: 'delegate',
1052
- staking: {
1053
- tx_hash: stakingTxHash,
1054
- address: await getCustomerStakeAddress(userDid, nonce || subscriptionId || ''),
1055
- },
1056
- stakingAmount,
1057
- };
1049
+
1050
+ return {
1051
+ tx_hash: delegationTxHash,
1052
+ payer: userDid,
1053
+ type: 'delegate',
1054
+ staking: {
1055
+ tx_hash: stakingTxHash,
1056
+ address: await getCustomerStakeAddress(userDid, nonce || subscriptionId || ''),
1057
+ },
1058
+ stakingAmount,
1059
+ };
1060
+ } catch (err) {
1061
+ logger.error('executeOcapTransactions failed', err);
1062
+ throw err;
1063
+ }
1058
1064
  }
1059
1065
 
1060
1066
 
@@ -23,6 +23,7 @@ import { EVM_CHAIN_TYPES } from '../../libs/constants';
23
23
  export default {
24
24
  action: 'subscription',
25
25
  authPrincipal: false,
26
+ persistentDynamicClaims: true,
26
27
  claims: {
27
28
  authPrincipal: async ({ extraParams }: CallbackArgs) => {
28
29
  const { paymentMethod } = await ensurePaymentIntent(extraParams.checkoutSessionId);
@@ -151,15 +152,15 @@ export default {
151
152
  if (!subscription) {
152
153
  throw new Error('Subscription for checkoutSession not found');
153
154
  }
154
-
155
+ const paymentSettings = {
156
+ payment_method_types: [paymentMethod.type],
157
+ payment_method_options: {
158
+ [paymentMethod.type]: { payer: userDid },
159
+ },
160
+ };
155
161
  const prepareTxExecution = async () => {
156
162
  await subscription.update({
157
- payment_settings: {
158
- payment_method_types: [paymentMethod.type],
159
- payment_method_options: {
160
- [paymentMethod.type]: { payer: userDid },
161
- },
162
- },
163
+ payment_settings: paymentSettings,
163
164
  });
164
165
  };
165
166
 
@@ -179,7 +180,9 @@ export default {
179
180
  if (paymentMethod.type === 'arcblock') {
180
181
  await prepareTxExecution();
181
182
  const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
182
-
183
+ if (invoice) {
184
+ await invoice.update({ payment_settings: paymentSettings });
185
+ }
183
186
  const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
184
187
  userDid,
185
188
  userPk,
@@ -217,6 +220,9 @@ export default {
217
220
  if (EVM_CHAIN_TYPES.includes(paymentMethod.type)) {
218
221
  await prepareTxExecution();
219
222
  const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
223
+ if (invoice) {
224
+ await invoice.update({ payment_settings: paymentSettings });
225
+ }
220
226
  broadcastEvmTransaction(checkoutSessionId, 'pending', claimsList);
221
227
 
222
228
  const paymentDetails = await executeEvmTransaction('approve', userDid, claimsList, paymentMethod);
@@ -11,6 +11,7 @@ import { formatMetadata } from '../libs/util';
11
11
  import { Customer } from '../store/models/customer';
12
12
  import { blocklet } from '../libs/auth';
13
13
  import logger from '../libs/logger';
14
+ import { Invoice } from '../store/models';
14
15
 
15
16
  const router = Router();
16
17
  const auth = authenticate<Customer>({ component: true, roles: ['owner', 'admin'] });
@@ -86,26 +87,99 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
86
87
  }
87
88
 
88
89
  try {
89
- const doc = await Customer.findByPkOrDid(req.user.did as string);
90
+ let doc = await Customer.findByPkOrDid(req.user.did as string);
90
91
  const livemode = req.query.livemode ? !!req?.livemode : !!doc?.livemode;
91
92
  if (!doc) {
92
93
  if (req.query.fallback) {
93
94
  const result = await blocklet.getUser(req.user.did);
94
- res.json({ ...result.user, address: {}, livemode });
95
+ return res.json({ ...result.user, address: {}, livemode });
96
+ }
97
+ if (req.query.create) {
98
+ // create customer
99
+ const { user } = await blocklet.getUser(req.user.did);
100
+ const customer = await Customer.create({
101
+ livemode: true,
102
+ did: req.user.did,
103
+ name: user.fullName,
104
+ email: user.email,
105
+ phone: '',
106
+ address: {},
107
+ description: user.remark,
108
+ metadata: {},
109
+ balance: '0',
110
+ next_invoice_sequence: 1,
111
+ delinquent: false,
112
+ invoice_prefix: Customer.getInvoicePrefix(),
113
+ });
114
+ logger.info('customer created', {
115
+ customerId: customer.id,
116
+ did: customer.did,
117
+ });
118
+ doc = customer;
95
119
  } else {
96
- res.json({ error: 'Customer not found' });
120
+ return res.json({ error: 'Customer not found' });
97
121
  }
98
- } else {
122
+ }
123
+ try {
99
124
  const [summary, stake, token] = await Promise.all([
100
125
  doc.getSummary(livemode),
101
126
  getStakeSummaryByDid(doc.did, livemode),
102
127
  getTokenSummaryByDid(doc.did, livemode),
103
128
  ]);
104
129
  res.json({ ...doc.toJSON(), summary: { ...summary, stake, token }, livemode });
130
+ } catch (summaryErr) {
131
+ logger.error('get customer summary failed', summaryErr);
132
+ if (req.query.skipError) {
133
+ return res.json({
134
+ ...doc.toJSON(),
135
+ summary: { stake: {}, token: {} },
136
+ livemode,
137
+ summaryError: summaryErr.message,
138
+ error: `Failed to get customer: ${summaryErr.message}`,
139
+ });
140
+ }
141
+ throw summaryErr;
105
142
  }
106
143
  } catch (err) {
107
144
  logger.error('get customer failed', err);
108
- res.status(500).json({ error: `Failed to get customer: ${err.message}` });
145
+ if (req.query.skipError) {
146
+ return res.json({
147
+ error: `Failed to get customer: ${err.message}`,
148
+ did: req.user?.did,
149
+ name: req.user?.fullName,
150
+ address: {},
151
+ livemode: !!req.query.livemode,
152
+ summary: { stake: {}, token: {} },
153
+ });
154
+ }
155
+ return res.status(500).json({ error: `Failed to get customer: ${err.message}` });
156
+ }
157
+ });
158
+
159
+ // get overdue invoices
160
+ router.get('/:id/overdue/invoices', auth, async (req, res) => {
161
+ if (!req.user) {
162
+ return res.status(403).json({ error: 'Unauthorized' });
163
+ }
164
+ try {
165
+ const doc = await Customer.findByPkOrDid(req.params.id as string);
166
+ if (!doc) {
167
+ return res.status(404).json({ error: 'Customer not found' });
168
+ }
169
+ const [summary, detail, invoices] = await Invoice!.getUncollectibleAmount({
170
+ customerId: doc.id,
171
+ livemode: req.query.livemode ? !!req.query.livemode : doc.livemode,
172
+ });
173
+ const subscriptionCount = new Set(invoices.map((x) => x.subscription_id)).size;
174
+ return res.json({
175
+ summary,
176
+ invoices,
177
+ subscriptionCount,
178
+ detail,
179
+ });
180
+ } catch (err) {
181
+ logger.error(err);
182
+ return res.status(500).json({ error: `Failed to get overdue invoices: ${err.message}` });
109
183
  }
110
184
  });
111
185
 
@@ -97,6 +97,7 @@ const schema = createListParamSchema<{
97
97
  activeFirst?: boolean;
98
98
  price_id?: string;
99
99
  order?: string | string[] | OrderItem | OrderItem[];
100
+ showTotalCount?: boolean;
100
101
  }>({
101
102
  status: Joi.string().empty(''),
102
103
  customer_id: Joi.string().empty(''),
@@ -110,6 +111,7 @@ const schema = createListParamSchema<{
110
111
  Joi.array().items(Joi.array().ordered(Joi.string(), Joi.string().valid('ASC', 'DESC').insensitive()))
111
112
  )
112
113
  .optional(),
114
+ showTotalCount: Joi.boolean().optional(),
113
115
  });
114
116
 
115
117
  const parseOrder = (orderStr: string): OrderItem => {
@@ -195,7 +197,17 @@ router.get('/', authMine, async (req, res) => {
195
197
  // @ts-ignore
196
198
  docs.forEach((x) => expandLineItems(x.items, products, prices));
197
199
 
198
- res.json({ count, list: docs, paging: { page, pageSize } });
200
+ if (query.showTotalCount) {
201
+ const totalCount = await Subscription.count({
202
+ where: {
203
+ customer_id: where.customer_id,
204
+ },
205
+ distinct: true,
206
+ });
207
+ res.json({ count, list: docs, paging: { page, pageSize }, totalCount });
208
+ } else {
209
+ res.json({ count, list: docs, paging: { page, pageSize } });
210
+ }
199
211
  } catch (err) {
200
212
  logger.error(err);
201
213
  res.json({ count: 0, list: [], paging: { page, pageSize } });
@@ -530,7 +530,9 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
530
530
  return ['paid', 'void'].includes(this.status);
531
531
  }
532
532
 
533
- private static async _getUncollectibleAmount(where: WhereOptions<Invoice>): Promise<[GroupedBN, GroupedStrList]> {
533
+ private static async _getUncollectibleAmount(
534
+ where: WhereOptions<Invoice>
535
+ ): Promise<[GroupedBN, GroupedStrList, Invoice[]]> {
534
536
  const invoices = await Invoice.findAll({ where });
535
537
  const summary: GroupedBN = {};
536
538
  const detail: GroupedStrList = {};
@@ -548,7 +550,7 @@ export class Invoice extends Model<InferAttributes<Invoice>, InferCreationAttrib
548
550
  detail[key]!.push(invoice.id);
549
551
  });
550
552
 
551
- return [summary, detail];
553
+ return [summary, detail, invoices];
552
554
  }
553
555
 
554
556
  public static getUncollectibleAmount({
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.18.11
17
+ version: 1.18.13
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
@@ -113,7 +113,7 @@ navigation:
113
113
  icon: ion:receipt-outline
114
114
  link: /customer
115
115
  section:
116
- - dashboard
116
+ - userCenter
117
117
  - sessionManager
118
118
  role:
119
119
  - owner
@@ -165,4 +165,4 @@ events:
165
165
  - type: invoice.paid
166
166
  description: Invoice has been fully paid and settled
167
167
  - type: refund.succeeded
168
- description: Refund has been successfully processed and completed
168
+ description: Refund has been successfully processed and completed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.18.11",
3
+ "version": "1.18.13",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -44,29 +44,29 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@abtnode/cron": "^1.16.39",
47
- "@arcblock/did": "^1.19.10",
47
+ "@arcblock/did": "^1.19.14",
48
48
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
49
49
  "@arcblock/did-connect": "^2.11.48",
50
- "@arcblock/did-util": "^1.19.10",
51
- "@arcblock/jwt": "^1.19.10",
50
+ "@arcblock/did-util": "^1.19.14",
51
+ "@arcblock/jwt": "^1.19.14",
52
52
  "@arcblock/ux": "^2.11.48",
53
- "@arcblock/validator": "^1.19.10",
53
+ "@arcblock/validator": "^1.19.14",
54
54
  "@blocklet/js-sdk": "^1.16.39",
55
55
  "@blocklet/logger": "^1.16.39",
56
- "@blocklet/payment-react": "1.18.11",
56
+ "@blocklet/payment-react": "1.18.13",
57
57
  "@blocklet/sdk": "^1.16.39",
58
58
  "@blocklet/ui-react": "^2.11.48",
59
- "@blocklet/uploader": "^0.1.70",
60
- "@blocklet/xss": "^0.1.25",
59
+ "@blocklet/uploader": "^0.1.71",
60
+ "@blocklet/xss": "^0.1.27",
61
61
  "@mui/icons-material": "^5.16.6",
62
62
  "@mui/lab": "^5.0.0-alpha.173",
63
63
  "@mui/material": "^5.16.6",
64
64
  "@mui/system": "^5.16.6",
65
- "@ocap/asset": "^1.19.10",
66
- "@ocap/client": "^1.19.10",
67
- "@ocap/mcrypto": "^1.19.10",
68
- "@ocap/util": "^1.19.10",
69
- "@ocap/wallet": "^1.19.10",
65
+ "@ocap/asset": "^1.19.14",
66
+ "@ocap/client": "^1.19.14",
67
+ "@ocap/mcrypto": "^1.19.14",
68
+ "@ocap/util": "^1.19.14",
69
+ "@ocap/wallet": "^1.19.14",
70
70
  "@stripe/react-stripe-js": "^2.7.3",
71
71
  "@stripe/stripe-js": "^2.4.0",
72
72
  "ahooks": "^3.8.0",
@@ -121,7 +121,7 @@
121
121
  "devDependencies": {
122
122
  "@abtnode/types": "^1.16.39",
123
123
  "@arcblock/eslint-config-ts": "^0.3.3",
124
- "@blocklet/payment-types": "1.18.11",
124
+ "@blocklet/payment-types": "1.18.13",
125
125
  "@types/cookie-parser": "^1.4.7",
126
126
  "@types/cors": "^2.8.17",
127
127
  "@types/debug": "^4.1.12",
@@ -167,5 +167,5 @@
167
167
  "parser": "typescript"
168
168
  }
169
169
  },
170
- "gitHead": "370cb327400359ab5972ef60316ccd7fc86c174e"
170
+ "gitHead": "b26aa859ba5961c7a8dedfc238a31476cf16036c"
171
171
  }