payment-kit 1.13.174 → 1.13.176

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 (40) hide show
  1. package/api/src/crons/index.ts +20 -1
  2. package/api/src/integrations/blockchain/stake.ts +99 -1
  3. package/api/src/integrations/stripe/handlers/invoice.ts +4 -0
  4. package/api/src/integrations/stripe/handlers/payment-intent.ts +1 -1
  5. package/api/src/integrations/stripe/resource.ts +63 -12
  6. package/api/src/libs/audit.ts +3 -3
  7. package/api/src/libs/env.ts +2 -0
  8. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  9. package/api/src/libs/subscription.ts +35 -2
  10. package/api/src/queues/checkout-session.ts +98 -94
  11. package/api/src/queues/invoice.ts +23 -13
  12. package/api/src/queues/notification.ts +13 -11
  13. package/api/src/queues/payment.ts +27 -21
  14. package/api/src/queues/refund.ts +5 -5
  15. package/api/src/queues/subscription.ts +210 -49
  16. package/api/src/routes/checkout-sessions.ts +8 -40
  17. package/api/src/routes/connect/change-payment.ts +52 -38
  18. package/api/src/routes/connect/change-plan.ts +51 -39
  19. package/api/src/routes/connect/collect-batch.ts +1 -0
  20. package/api/src/routes/connect/collect.ts +2 -1
  21. package/api/src/routes/connect/pay.ts +1 -0
  22. package/api/src/routes/connect/setup.ts +70 -56
  23. package/api/src/routes/connect/shared.ts +162 -17
  24. package/api/src/routes/connect/subscribe.ts +60 -54
  25. package/api/src/routes/invoices.ts +5 -0
  26. package/api/src/routes/payment-intents.ts +6 -2
  27. package/api/src/store/models/subscription.ts +1 -6
  28. package/api/src/store/models/types.ts +5 -1
  29. package/api/tests/libs/subscription.spec.ts +85 -3
  30. package/blocklet.yml +1 -1
  31. package/package.json +4 -4
  32. package/src/app.tsx +2 -1
  33. package/src/components/customer/link.tsx +22 -8
  34. package/src/components/event/list.tsx +1 -3
  35. package/src/pages/admin/billing/subscriptions/detail.tsx +12 -1
  36. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  37. package/src/pages/admin/products/products/index.tsx +3 -0
  38. package/src/pages/customer/invoice/detail.tsx +7 -3
  39. package/src/pages/customer/subscription/detail.tsx +14 -5
  40. /package/api/src/libs/notification/template/{subscription-cacceled.ts → subscription-canceled.ts} +0 -0
@@ -1,22 +1,26 @@
1
1
  /* eslint-disable @typescript-eslint/indent */
2
2
  /* eslint-disable prettier/prettier */
3
3
  import { toTypeInfo } from '@arcblock/did';
4
- import { toDelegateAddress } from '@arcblock/did-util';
5
- import { BN } from '@ocap/util';
4
+ import { toDelegateAddress, toStakeAddress } from '@arcblock/did-util';
5
+ import type { Transaction } from '@ocap/client';
6
+ import { BN, fromTokenToUnit } from '@ocap/util';
6
7
  import { fromPublicKey } from '@ocap/wallet';
7
8
  import isEmpty from 'lodash/isEmpty';
8
9
 
9
10
  import { estimateMaxGasForTx, hasStakedForGas } from '../../integrations/blockchain/stake';
10
11
  import { blocklet, wallet } from '../../libs/auth';
11
12
  import dayjs from '../../libs/dayjs';
13
+ import env from '../../libs/env';
12
14
  import logger from '../../libs/logger';
