payment-kit 1.16.17 → 1.16.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 (70) hide show
  1. package/api/src/crons/index.ts +1 -1
  2. package/api/src/hooks/pre-start.ts +2 -0
  3. package/api/src/index.ts +2 -0
  4. package/api/src/integrations/arcblock/stake.ts +7 -1
  5. package/api/src/integrations/stripe/resource.ts +1 -1
  6. package/api/src/libs/env.ts +12 -0
  7. package/api/src/libs/event.ts +8 -0
  8. package/api/src/libs/invoice.ts +585 -3
  9. package/api/src/libs/notification/template/subscription-succeeded.ts +1 -2
  10. package/api/src/libs/notification/template/subscription-trial-will-end.ts +2 -2
  11. package/api/src/libs/notification/template/subscription-will-renew.ts +6 -2
  12. package/api/src/libs/notification/template/subscription.overdraft-protection.exhausted.ts +139 -0
  13. package/api/src/libs/overdraft-protection.ts +86 -0
  14. package/api/src/libs/payment.ts +1 -65
  15. package/api/src/libs/queue/index.ts +0 -1
  16. package/api/src/libs/subscription.ts +532 -2
  17. package/api/src/libs/util.ts +4 -0
  18. package/api/src/locales/en.ts +5 -0
  19. package/api/src/locales/zh.ts +5 -0
  20. package/api/src/queues/event.ts +3 -2
  21. package/api/src/queues/invoice.ts +28 -3
  22. package/api/src/queues/notification.ts +25 -3
  23. package/api/src/queues/payment.ts +154 -3
  24. package/api/src/queues/refund.ts +2 -2
  25. package/api/src/queues/subscription.ts +215 -4
  26. package/api/src/queues/webhook.ts +1 -0
  27. package/api/src/routes/connect/change-payment.ts +1 -1
  28. package/api/src/routes/connect/change-plan.ts +1 -1
  29. package/api/src/routes/connect/overdraft-protection.ts +120 -0
  30. package/api/src/routes/connect/recharge.ts +2 -1
  31. package/api/src/routes/connect/setup.ts +1 -1
  32. package/api/src/routes/connect/shared.ts +117 -350
  33. package/api/src/routes/connect/subscribe.ts +1 -1
  34. package/api/src/routes/customers.ts +2 -2
  35. package/api/src/routes/invoices.ts +9 -4
  36. package/api/src/routes/subscriptions.ts +172 -2
  37. package/api/src/store/migrate.ts +9 -10
  38. package/api/src/store/migrations/20240905-index.ts +95 -60
  39. package/api/src/store/migrations/20241203-overdraft-protection.ts +25 -0
  40. package/api/src/store/migrations/20241216-update-overdraft-protection.ts +30 -0
  41. package/api/src/store/models/customer.ts +2 -2
  42. package/api/src/store/models/invoice.ts +7 -0
  43. package/api/src/store/models/lock.ts +7 -0
  44. package/api/src/store/models/subscription.ts +15 -0
  45. package/api/src/store/sequelize.ts +6 -1
  46. package/blocklet.yml +1 -1
  47. package/package.json +23 -23
  48. package/src/components/customer/overdraft-protection.tsx +367 -0
  49. package/src/components/event/list.tsx +3 -4
  50. package/src/components/product/edit-price.tsx +2 -2
  51. package/src/components/subscription/actions/cancel.tsx +3 -0
  52. package/src/components/subscription/portal/actions.tsx +324 -77
  53. package/src/components/uploader.tsx +31 -26
  54. package/src/env.d.ts +1 -0
  55. package/src/hooks/subscription.ts +30 -0
  56. package/src/libs/env.ts +4 -0
  57. package/src/locales/en.tsx +41 -0
  58. package/src/locales/zh.tsx +37 -0
  59. package/src/pages/admin/billing/invoices/detail.tsx +16 -15
  60. package/src/pages/admin/index.tsx +3 -1
  61. package/src/pages/admin/products/prices/detail.tsx +1 -1
  62. package/src/pages/admin/products/products/detail.tsx +6 -2
  63. package/src/pages/customer/index.tsx +7 -2
  64. package/src/pages/customer/invoice/detail.tsx +29 -5
  65. package/src/pages/customer/invoice/past-due.tsx +18 -4
  66. package/src/pages/customer/recharge.tsx +2 -4
  67. package/src/pages/customer/subscription/change-payment.tsx +7 -1
  68. package/src/pages/customer/subscription/detail.tsx +69 -51
  69. package/tsconfig.json +0 -5
  70. package/api/tests/libs/payment.spec.ts +0 -168