13
- import { getTokenLimitsForDelegation } from '../../libs/payment';
15
+ import { getGasPayerExtra, getTokenLimitsForDelegation } from '../../libs/payment';
16
+ import { getFastCheckoutAmount, getPriceUintAmountByCurrency, getStatementDescriptor } from '../../libs/session';
14
17
  import {
15
- getFastCheckoutAmount,
16
- getPriceUintAmountByCurrency,
17
- getStatementDescriptor,
18
- } from '../../libs/session';
19
- import { expandSubscriptionItems, getSubscriptionItemPrice } from '../../libs/subscription';
18
+ expandSubscriptionItems,
19
+ getSubscriptionCreateSetup,
20
+ getSubscriptionItemPrice,
21
+ getSubscriptionStakeSetup,
22
+ } from '../../libs/subscription';
23
+ import { OCAP_PAYMENT_TX_TYPE } from '../../libs/util';
20
24
  import type { TLineItemExpanded } from '../../store/models';
21
25
  import { CheckoutSession } from '../../store/models/checkout-session';
22
26
  import { Customer } from '../../store/models/customer';
@@ -29,7 +33,6 @@ import { Price } from '../../store/models/price';
29
33
  import { SetupIntent } from '../../store/models/setup-intent';
30
34
  import { Subscription } from '../../store/models/subscription';
31
35
  import { SubscriptionItem } from '../../store/models/subscription-item';
32
- import { OCAP_PAYMENT_TX_TYPE } from '../../libs/util';
33
36
 
34
37
  type Result = {
35
38
  checkoutSession: CheckoutSession;
@@ -589,21 +592,23 @@ export async function getDelegationTxClaim({
589
592
  userPk,
590
593
  nonce,
591
594
  mode,
592
- trial,
593
595
  data,
594
596
  items,
595
597
  paymentCurrency,
596
598
  paymentMethod,
599
+ trialInDays = 0,
600
+ billingThreshold = 0,
597
601
  }: {
598
602
  userDid: string;
599
603
  userPk: string;
600
604
  nonce: string;
601
- mode: string;
602
- trial: boolean;
605
+ mode: string;
603
606
  data: any;
604
607
  items: TLineItemExpanded[];
605
608
  paymentCurrency: PaymentCurrency;
606
609
  paymentMethod: PaymentMethod;
610
+ trialInDays: number;
611
+ billingThreshold?: number;
607
612
  }) {
608
613
  const amount = getFastCheckoutAmount(items, mode, paymentCurrency.id);
609
614
  const address = toDelegateAddress(userDid, wallet.address);
@@ -611,7 +616,8 @@ export async function getDelegationTxClaim({
611
616
  const tokenRequirements = await getTokenRequirements({
612
617
  items,
613
618
  mode,
614
- includeFreeTrial: trial,
619
+ trialInDays,
620
+ billingThreshold,
615
621
  paymentMethod,
616
622
  paymentCurrency,
617
623
  });
@@ -637,26 +643,102 @@ export async function getDelegationTxClaim({
637
643
  host: paymentMethod.settings?.arcblock?.api_host as string,
638
644
  id: paymentMethod.settings?.arcblock?.chain_id as string,
639
645
  },
646
+ meta: {
647
+ purpose: 'delegation',
648
+ address,
649
+ },
650
+ };
651
+ }
652
+
653
+ export async function getStakeTxClaim({
654
+ userDid,
655
+ userPk,
656
+ items,
657
+ subscription,
658
+ paymentCurrency,
659
+ paymentMethod,
660
+ }: {
661
+ userDid: string;
662
+ userPk: string;
663
+ subscription: Subscription;
664
+ items: TLineItemExpanded[];
665
+ paymentCurrency: PaymentCurrency;
666
+ paymentMethod: PaymentMethod;
667
+ }) {
668
+ // create staking amount
669
+ const billingThreshold = fromTokenToUnit(subscription.billing_thresholds?.amount_gte || 0, paymentCurrency.decimal);
670
+ const staking = getSubscriptionStakeSetup(items, paymentCurrency.id, billingThreshold.toString());
671
+ const amount = staking.licensed.add(staking.metered).toString();
672
+
673
+ // create staking data
674
+ const client = paymentMethod.getOcapClient();
675
+ const address = toStakeAddress(userDid, wallet.address);
676
+ const { state } = await client.getStakeState({ address });
677
+ const data = {
678
+ type: 'json',
679
+ value: Object.assign(
680
+ {
681
+ appId: wallet.address,
682
+ },
683
+ JSON.parse(state?.data?.value || '{}'),
684
+ { [subscription.id]: amount }
685
+ ),
686
+ };
687
+
688
+ const setup = getSubscriptionCreateSetup(items, paymentCurrency.id, 0, 0);
689
+
690
+ return {
691
+ type: 'StakeTx',
692
+ description: 'Sign the staking to continue',
693
+ partialTx: {
694
+ from: userDid,
695
+ pk: userPk,
696
+ itx: {
697
+ address,
698
+ receiver: wallet.address,
699
+ slashers: [wallet.address],
700
+ revokeWaitingPeriod: setup.cycle.duration / 1000, // wait for at least 1 billing cycle
701
+ message: `Stake for subscription on ${env.appName}`,
702
+ inputs: [],
703
+ data,
704
+ },
705
+ signatures: [],
706
+ },
707
+ requirement: {
708
+ tokens: [{ address: paymentCurrency.contract as string, value: amount }],
709
+ },
710
+ nonce: `stake-${subscription.id}`,
711
+ chainInfo: {
712
+ type: paymentMethod.type,
713
+ host: paymentMethod.settings?.arcblock?.api_host as string,
714
+ id: paymentMethod.settings?.arcblock?.chain_id as string,
715
+ },
716
+ meta: {
717
+ purpose: 'staking',
718
+ address,
719
+ },
640
720
  };
641
721
  }
642
722
 
643
723
  export type TokenRequirementArgs = {
644
724
  items: TLineItemExpanded[];
645
725
  mode: string;
646
- includeFreeTrial: boolean;
647
726
  paymentMethod: PaymentMethod;
648
727
  paymentCurrency: PaymentCurrency;
728
+ trialInDays: number;
729
+ billingThreshold: number;
649
730
  };
650
731
 
651
732
  export async function getTokenRequirements({
652
733
  items,
653
734
  mode,
654
- includeFreeTrial,
655
735
  paymentMethod,
656
736
  paymentCurrency,
737
+ trialInDays = 0,
738
+ billingThreshold = 0,
657
739
  }: TokenRequirementArgs) {
658
740
  const tokenRequirements = [];
659
- let amount = getFastCheckoutAmount(items, mode, paymentCurrency.id, !!includeFreeTrial);
741
+ let amount = getFastCheckoutAmount(items, mode, paymentCurrency.id, !!trialInDays);
660
742
 
661
743
  // If the app has not staked, we need to add the gas fee to the amount
662
744
  if ((await hasStakedForGas(paymentMethod)) === false) {
@@ -680,10 +762,21 @@ export async function getTokenRequirements({
680
762
  tokenRequirements.push({ address: paymentCurrency.contract as string, value: amount });
681
763
  }
682
764
 
765
+ // Add stake requirement to token requirement
766
+ const staking = getSubscriptionStakeSetup(
767
+ items,
768
+ paymentCurrency.id,
769
+ fromTokenToUnit(billingThreshold, paymentCurrency.decimal).toString()
770
+ );
771
+ const exist = tokenRequirements.find((x) => x.address === paymentCurrency.contract);
772
+ if (exist) {
773
+ exist.value = new BN(exist.value).add(staking.licensed).add(staking.metered).toString();
774
+ }
775
+
683
776
  return tokenRequirements;
684
777
  }
685
778
 
686
- export async function ensureSubscription(subscriptionId: string): Promise<Result> {
779
+ export async function ensureSubscription(subscriptionId: string): Promise<Result & { customer: Customer }> {
687
780
  const subscription = await Subscription.findByPk(subscriptionId);
688
781
  if (!subscription) {
689
782
  throw new Error(`Subscription not found: ${subscriptionId}`);
@@ -722,6 +815,8 @@ export async function ensureSubscription(subscriptionId: string): Promise<Result
722
815
  paymentCurrency,
723
816
  // @ts-ignore
724
817
  invoice,
818
+ // @ts-ignore
819
+ customer: await Customer.findByPk(subscription.customer_id),
725
820
  };
726
821
  }
727
822
 
@@ -770,6 +865,7 @@ export async function ensureChangePaymentContext(subscriptionId: string) {
770
865
  setupIntent,
771
866
  paymentMethod,
772
867
  paymentCurrency,
868
+ customer: await Customer.findByPk(subscription.customer_id),
773
869
  };
774
870
  }
775
871
 
@@ -803,3 +899,52 @@ export async function ensureSubscriptionForCollectBatch(subscriptionId: string,
803
899
  invoices: detail[currencyId],
804
900
  };
805
901
  }
902
+
903
+ export async function executeOcapTransactions(
904
+ userDid: string,
905
+ userPk: string,
906
+ claims: any[],
907
+ paymentMethod: PaymentMethod
908
+ ) {
909
+ const client = paymentMethod.getOcapClient();
910
+ const delegation = claims.find((x) => x.type === 'signature' && x.meta?.purpose === 'delegation');
911
+ const staking = claims.find((x) => x.type === 'prepareTx' && x.meta?.purpose === 'staking');
912
+ const transactions = [
913
+ [delegation, 'Delegate'],
914
+ [staking, 'Stake'],
915
+ ];
916
+
917
+ const [delegationTxHash, stakingTxHash] = await Promise.all(
918
+ transactions.map(async ([claim, type]) => {
919
+ if (!claim) {
920
+ return '';
921
+ }
922
+
923
+ const tx: Partial<Transaction> = client.decodeTx(claim.finalTx || claim.origin);
924
+ if (claim.sig) {
925
+ tx.signature = claim.sig;
926
+ }
927
+
928
+ // @ts-ignore
929
+ const { buffer } = await client[`encode${type}Tx`]({ tx });
930
+ // @ts-ignore
931
+ const txHash = await client[`send${type}Tx`](
932
+ // @ts-ignore
933
+ { tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
934
+ getGasPayerExtra(buffer)
935
+ );
936
+
937
+ return txHash;
938
+ })
939
+ );
940
+
941
+ return {
942
+ tx_hash: delegationTxHash,
943
+ payer: userDid,
944
+ type: 'delegate',
945
+ staking: {
946
+ tx_hash: stakingTxHash,
947
+ address: toStakeAddress(userDid, wallet.address),
948
+ },
949
+ };
950
+ }
@@ -1,15 +1,19 @@
1
- import { toTypeInfo } from '@arcblock/did';
2
- import type { Transaction } from '@ocap/client';
3
- import { BN } from '@ocap/util';
4
- import { fromPublicKey } from '@ocap/wallet';
5
-
6
1
  import type { CallbackArgs } from '../../libs/auth';
7
- import { getGasPayerExtra } from '../../libs/payment';
2
+ import logger from '../../libs/logger';
3
+ import { isDelegationSufficientForPayment } from '../../libs/payment';
4
+ import { getFastCheckoutAmount } from '../../libs/session';
8
5
  import { getTxMetadata } from '../../libs/util';
9
6
  import { invoiceQueue } from '../../queues/invoice';
10
7
  import { addSubscriptionJob } from '../../queues/subscription';
11
8
  import type { TLineItemExpanded } from '../../store/models';
12
- import { ensureInvoiceForCheckout, ensurePaymentIntent, getAuthPrincipalClaim, getDelegationTxClaim } from './shared';
9
+ import {
10
+ ensureInvoiceForCheckout,
11
+ ensurePaymentIntent,
12
+ executeOcapTransactions,
13
+ getAuthPrincipalClaim,
14
+ getDelegationTxClaim,
15
+ getStakeTxClaim,
16
+ } from './shared';
13
17
 
14
18
  export default {
15
19
  action: 'subscription',
@@ -22,7 +26,7 @@ export default {
22
26
  },
23
27
  onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
24
28
  const { checkoutSessionId } = extraParams;
25
- const { checkoutSession, paymentMethod, paymentCurrency, subscription } = await ensurePaymentIntent(
29
+ const { checkoutSession, paymentMethod, paymentCurrency, subscription, customer } = await ensurePaymentIntent(
26
30
  checkoutSessionId,
27
31
  userDid
28
32
  );
@@ -31,28 +35,59 @@ export default {
31
35
  }
32
36
 
33
37
  if (paymentMethod.type === 'arcblock') {
38
+ const claims: { [type: string]: [string, object] } = {};
39
+
34
40
  const items = checkoutSession.line_items as TLineItemExpanded[];
41
+ const trialInDays = Number(checkoutSession.subscription_data?.trial_period_days || 0);
42
+ const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
43
+ const fastCheckoutAmount = getFastCheckoutAmount(items, checkoutSession.mode, paymentCurrency.id, !!trialInDays);
44
+ const delegation = await isDelegationSufficientForPayment({
45
+ paymentMethod,
46
+ paymentCurrency,
47
+ userDid: customer.did,
48
+ amount: fastCheckoutAmount,
49
+ });
50
+
51
+ // if we can complete purchase without any wallet interaction
52
+ if (delegation.sufficient === false) {
53
+ claims.delegation = [
54
+ 'signature',
55
+ await getDelegationTxClaim({
56
+ mode: checkoutSession.mode,
57
+ userDid,
58
+ userPk,
59
+ nonce: checkoutSession.id,
60
+ data: getTxMetadata({ subscriptionId: subscription.id, checkoutSessionId }),
61
+ paymentCurrency,
62
+ paymentMethod,
63
+ trialInDays,
64
+ billingThreshold,
65
+ items,
66
+ }),
67
+ ];
68
+ }
35
69
 
36
- return {
37
- signature: await getDelegationTxClaim({
38
- mode: checkoutSession.mode,
70
+ // we always need to stake for the subscription
71
+ claims.staking = [
72
+ 'prepareTx',
73
+ await getStakeTxClaim({
39
74
  userDid,
40
75
  userPk,
41
- nonce: checkoutSession.id,
42
- data: getTxMetadata({ subscriptionId: subscription.id, checkoutSessionId }),
43
76
  paymentCurrency,
44
77
  paymentMethod,
45
- trial: !!checkoutSession.subscription_data?.trial_period_days,
46
78
  items,
79
+ subscription,
47
80
  }),
48
- };
81
+ ];
82
+
83
+ return claims;
49
84
  }
50
85
 
51
86
  throw new Error(`Payment method ${paymentMethod.type} not supported`);
52
87
  },
53
88
  onAuth: async ({ userDid, userPk, claims, extraParams }: CallbackArgs) => {
54
89
  const { checkoutSessionId } = extraParams;
55
- const { checkoutSession, customer, paymentMethod, paymentCurrency, subscription } = await ensurePaymentIntent(
90
+ const { checkoutSession, customer, paymentMethod, subscription } = await ensurePaymentIntent(
56
91
  checkoutSessionId,
57
92
  userDid
58
93
  );
@@ -61,17 +96,6 @@ export default {
61
96
  throw new Error('Subscription for checkoutSession not found');
62
97
  }
63
98
 
64
- if (checkoutSession.amount_total > '0') {
65
- const client = paymentMethod.getOcapClient();
66
- const result = await client.getAccountTokens({ address: userDid, token: paymentCurrency.contract });
67
- const balance = result.tokens[0]?.balance || '0';
68
- if (new BN(balance).lt(new BN(checkoutSession.amount_total))) {
69
- throw new Error(
70
- `Your account ${userDid} does not have enough ${paymentCurrency.symbol} to complete this subscription`
71
- );
72
- }
73
- }
74
-
75
99
  if (paymentMethod.type === 'arcblock') {
76
100
  await subscription.update({
77
101
  payment_settings: {
@@ -83,37 +107,19 @@ export default {
83
107
  });
84
108
 
85
109
  const { invoice } = await ensureInvoiceForCheckout({ checkoutSession, customer, subscription });
86
-
87
- const client = paymentMethod.getOcapClient();
88
- const claim = claims.find((x) => x.type === 'signature');
89
-
90
- // execute the delegate tx
91
- const tx: Partial<Transaction> = client.decodeTx(claim.origin);
92
- tx.signature = claim.sig;
93
-
94
- // @ts-ignore
95
- const { buffer } = await client.encodeDelegateTx({ tx });
96
- const txHash = await client.sendDelegateTx(
97
- // @ts-ignore
98
- { tx, wallet: fromPublicKey(userPk, toTypeInfo(userDid)) },
99
- getGasPayerExtra(buffer)
100
- );
101
-
102
- await subscription.update({
103
- payment_details: {
104
- arcblock: {
105
- tx_hash: txHash,
106
- payer: userDid,
107
- },
108
- },
109
- });
110
-
110
+ const paymentDetails = await executeOcapTransactions(userDid, userPk, claims, paymentMethod);
111
+ await subscription.update({ payment_details: { arcblock: paymentDetails } });
111
112
  if (invoice) {
112
- invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
113
+ invoiceQueue.pushAndWait({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
113
114
  }
114
115
  await addSubscriptionJob(subscription, 'cycle', false, subscription.trial_end);
116
+ logger.info('CheckoutSession updated on subscription done', {
117
+ checkoutSession: checkoutSession.id,
118
+ subscription: subscription.id,
119
+ paymentDetails,
120
+ });
115
121
 
116
- return { hash: txHash };
122
+ return { hash: paymentDetails.tx_hash };
117
123
  }
118
124
 
119
125
  throw new Error(`Payment method ${paymentMethod.type} not supported`);
@@ -4,6 +4,7 @@ import Joi from 'joi';
4
4
  import pick from 'lodash/pick';
5
5
 
6
6
  import { syncStripeInvoice } from '../integrations/stripe/handlers/invoice';
7
+ import { syncStripePayment } from '../integrations/stripe/handlers/payment-intent';
7
8
  import { getWhereFromKvQuery } from '../libs/api';
8
9
  import { authenticate } from '../libs/security';
9
10
  import { expandLineItems } from '../libs/session';
@@ -175,6 +176,10 @@ router.get('/:id', authPortal, async (req, res) => {
175
176
  if (doc.status !== 'paid' && doc.metadata?.stripe_id) {
176
177
  await syncStripeInvoice(doc);
177
178
  }
179
+ if (doc.payment_intent_id) {
180
+ const paymentIntent = await PaymentIntent.findByPk(doc.payment_intent_id);
181
+ await syncStripePayment(paymentIntent!);
182
+ }
178
183
 
179
184
  const json = doc.toJSON();
180
185
  const products = (await Product.findAll()).map((x) => x.toJSON());
@@ -3,6 +3,7 @@ import { Router } from 'express';
3
3
  import Joi from 'joi';
4
4
  import pick from 'lodash/pick';
5
5
 
6
+ import { syncStripeInvoice } from '../integrations/stripe/handlers/invoice';
6
7
  import { syncStripePayment } from '../integrations/stripe/handlers/payment-intent';
7
8
  import { getWhereFromKvQuery, getWhereFromQuery } from '../libs/api';
8
9
  import { authenticate } from '../libs/security';
@@ -175,12 +176,15 @@ router.get('/:id', authPortal, async (req, res) => {
175
176
 
176
177
  if (doc) {
177
178
  const shouldSync = doc.status !== 'succeeded' || req.query.sync === '1';
178
- if (doc.metadata?.stripe_id && shouldSync) {
179
+ if (shouldSync) {
179
180
  await syncStripePayment(doc);
180
181
  }
182
+ invoice = await Invoice.findByPk(doc.invoice_id);
183
+ if (invoice?.metadata?.stripe_id) {
184
+ await syncStripeInvoice(invoice);
185
+ }
181
186
 
182
187
  checkoutSession = await CheckoutSession.findOne({ where: { payment_intent_id: doc.id } });
183
- invoice = await Invoice.findByPk(doc.invoice_id);
184
188
  if (invoice && invoice.subscription_id) {
185
189
  subscription = await Subscription.findByPk(invoice.subscription_id);
186
190
  }
@@ -59,7 +59,7 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
59
59
  | 'other',
60
60
  string
61
61
  >;
62
- reason: LiteralUnion<'cancellation_requested' | 'payment_disputed' | 'payment_failed', string>;
62
+ reason: LiteralUnion<'cancellation_requested' | 'payment_disputed' | 'payment_failed' | 'stake_revoked', string>;
63
63
  };
64
64
 
65
65
  declare billing_cycle_anchor: number;
@@ -328,11 +328,6 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
328
328
  }
329
329
  }
330
330
 
331
- // 发射 canceled 事件
332
- if (model.status === 'canceled' && model.previous('status') !== model.status) {
333
- createEvent('Subscription', 'customer.subscription.canceled', model, options).catch(console.error);
334
- }
335
-
336
331
  createStatusEvent(
337
332
  'Subscription',
338
333
  'customer.subscription',
@@ -260,6 +260,11 @@ export type PaymentDetails = {
260
260
  arcblock?: {
261
261
  tx_hash: string;
262
262
  payer: string;
263
+ type?: LiteralUnion<'slash' | 'transfer' | 'delegate', string>;
264
+ staking?: {
265
+ tx_hash: string;
266
+ address: string;
267
+ };
263
268
  };
264
269
  stripe?: {
265
270
  payment_intent_id?: string;
@@ -434,7 +439,6 @@ export type EventType = LiteralUnion<
434
439
  | 'customer.subscription.trial_end'
435
440
  | 'customer.subscription.started'
436
441
  | 'customer.subscription.updated'
437
- | 'customer.subscription.canceled'
438
442
  | 'customer.tax_id.created'
439
443
  | 'customer.tax_id.deleted'
440
444
  | 'customer.tax_id.updated'
@@ -6,6 +6,7 @@ import {
6
6
  getMaxRetryCount,
7
7
  getMinRetryMail,
8
8
  getSubscriptionCreateSetup,
9
+ getSubscriptionStakeSetup,
9
10
  shouldCancelSubscription,
10
11
  } from '../../src/libs/subscription';
11
12
 
@@ -165,7 +166,7 @@ describe('getSubscriptionCreateSetup', () => {
165
166
  expect(result.cycle.duration).toBe(24 * 60 * 60 * 1000);
166
167
  });
167
168
 
168
- it('should calculate trial period when trialInDays is provided', () => {
169
+ it('should calculate trial period when only trialInDays is provided', () => {
169
170
  const items = [
170
171
  {
171
172
  price: { type: 'recurring', currency_options: currencies, recurring: { interval: 'day', interval_count: '1' } },
@@ -182,7 +183,7 @@ describe('getSubscriptionCreateSetup', () => {
182
183
  );
183
184
  });
184
185
 
185
- it('should calculate trial period when trialEnds is provided', () => {
186
+ it('should trialEnds overwrite trialInDays', () => {
186
187
  const items = [
187
188
  {
188
189
  price: { type: 'recurring', currency_options: currencies, recurring: { interval: 'day', interval_count: '1' } },
@@ -199,7 +200,7 @@ describe('getSubscriptionCreateSetup', () => {
199
200
  );
200
201
  });
201
202
 
202
- it('should calculate trial period when trialEnds is provided', () => {
203
+ it('should calculate trial period when only trialEnds is provided', () => {
203
204
  const items = [
204
205
  {
205
206
  price: { type: 'recurring', currency_options: currencies, recurring: { interval: 'day', interval_count: '1' } },
@@ -305,3 +306,84 @@ describe('shouldCancelSubscription', () => {
305
306
  expect(result).toBe(false);
306
307
  });
307
308
  });
309
+
310
+ describe('getSubscriptionStakeSetup', () => {
311
+ const items: any[] = [
312
+ {
313
+ price: {
314
+ type: 'one_time',
315
+ currency_options: [
316
+ {
317
+ currency_id: 'usd',
318
+ unit_amount: '1',
319
+ },
320
+ ],
321
+ },
322
+ quantity: 1,
323
+ },
324
+ {
325
+ price: {
326
+ type: 'recurring',
327
+ currency_options: [
328
+ {
329
+ currency_id: 'usd',
330
+ unit_amount: '1',
331
+ },
332
+ ],
333
+ recurring: {
334
+ interval: 'day',
335
+ interval_count: '1',
336
+ usage_type: 'licensed',
337
+ },
338
+ },
339
+ quantity: 2,
340
+ },
341
+ {
342
+ price: {
343
+ type: 'recurring',
344
+ currency_options: [
345
+ {
346
+ currency_id: 'usd',
347
+ unit_amount: '1',
348
+ },
349
+ ],
350
+ recurring: {
351
+ interval: 'day',
352
+ interval_count: '1',
353
+ usage_type: 'metered',
354
+ },
355
+ },
356
+ quantity: 2,
357
+ },
358
+ ];
359
+
360
+ it('should calculate staking for recurring licensed price type #1', () => {
361
+ const result = getSubscriptionStakeSetup(items, 'usd');
362
+ expect(result.licensed.toString()).toBe('2');
363
+ expect(result.metered.toString()).toBe('2');
364
+ });
365
+
366
+ it('should calculate staking for recurring licensed price type #2', () => {
367
+ const result = getSubscriptionStakeSetup(items.slice(0, 2), 'usd');
368
+ expect(result.licensed.toString()).toBe('2');
369
+ expect(result.metered.toString()).toBe('0');
370
+ });
371
+
372
+ it('should calculate staking for recurring metered price type when billingThreshold is 0 #1', () => {
373
+ const result = getSubscriptionStakeSetup(items.slice(2, 3), 'usd', '10');
374
+ expect(result.licensed.toString()).toBe('0');
375
+ expect(result.metered.toString()).toBe('10');
376
+ });
377
+
378
+ it('should calculate staking for recurring metered price type when billingThreshold is 0 #2', () => {
379
+ const result = getSubscriptionStakeSetup(items, 'usd', '0');
380
+ expect(result.licensed.toString()).toBe('2');
381
+ expect(result.metered.toString()).toBe('2');
382
+ });
383
+
384
+ it('should calculate staking for recurring metered price type when billingThreshold is greater than 0', () => {
385
+ const result = getSubscriptionStakeSetup(items, 'usd', '10');
386
+ expect(result.licensed.toString()).toBe('2');
387
+ expect(result.metered.toString()).toBe('10');
388
+ });
389
+ });
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.13.174
17
+ version: 1.13.176
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.13.174",
3
+ "version": "1.13.176",
4
4
  "scripts": {
5
5
  "dev": "cross-env COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -50,7 +50,7 @@
50
50
  "@arcblock/jwt": "^1.18.110",
51
51
  "@arcblock/ux": "^2.9.39",
52
52
  "@blocklet/logger": "1.16.23",
53
- "@blocklet/payment-react": "1.13.174",
53
+ "@blocklet/payment-react": "1.13.176",
54
54
  "@blocklet/sdk": "1.16.23",
55
55
  "@blocklet/ui-react": "^2.9.39",
56
56
  "@blocklet/uploader": "^0.0.74",
@@ -110,7 +110,7 @@
110
110
  "devDependencies": {
111
111
  "@abtnode/types": "1.16.23",
112
112
  "@arcblock/eslint-config-ts": "^0.2.4",
113
- "@blocklet/payment-types": "1.13.174",
113
+ "@blocklet/payment-types": "1.13.176",
114
114
  "@types/cookie-parser": "^1.4.6",
115
115
  "@types/cors": "^2.8.17",
116
116
  "@types/dotenv-flow": "^3.3.3",
@@ -149,5 +149,5 @@
149
149
  "parser": "typescript"
150
150
  }
151
151
  },
152
- "gitHead": "6aa0d712bf8f36a3592fd564f14fd9ca05b5c7a9"
152
+ "gitHead": "d39e02f65d5f7a168b3ee3d589b47c4d77c53125"
153
153
  }