@@ -0,0 +1,120 @@
1
+ import type { CallbackArgs } from '../../libs/auth';
2
+ import { isSubscriptionOverdraftProtectionEnabled } from '../../libs/subscription';
3
+ import { Lock } from '../../store/models';
4
+ import {
5
+ ensureSubscriptionForOverdraftProtection,
6
+ executeOcapTransactions,
7
+ getAuthPrincipalClaim,
8
+ getOverdraftProtectionStakeTxClaim,
9
+ } from './shared';
10
+ import { ensureStakeInvoice } from '../../libs/invoice';
11
+
12
+ export default {
13
+ action: 'overdraft-protection',
14
+ authPrincipal: false,
15
+ claims: {
16
+ authPrincipal: async ({ extraParams }: CallbackArgs) => {
17
+ const { paymentMethod } = await ensureSubscriptionForOverdraftProtection(
18
+ extraParams.subscriptionId,
19
+ extraParams.amount
20
+ );
21
+ return getAuthPrincipalClaim(paymentMethod, 'continue');
22
+ },
23
+ },
24
+ onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
25
+ const { subscriptionId } = extraParams;
26
+ const { subscription, paymentMethod, paymentCurrency, stakeAmount } =
27
+ await ensureSubscriptionForOverdraftProtection(subscriptionId, extraParams.amount);
28
+ const { remaining } = await isSubscriptionOverdraftProtectionEnabled(subscription);
29
+
30
+ const payerAddress = subscription.overdraft_protection?.payment_details?.arcblock?.payer;
31
+ if (userDid !== payerAddress && remaining !== '0') {
32
+ // if previous account has remaining overdraft protection, we don't allow to use new account to pay for it
33
+ throw new Error(
34
+ `You are not the payer for this subscription. Expected payer: ${payerAddress}, but found: ${userDid}.`
35
+ );
36
+ }
37
+
38
+ const claims: { [type: string]: [string, object] } = {};
39
+
40
+ if (paymentMethod.type === 'arcblock') {
41
+ // we always need to stake for the subscription
42
+ claims.staking = [
43
+ 'prepareTx',
44
+ await getOverdraftProtectionStakeTxClaim({
45
+ userDid,
46
+ userPk,
47
+ paymentCurrency,
48
+ paymentMethod,
49
+ stakeAmount,
50
+ subscription,
51
+ }),
52
+ ];
53
+
54
+ return claims;
55
+ }
56
+
57
+ throw new Error(`OverdraftProtection: Payment method ${paymentMethod.type} not supported`);
58
+ },
59
+
60
+ onAuth: async ({ request, userDid, userPk, claims, extraParams }: CallbackArgs) => {
61
+ const { subscriptionId, amount } = extraParams;
62
+ const { subscription, paymentCurrency, paymentMethod, customer } = await ensureSubscriptionForOverdraftProtection(
63
+ subscriptionId,
64
+ amount
65
+ );
66
+
67
+ const afterTxExecution = async (paymentDetails: any) => {
68
+ await subscription?.update({
69
+ overdraft_protection: {
70
+ payment_details: {
71
+ ...(subscription.overdraft_protection?.payment_details || {}),
72
+ [paymentMethod.type]: paymentDetails,
73
+ },
74
+ enabled: subscription.overdraft_protection?.enabled || false,
75
+ payment_method_id: paymentMethod.id,
76
+ },
77
+ });
78
+
79
+ // release the exhausted lock, so that the notification can be sent again if overdraft protection exhausted
80
+ await Lock.release(`${subscription.id}-${paymentCurrency.id}-overdraft-protection-exhausted`);
81
+ };
82
+
83
+ if (paymentMethod.type === 'arcblock') {
84
+ const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
85
+ userDid,
86
+ userPk,
87
+ claims,
88
+ paymentMethod,
89
+ request,
90
+ subscription?.id,
91
+ paymentCurrency.contract,
92
+ `overdraft-protection-${subscription.id}`
93
+ );
94
+ await ensureStakeInvoice(
95
+ {
96
+ total: stakingAmount,
97
+ description: 'Stake for subscription overdraft protection',
98
+ currency_id: paymentCurrency.id,
99
+ billing_reason: 'stake_overdraft_protection',
100
+ metadata: {
101
+ payment_details: {
102
+ arcblock: {
103
+ tx_hash: paymentDetails?.staking?.tx_hash,
104
+ payer: paymentDetails?.payer,
105
+ address: paymentDetails?.staking?.address,
106
+ },
107
+ },
108
+ },
109
+ },
110
+ subscription!,
111
+ paymentMethod,
112
+ customer!
113
+ );
114
+ await afterTxExecution(paymentDetails);
115
+ return { hash: paymentDetails.tx_hash };
116
+ }
117
+
118
+ throw new Error(`OverdraftProtection: Payment method ${paymentMethod.type} not supported`);
119
+ },
120
+ };
@@ -6,8 +6,9 @@ import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/e
6
6
  import type { CallbackArgs } from '../../libs/auth';
7
7
  import { getGasPayerExtra } from '../../libs/payment';
8
8
  import { getTxMetadata } from '../../libs/util';
9
- import { ensureRechargeInvoice, ensureSubscriptionRecharge, getAuthPrincipalClaim } from './shared';
9
+ import { ensureSubscriptionRecharge, getAuthPrincipalClaim } from './shared';
10
10
  import logger from '../../libs/logger';
11
+ import { ensureRechargeInvoice } from '../../libs/invoice';
11
12
 
12
13
  export default {
13
14
  action: 'recharge',
@@ -11,12 +11,12 @@ import { addSubscriptionJob } from '../../queues/subscription';
11
11
  import type { TLineItemExpanded } from '../../store/models';
12
12
  import {
13
13
  ensureSetupIntent,
14
- ensureStakeInvoice,
15
14
  executeOcapTransactions,
16
15
  getAuthPrincipalClaim,
17
16
  getDelegationTxClaim,
18
17
  getStakeTxClaim,
19
18
  } from './shared';
19
+ import { ensureStakeInvoice } from '../../libs/invoice';
20
20
 
21
21
  export default {
22
22
  action: 'setup',
@@ -11,18 +11,17 @@ import isEmpty from 'lodash/isEmpty';
11
11
  import { estimateMaxGasForTx, hasStakedForGas } from '../../integrations/arcblock/stake';
12
12
  import { encodeApproveItx } from '../../integrations/ethereum/token';
13
13
  import { blocklet, ethWallet, wallet } from '../../libs/auth';
14
- import dayjs from '../../libs/dayjs';
15
14
  import logger from '../../libs/logger';
16
15
  import { getGasPayerExtra, getTokenLimitsForDelegation } from '../../libs/payment';
17
- import { getFastCheckoutAmount, getPriceUintAmountByCurrency, getStatementDescriptor } from '../../libs/session';
16
+ import { getFastCheckoutAmount, getStatementDescriptor } from '../../libs/session';
18
17
  import {
19
18
  expandSubscriptionItems,
20
19
  getSubscriptionCreateSetup,
21
- getSubscriptionItemPrice,
22
20
  getSubscriptionPaymentAddress,
23
21
  getSubscriptionStakeSetup,
24
22
  } from '../../libs/subscription';
25
23
  import { getCustomerStakeAddress, OCAP_PAYMENT_TX_TYPE } from '../../libs/util';
24
+
26
25
  import { invoiceQueue } from '../../queues/invoice';
27
26
  import type { TLineItemExpanded } from '../../store/models';
28
27
  import { CheckoutSession } from '../../store/models/checkout-session';
@@ -35,7 +34,7 @@ import { PaymentMethod } from '../../store/models/payment-method';
35
34
  import { Price } from '../../store/models/price';
36
35
  import { SetupIntent } from '../../store/models/setup-intent';
37
36
  import { Subscription } from '../../store/models/subscription';
38
- import { SubscriptionItem } from '../../store/models/subscription-item';
37
+ import { ensureInvoiceAndItems } from '../../libs/invoice';
39
38
 
40
39
  type Result = {
41
40
  checkoutSession: CheckoutSession;
@@ -401,184 +400,6 @@ export async function ensureInvoiceForCheckout({
401
400
  return { invoice, items };
402
401
  }
403
402
 
404
- export async function ensureInvoiceAndItems({
405
- customer,
406
- currency,
407
- subscription,
408
- props,
409
- lineItems,
410
- trialing,
411
- metered,
412
- applyCredit = true,
413
- }: {
414
- customer: Customer;
415
- currency: PaymentCurrency;
416
- subscription?: Subscription;
417
- props: TInvoice;
418
- lineItems: TLineItemExpanded[];
419
- trialing: boolean; // do we have trialing
420
- metered: boolean; // is the quantity metered
421
- applyCredit?: boolean; // should we apply customer credit?
422
- }): Promise<{ invoice: Invoice; items: InvoiceItem[] }> {
423
- // apply possible balance to invoice
424
- let remaining = props.total;
425
- let result = { starting: {}, ending: {} };
426
- if (applyCredit && props.total > '0') {
427
- const balance = customer.getBalanceToApply(currency.id, props.total);
428
- result = await customer.decreaseTokenBalance(currency.id, balance);
429
- remaining = new BN(props.total).sub(new BN(balance)).toString();
430
- logger.info('Invoice will use customer credit', { result, remaining, total: props.total });
431
- }
432
-
433
- const invoice = await Invoice.create({
434
- livemode: props.livemode,
435
- number: await customer.getInvoiceNumber(),
436
- description: props.description,
437
- statement_descriptor: props.statement_descriptor,
438
- period_start: props.period_start,
439
- period_end: props.period_end,
440
-
441
- auto_advance: props.auto_advance,
442
- paid: false,
443
- paid_out_of_band: false,
444
-
445
- status: props.status || 'open',
446
- collection_method: 'charge_automatically',
447
- billing_reason: props.billing_reason,
448
-
449
- currency_id: props.currency_id,
450
- customer_id: customer.id,
451
- payment_intent_id: props.payment_intent_id || '',
452
- subscription_id: subscription?.id,
453
- checkout_session_id: props.checkout_session_id || '',
454
-
455
- total: props.total || '0',
456
- subtotal: props.total || '0',
457
- tax: '0',
458
- subtotal_excluding_tax: props.total || '0',
459
-
460
- amount_due: props.amount_due || remaining,
461
- amount_paid: props.amount_paid || '0',
462
- amount_remaining: props.amount_remaining || remaining,
463
- amount_shipping: '0',
464
-
465
- starting_balance: '0',
466
- ending_balance: '0',
467
- starting_token_balance: result.starting,
468
- ending_token_balance: result.ending,
469
-
470
- attempt_count: 0,
471
- attempted: false,
472
- // next_payment_attempt: undefined,
473
-
474
- custom_fields: props.custom_fields || [],
475
- customer_address: customer.address,
476
- customer_email: customer.email,
477
- customer_name: customer.name,
478
- customer_phone: customer.phone,
479
-
480
- discounts: [],
481
- total_discount_amounts: [],
482
-
483
- due_date: undefined, // The date on which payment for this invoice is due
484
- effective_at: dayjs().unix(), // The date when this invoice is in effect
485
- status_transitions: {
486
- finalized_at: dayjs().unix(),
487
- },
488
-
489
- payment_settings: subscription?.payment_settings,
490
- default_payment_method_id: props.default_payment_method_id,
491
-
492
- account_country: '',
493
- account_name: '',
494
- footer: props.footer || '',
495
- metadata: props.metadata || {},
496
- });
497
-
498
- // create invoice items: for those require payment this time
499
- const subscriptionItems = subscription
500
- ? await SubscriptionItem.findAll({ where: { subscription_id: subscription?.id } })
501
- : [];
502
-
503
- const getLineSetup = (x: TLineItemExpanded) => {
504
- const price = getSubscriptionItemPrice(x);
505
- if (price.type === 'recurring' && trialing) {
506
- return {
507
- price,
508
- amount: '0',
509
- // @ts-ignore
510
- description: trialing ? `${price.product.name} (trialing)` : price.product.name,
511
- period: {
512
- start: props.period_start,
513
- end: props.period_end,
514
- },
515
- };
516
- }
517
-
518
- return {
519
- price,
520
- amount:
521
- x.custom_amount ||
522
- new BN(getPriceUintAmountByCurrency(price, props.currency_id)).mul(new BN(x.quantity)).toString(),
523
- // @ts-ignore
524
- description: price.product.name,
525
- period: undefined,
526
- };
527
- };
528
-
529
- const items = await Promise.all(
530
- lineItems.map((x: TLineItemExpanded) => {
531
- const setup = getLineSetup(x);
532
- const { price } = setup;
533
- let { quantity } = x;
534
- if (price.type === 'recurring') {
535
- if (price.recurring?.usage_type === 'metered' && !metered) {
536
- quantity = 0;
537
- }
538
- if (trialing) {
539
- quantity = 0;
540
- }
541
- }
542
-
543
- return InvoiceItem.create({
544
- livemode: !!props.livemode,
545
- amount: quantity > 0 ? setup.amount : '0',
546
- quantity,
547
- description: setup.description,
548
- period: setup.period,
549
- currency_id: props.currency_id,
550
- customer_id: customer.id,
551
- price_id: price.id,
552
- invoice_id: invoice.id,
553
- subscription_id: subscription?.id,
554
- subscription_item_id: subscriptionItems.find((si) => si.price_id === price.id)?.id,
555
- discountable: false,
556
- discounts: [],
557
- discount_amounts: [],
558
- proration: false,
559
- proration_details: {},
560
- metadata: x.metadata || {},
561
- });
562
- })
563
- );
564
-
565
- return { invoice, items };
566
- }
567
-
568
- export async function cleanupInvoiceAndItems(invoiceId: string) {
569
- const invoice = await Invoice.findByPk(invoiceId);
570
- if (!invoice) {
571
- return;
572
- }
573
- if (invoice.isImmutable()) {
574
- return;
575
- }
576
-
577
- const removedItem = await InvoiceItem.destroy({ where: { invoice_id: invoiceId } });
578
- const removedInvoice = await Invoice.destroy({ where: { id: invoiceId } });
579
- logger.info('cleanup invoice and items', { invoiceId, removedItem, removedInvoice });
580
- }
581
-
582
403
  export async function ensureInvoiceForCollect(invoiceId: string) {
583
404
  const invoice = await Invoice.findByPk(invoiceId);
584
405
  if (!invoice) {
@@ -717,6 +538,7 @@ export async function getDelegationTxClaim({
717
538
  paymentMethod,
718
539
  paymentCurrency,
719
540
  });
541
+
720
542
  if (mode === 'delegation') {
721
543
  tokenRequirements = [];
722
544
  }
@@ -863,6 +685,82 @@ export async function getStakeTxClaim({
863
685
  throw new Error(`getStakeTxClaim: Payment method ${paymentMethod.type} not supported`);
864
686
  }
865
687
 
688
+ export async function getOverdraftProtectionStakeTxClaim({
689
+ userDid,
690
+ userPk,
691
+ subscription,
692
+ paymentCurrency,
693
+ paymentMethod,
694
+ stakeAmount,
695
+ }: {
696
+ userDid: string;
697
+ userPk: string;
698
+ subscription: Subscription;
699
+ paymentCurrency: PaymentCurrency;
700
+ paymentMethod: PaymentMethod;
701
+ stakeAmount: string;
702
+ }) {
703
+ // create staking amount
704
+ logger.info('getStakeTxClaim', {
705
+ subscriptionId: subscription.id,
706
+ paymentCurrencyId: paymentCurrency.id,
707
+ paymentMethodId: paymentMethod.id,
708
+ stakeAmount,
709
+ });
710
+
711
+ if (paymentMethod.type === 'arcblock') {
712
+ // create staking data
713
+ const client = paymentMethod.getOcapClient();
714
+ const nonce = `overdraft-protection-${subscription.id}`;
715
+ const address = await getCustomerStakeAddress(userDid, nonce);
716
+ const { state } = await client.getStakeState({ address });
717
+ const data = {
718
+ type: 'json',
719
+ value: Object.assign(
720
+ {
721
+ appId: wallet.address,
722
+ subscriptionId: subscription.id,
723
+ },
724
+ JSON.parse(state?.data?.value || '{}')
725
+ ),
726
+ };
727
+
728
+ return {
729
+ type: 'StakeTx',
730
+ description: `Stake to complete subscription ${subscription.id} overdraft protection`,
731
+ partialTx: {
732
+ from: userDid,
733
+ pk: userPk,
734
+ itx: {
735
+ address,
736
+ receiver: wallet.address,
737
+ slashers: [wallet.address],
738
+ revokeWaitingPeriod: 0,
739
+ message: `Stake for subscription ${subscription.id} overdraft protection`,
740
+ nonce,
741
+ inputs: [],
742
+ data,
743
+ },
744
+ signatures: [],
745
+ },
746
+ requirement: {
747
+ tokens: [{ address: paymentCurrency.contract as string, value: stakeAmount }],
748
+ },
749
+ nonce: `stake-${subscription.id}`,
750
+ meta: {
751
+ purpose: 'staking',
752
+ address,
753
+ },
754
+ chainInfo: {
755
+ host: paymentMethod.settings?.arcblock?.api_host as string,
756
+ id: paymentMethod.settings?.arcblock?.chain_id as string,
757
+ },
758
+ };
759
+ }
760
+
761
+ throw new Error(`getStakeTxClaim: Payment method ${paymentMethod.type} not supported`);
762
+ }
763
+
866
764
  export type TokenRequirementArgs = {
867
765
  items: TLineItemExpanded[];
868
766
  mode: string;
@@ -1045,6 +943,40 @@ export async function ensureSubscriptionForCollectBatch(subscriptionId: string,
1045
943
  };
1046
944
  }
1047
945
 
946
+ export async function ensureSubscriptionForOverdraftProtection(subscriptionId: string, amount: string) {
947
+ const subscription = await Subscription.findByPk(subscriptionId);
948
+ if (!subscription) {
949
+ throw new Error(`Subscription ${subscriptionId} not found when prepare overdraft protection`);
950
+ }
951
+
952
+ const paymentCurrency = await PaymentCurrency.findByPk(subscription.currency_id);
953
+ if (!paymentCurrency) {
954
+ throw new Error(`PaymentCurrency ${subscription.currency_id} not found when prepare overdraft protection`);
955
+ }
956
+
957
+ const paymentMethod = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
958
+
959
+ if (!paymentMethod) {
960
+ throw new Error(`Payment method not found for subscription ${subscriptionId}`);
961
+ }
962
+ if (paymentMethod.type !== 'arcblock') {
963
+ throw new Error(`Payment method ${paymentMethod.type} not supported for overdraft protection`);
964
+ }
965
+
966
+ const customer = await Customer.findByPk(subscription.customer_id);
967
+ if (!customer) {
968
+ throw new Error(`Customer not found for subscription ${subscriptionId}`);
969
+ }
970
+
971
+ return {
972
+ subscription,
973
+ paymentCurrency: paymentCurrency as PaymentCurrency,
974
+ paymentMethod: paymentMethod as PaymentMethod,
975
+ customer,
976
+ stakeAmount: fromTokenToUnit(amount, paymentCurrency.decimal).toString(),
977
+ };
978
+ }
979
+
1048
980
  export async function executeOcapTransactions(
1049
981
  userDid: string,
1050
982
  userPk: string,
@@ -1052,7 +984,8 @@ export async function executeOcapTransactions(
1052
984
  paymentMethod: PaymentMethod,
1053
985
  request: Request,
1054
986
  subscriptionId?: string,
1055
- paymentCurrencyContract?: string
987
+ paymentCurrencyContract?: string,
988
+ nonce?: string
1056
989
  ) {
1057
990
  const client = paymentMethod.getOcapClient();
1058
991
  const delegation = claims.find((x) => x.type === 'signature' && x.meta?.purpose === 'delegation');
@@ -1089,104 +1022,18 @@ export async function executeOcapTransactions(
1089
1022
  })
1090
1023
  );
1091
1024
 
1092
- const nonce = subscriptionId || '';
1093
-
1094
1025
  return {
1095
1026
  tx_hash: delegationTxHash,
1096
1027
  payer: userDid,
1097
1028
  type: 'delegate',
1098
1029
  staking: {
1099
1030
  tx_hash: stakingTxHash,
1100
- address: await getCustomerStakeAddress(userDid, nonce),
1031
+ address: await getCustomerStakeAddress(userDid, nonce || subscriptionId || ''),
1101
1032
  },
1102
1033
  stakingAmount,
1103
1034
  };
1104
1035
  }
1105
1036
 
1106
- export async function ensureStakeInvoice(
1107
- invoiceProps: { total: string; description?: string; checkout_session_id?: string; currency_id: string; metadata?: any; payment_settings?: any },
1108
- subscription: Subscription,
1109
- paymentMethod: PaymentMethod,
1110
- customer: Customer
1111
- ) {
1112
- if (paymentMethod.type !== 'arcblock') {
1113
- return;
1114
- }
1115
- try {
1116
- const stakingInvoice = await Invoice.create({
1117
- livemode: subscription.livemode,
1118
- number: await customer.getInvoiceNumber(),
1119
- description: invoiceProps?.description || 'Stake for subscription',
1120
- statement_descriptor: '',
1121
- period_start: dayjs().unix(),
1122
- period_end: subscription.current_period_end,
1123
-
1124
- auto_advance: false,
1125
- paid: true,
1126
- paid_out_of_band: false,
1127
-
1128
- status: 'paid',
1129
- collection_method: 'charge_automatically',
1130
- billing_reason: 'stake',
1131
-
1132
- currency_id: invoiceProps.currency_id,
1133
- customer_id: customer.id,
1134
- payment_intent_id: '',
1135
- subscription_id: subscription?.id,
1136
- checkout_session_id: invoiceProps?.checkout_session_id || '',
1137
-
1138
- total: invoiceProps.total || '0',
1139
- subtotal: invoiceProps.total || '0',
1140
- tax: '0',
1141
- subtotal_excluding_tax: invoiceProps.total || '0',
1142
-
1143
- amount_due: '0',
1144
- amount_paid: invoiceProps.total || '0',
1145
- amount_remaining: '0',
1146
- amount_shipping: '0',
1147
-
1148
- starting_balance: '0',
1149
- ending_balance: '0',
1150
- starting_token_balance: {},
1151
- ending_token_balance: {},
1152
-
1153
- attempt_count: 0,
1154
- attempted: false,
1155
- // next_payment_attempt: undefined,
1156
-
1157
- custom_fields: [],
1158
- customer_address: customer.address,
1159
- customer_email: customer.email,
1160
- customer_name: customer.name,
1161
- customer_phone: customer.phone,
1162
-
1163
- discounts: [],
1164
- total_discount_amounts: [],
1165
-
1166
- due_date: undefined,
1167
- effective_at: dayjs().unix(),
1168
- status_transitions: {
1169
- finalized_at: dayjs().unix(),
1170
- },
1171
-
1172
- payment_settings: invoiceProps?.payment_settings || subscription?.payment_settings,
1173
- default_payment_method_id: paymentMethod.id,
1174
-
1175
- account_country: '',
1176
- account_name: '',
1177
- metadata: invoiceProps.metadata || {},
1178
- });
1179
- logger.info('create staking invoice success', {
1180
- stakingInvoice,
1181
- subscriptionId: subscription?.id,
1182
- paymentMethod: paymentMethod.id,
1183
- customerId: customer.id,
1184
- });
1185
- } catch (error) {
1186
- logger.error('ensureStake: create invoice failed', { error, subscriptionId: subscription?.id, paymentMethod: paymentMethod.id, customerId: customer.id });
1187
- }
1188
- }
1189
-
1190
1037
 
1191
1038
  export async function updateStripeSubscriptionAfterChangePayment(setupIntent: SetupIntent, subscription: Subscription) {
1192
1039
  const { from_method: fromMethodId, to_method: toMethodId } = setupIntent.metadata || {};
@@ -1232,83 +1079,3 @@ export async function updateStripeSubscriptionAfterChangePayment(setupIntent: Se
1232
1079
  }
1233
1080
  }
1234
1081
 
1235
- export async function ensureRechargeInvoice(
1236
- invoiceProps: { total: string; description?: string; checkout_session_id?: string; currency_id: string; metadata?: any; payment_settings?: any },
1237
- subscription: Subscription,
1238
- paymentMethod: PaymentMethod,
1239
- customer: Customer
1240
- ) {
1241
- try {
1242
- const rechargeInvoice = await Invoice.create({
1243
- livemode: subscription.livemode,
1244
- number: await customer.getInvoiceNumber(),
1245
- description: invoiceProps?.description || 'Add funds for subscription',
1246
- statement_descriptor: '',
1247
- period_start: dayjs().unix(),
1248
- period_end: dayjs().unix(),
1249
-
1250
- auto_advance: false,
1251
- paid: true,
1252
- paid_out_of_band: false,
1253
-
1254
- status: 'paid',
1255
- collection_method: 'charge_automatically',
1256
- billing_reason: 'recharge',
1257
-
1258
- currency_id: invoiceProps.currency_id,
1259
- customer_id: customer.id,
1260
- payment_intent_id: '',
1261
- subscription_id: subscription?.id,
1262
- checkout_session_id: invoiceProps?.checkout_session_id || '',
1263
-
1264
- total: invoiceProps.total || '0',
1265
- subtotal: invoiceProps.total || '0',
1266
- tax: '0',
1267
- subtotal_excluding_tax: invoiceProps.total || '0',
1268
-
1269
- amount_due: '0',
1270
- amount_paid: invoiceProps.total || '0',
1271
- amount_remaining: '0',
1272
- amount_shipping: '0',
1273
-
1274
- starting_balance: '0',
1275
- ending_balance: '0',
1276
- starting_token_balance: {},
1277
- ending_token_balance: {},
1278
-
1279
- attempt_count: 0,
1280
- attempted: false,
1281
- // next_payment_attempt: undefined,
1282
-
1283
- custom_fields: [],
1284
- customer_address: customer.address,
1285
- customer_email: customer.email,
1286
- customer_name: customer.name,
1287
- customer_phone: customer.phone,
1288
-
1289
- discounts: [],
1290
- total_discount_amounts: [],
1291
-
1292
- due_date: undefined,
1293
- effective_at: dayjs().unix(),
1294
- status_transitions: {
1295
- finalized_at: dayjs().unix(),
1296
- },
1297
-
1298
- payment_settings: invoiceProps?.payment_settings || subscription?.payment_settings,
1299
- default_payment_method_id: paymentMethod.id,
1300
-
1301
- account_country: '',
1302
- account_name: '',
1303
- metadata: invoiceProps.metadata || {},
1304
- });
1305
- logger.info('create recharge invoice success', {
1306
- rechargeInvoice,
1307
- subscriptionId: subscription?.id,
1308
- paymentMethod: paymentMethod.id,
1309
- customerId: customer.id,
1310
- });
1311
- } catch (error) {
1312
- logger.error('ensureRechargeInvoice: create invoice failed', { error, subscriptionId: subscription?.id, paymentMethod: paymentMethod.id, customerId: customer.id });
1313
- }
1314
- }
@@ -12,12 +12,12 @@ import type { Invoice, TLineItemExpanded } from '../../store/models';
12
12
  import {
13
13
  ensureInvoiceForCheckout,
14
14
  ensurePaymentIntent,
15
- ensureStakeInvoice,
16
15
  executeOcapTransactions,
17
16
  getAuthPrincipalClaim,
18
17
  getDelegationTxClaim,
19
18
  getStakeTxClaim,
20
19
  } from './shared';
20
+ import { ensureStakeInvoice } from '../../libs/invoice';
21
21
 
22
22
  export default {
23
23
  action: 'subscription',