payment-kit 1.15.33 → 1.15.34
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.
- package/api/src/integrations/stripe/handlers/setup-intent.ts +3 -1
- package/api/src/integrations/stripe/handlers/subscription.ts +2 -8
- package/api/src/integrations/stripe/resource.ts +0 -11
- package/api/src/libs/invoice.ts +202 -1
- package/api/src/libs/notification/template/subscription-canceled.ts +11 -2
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -1
- package/api/src/libs/notification/template/subscription-renewed.ts +1 -1
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +9 -5
- package/api/src/libs/notification/template/subscription-will-canceled.ts +9 -5
- package/api/src/libs/notification/template/subscription-will-renew.ts +10 -12
- package/api/src/libs/payment.ts +3 -2
- package/api/src/libs/subscription.ts +33 -14
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +3 -1
- package/api/src/queues/refund.ts +9 -8
- package/api/src/queues/subscription.ts +109 -38
- package/api/src/routes/checkout-sessions.ts +20 -4
- package/api/src/routes/connect/change-payment.ts +51 -34
- package/api/src/routes/connect/change-plan.ts +25 -3
- package/api/src/routes/connect/setup.ts +27 -6
- package/api/src/routes/connect/shared.ts +135 -1
- package/api/src/routes/connect/subscribe.ts +25 -3
- package/api/src/routes/invoices.ts +23 -105
- package/api/src/routes/subscriptions.ts +66 -17
- package/api/src/store/models/invoice.ts +2 -1
- package/blocklet.yml +1 -1
- package/package.json +4 -4
- package/src/components/invoice/list.tsx +47 -24
- package/src/components/pricing-table/payment-settings.tsx +1 -1
- package/src/components/subscription/actions/cancel.tsx +10 -7
- package/src/components/subscription/metrics.tsx +1 -1
- package/src/pages/admin/billing/invoices/detail.tsx +15 -0
- package/src/pages/admin/billing/invoices/index.tsx +1 -1
- package/src/pages/admin/billing/subscriptions/detail.tsx +12 -4
- package/src/pages/admin/customers/customers/detail.tsx +1 -0
- package/src/pages/customer/index.tsx +1 -1
- package/src/pages/customer/invoice/detail.tsx +28 -14
- package/src/pages/customer/subscription/change-plan.tsx +8 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/src/pages/customer/subscription/embed.tsx +3 -1
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
shouldCancelSubscription,
|
|
24
24
|
} from '../libs/subscription';
|
|
25
25
|
import { ensureInvoiceAndItems } from '../routes/connect/shared';
|
|
26
|
-
import { PaymentCurrency, PaymentIntent, PaymentMethod, Refund, UsageRecord } from '../store/models';
|
|
26
|
+
import { PaymentCurrency, PaymentIntent, PaymentMethod, Refund, SetupIntent, UsageRecord } from '../store/models';
|
|
27
27
|
import { Customer } from '../store/models/customer';
|
|
28
28
|
import { Invoice } from '../store/models/invoice';
|
|
29
29
|
import { Price } from '../store/models/price';
|
|
@@ -325,24 +325,39 @@ const handleSubscriptionAfterRecover = async (subscription: Subscription) => {
|
|
|
325
325
|
};
|
|
326
326
|
|
|
327
327
|
const handleStakeSlashAfterCancel = async (subscription: Subscription) => {
|
|
328
|
-
const
|
|
329
|
-
if (!
|
|
328
|
+
const invoice = await Invoice.findByPk(subscription.latest_invoice_id);
|
|
329
|
+
if (!invoice || invoice.status !== 'uncollectible') {
|
|
330
|
+
logger.warn('Stake slashing aborted because invoice status', {
|
|
331
|
+
subscription: subscription.id,
|
|
332
|
+
invoice: invoice?.id,
|
|
333
|
+
status: invoice?.status,
|
|
334
|
+
});
|
|
330
335
|
return;
|
|
331
336
|
}
|
|
332
|
-
const currency = await PaymentCurrency.findByPk(subscription.currency_id);
|
|
337
|
+
const currency = await PaymentCurrency.findByPk(invoice.currency_id || subscription.currency_id);
|
|
333
338
|
if (!currency) {
|
|
339
|
+
logger.warn('Stake slashing aborted because currency not found', {
|
|
340
|
+
subscription: subscription.id,
|
|
341
|
+
invoice: invoice.id,
|
|
342
|
+
currency: invoice.currency_id || subscription.currency_id,
|
|
343
|
+
});
|
|
334
344
|
return;
|
|
335
345
|
}
|
|
336
|
-
const
|
|
337
|
-
if (!
|
|
346
|
+
const method = await PaymentMethod.findByPk(currency.payment_method_id);
|
|
347
|
+
if (!method || method.type !== 'arcblock') {
|
|
348
|
+
logger.warn('Stake slashing aborted because payment method not arcblock', {
|
|
349
|
+
subscription: subscription.id,
|
|
350
|
+
invoice: invoice.id,
|
|
351
|
+
method: method?.id,
|
|
352
|
+
});
|
|
338
353
|
return;
|
|
339
354
|
}
|
|
340
|
-
const
|
|
341
|
-
if (!
|
|
342
|
-
logger.warn('Stake slashing aborted because
|
|
355
|
+
const customer = await Customer.findByPk(subscription.customer_id);
|
|
356
|
+
if (!customer) {
|
|
357
|
+
logger.warn('Stake slashing aborted because customer not found', {
|
|
343
358
|
subscription: subscription.id,
|
|
344
|
-
invoice: invoice
|
|
345
|
-
|
|
359
|
+
invoice: invoice.id,
|
|
360
|
+
customer: subscription.customer_id,
|
|
346
361
|
});
|
|
347
362
|
return;
|
|
348
363
|
}
|
|
@@ -468,8 +483,18 @@ const handleStakeSlashAfterCancel = async (subscription: Subscription) => {
|
|
|
468
483
|
});
|
|
469
484
|
};
|
|
470
485
|
|
|
471
|
-
const ensureReturnStake = async (subscription: Subscription) => {
|
|
472
|
-
const
|
|
486
|
+
const ensureReturnStake = async (subscription: Subscription, paymentCurrencyId?: string, stakingAddress?: string) => {
|
|
487
|
+
const paymentCurrency = await PaymentCurrency.findByPk(paymentCurrencyId || subscription.currency_id);
|
|
488
|
+
if (!paymentCurrency) {
|
|
489
|
+
logger.warn('Stake return skipped because no payment currency', {
|
|
490
|
+
subscription: subscription.id,
|
|
491
|
+
currency: subscription.currency_id,
|
|
492
|
+
});
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const paymentMethod = await PaymentMethod.findByPk(
|
|
496
|
+
paymentCurrency.payment_method_id || subscription.default_payment_method_id
|
|
497
|
+
);
|
|
473
498
|
if (paymentMethod?.type !== 'arcblock') {
|
|
474
499
|
logger.warn('Stake return skipped because payment method not arcblock', {
|
|
475
500
|
subscription: subscription.id,
|
|
@@ -477,7 +502,7 @@ const ensureReturnStake = async (subscription: Subscription) => {
|
|
|
477
502
|
});
|
|
478
503
|
return;
|
|
479
504
|
}
|
|
480
|
-
const address = subscription?.payment_details?.arcblock?.staking?.address;
|
|
505
|
+
const address = stakingAddress || subscription?.payment_details?.arcblock?.staking?.address;
|
|
481
506
|
if (!address) {
|
|
482
507
|
logger.warn('Stake return skipped because no staking address', {
|
|
483
508
|
subscription: subscription.id,
|
|
@@ -486,21 +511,7 @@ const ensureReturnStake = async (subscription: Subscription) => {
|
|
|
486
511
|
return;
|
|
487
512
|
}
|
|
488
513
|
|
|
489
|
-
const
|
|
490
|
-
if (refunds.length > 0) {
|
|
491
|
-
logger.info(`Stake return skipped because subscription ${subscription.id} already has stake return records.`);
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const paymentCurrency = await PaymentCurrency.findByPk(subscription.currency_id);
|
|
495
|
-
if (!paymentCurrency) {
|
|
496
|
-
logger.warn('Stake return skipped because no payment currency', {
|
|
497
|
-
subscription: subscription.id,
|
|
498
|
-
currency: subscription.currency_id,
|
|
499
|
-
});
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const result = await getSubscriptionStakeReturnSetup(subscription, address, paymentMethod);
|
|
514
|
+
const result = await getSubscriptionStakeReturnSetup(subscription, address, paymentMethod, paymentCurrencyId);
|
|
504
515
|
|
|
505
516
|
const stakeEnough = await checkRemainingStake(paymentMethod, paymentCurrency, address, result.return_amount);
|
|
506
517
|
if (!stakeEnough.enough) {
|
|
@@ -513,17 +524,27 @@ const ensureReturnStake = async (subscription: Subscription) => {
|
|
|
513
524
|
return;
|
|
514
525
|
}
|
|
515
526
|
if (result.return_amount !== '0') {
|
|
527
|
+
const invoice = await Invoice.findOne({
|
|
528
|
+
where: {
|
|
529
|
+
billing_reason: 'stake',
|
|
530
|
+
subscription_id: subscription.id,
|
|
531
|
+
currency_id: paymentCurrency.id,
|
|
532
|
+
status: 'paid',
|
|
533
|
+
},
|
|
534
|
+
order: [['created_at', 'DESC']],
|
|
535
|
+
});
|
|
516
536
|
// do the stake return
|
|
517
537
|
const item = await Refund.create({
|
|
518
538
|
type: 'stake_return',
|
|
519
539
|
livemode: subscription.livemode,
|
|
520
540
|
amount: result.return_amount,
|
|
521
|
-
description: '
|
|
541
|
+
description: 'stake_return_for_subscription',
|
|
522
542
|
status: 'pending',
|
|
523
543
|
reason: 'requested_by_admin',
|
|
524
|
-
currency_id:
|
|
544
|
+
currency_id: paymentCurrency.id,
|
|
545
|
+
invoice_id: invoice?.id,
|
|
525
546
|
customer_id: subscription.customer_id,
|
|
526
|
-
payment_method_id:
|
|
547
|
+
payment_method_id: paymentMethod.id,
|
|
527
548
|
payment_intent_id: result?.lastInvoice?.payment_intent_id as string,
|
|
528
549
|
subscription_id: subscription.id,
|
|
529
550
|
attempt_count: 0,
|
|
@@ -538,6 +559,10 @@ const ensureReturnStake = async (subscription: Subscription) => {
|
|
|
538
559
|
// @ts-ignore
|
|
539
560
|
arcblock: {
|
|
540
561
|
receiver: result.sender,
|
|
562
|
+
staking: {
|
|
563
|
+
address,
|
|
564
|
+
tx_hash: '',
|
|
565
|
+
},
|
|
541
566
|
},
|
|
542
567
|
},
|
|
543
568
|
});
|
|
@@ -547,7 +572,7 @@ const ensureReturnStake = async (subscription: Subscription) => {
|
|
|
547
572
|
item: item.toJSON(),
|
|
548
573
|
});
|
|
549
574
|
} else {
|
|
550
|
-
logger.info('Skipped stake return for
|
|
575
|
+
logger.info('Skipped stake return for subscription', {
|
|
551
576
|
subscription: subscription.id,
|
|
552
577
|
return_amount: result.return_amount,
|
|
553
578
|
});
|
|
@@ -668,7 +693,15 @@ const ensureRefundOnCancel = async (subscription: Subscription) => {
|
|
|
668
693
|
});
|
|
669
694
|
return;
|
|
670
695
|
}
|
|
671
|
-
const
|
|
696
|
+
const lastInvoice = await Invoice.findByPk(subscription.latest_invoice_id);
|
|
697
|
+
if (!lastInvoice) {
|
|
698
|
+
logger.warn('Refund skipped because no latest invoice', {
|
|
699
|
+
subscription: subscription.id,
|
|
700
|
+
});
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const result = await getSubscriptionRefundSetup(subscription, subscription.cancel_at, lastInvoice.currency_id);
|
|
672
705
|
if (result.unused === '0') {
|
|
673
706
|
logger.warn('Refund skipped because unused amount is 0', {
|
|
674
707
|
subscription: subscription.id,
|
|
@@ -676,6 +709,7 @@ const ensureRefundOnCancel = async (subscription: Subscription) => {
|
|
|
676
709
|
});
|
|
677
710
|
return;
|
|
678
711
|
}
|
|
712
|
+
const currency = await PaymentCurrency.findByPk(lastInvoice?.currency_id);
|
|
679
713
|
const item = await Refund.create({
|
|
680
714
|
type: 'refund',
|
|
681
715
|
livemode: subscription.livemode,
|
|
@@ -683,9 +717,9 @@ const ensureRefundOnCancel = async (subscription: Subscription) => {
|
|
|
683
717
|
description: 'refund_transfer_on_subscription_cancel',
|
|
684
718
|
status: 'pending',
|
|
685
719
|
reason: 'requested_by_admin',
|
|
686
|
-
currency_id:
|
|
720
|
+
currency_id: result.lastInvoice?.currency_id,
|
|
687
721
|
customer_id: subscription.customer_id,
|
|
688
|
-
payment_method_id: subscription.default_payment_method_id,
|
|
722
|
+
payment_method_id: currency?.payment_method_id || subscription.default_payment_method_id,
|
|
689
723
|
payment_intent_id: result.lastInvoice.payment_intent_id as string,
|
|
690
724
|
invoice_id: result.lastInvoice.id,
|
|
691
725
|
subscription_id: subscription.id,
|
|
@@ -852,12 +886,12 @@ export const slashStakeQueue = createQueue({
|
|
|
852
886
|
export const returnStakeQueue = createQueue({
|
|
853
887
|
name: 'returnStake',
|
|
854
888
|
onJob: async (job) => {
|
|
855
|
-
const { subscriptionId } = job;
|
|
889
|
+
const { subscriptionId, stakingAddress, paymentCurrencyId } = job;
|
|
856
890
|
const subscription = await Subscription.findByPk(subscriptionId);
|
|
857
891
|
if (!subscription) {
|
|
858
892
|
return;
|
|
859
893
|
}
|
|
860
|
-
await ensureReturnStake(subscription);
|
|
894
|
+
await ensureReturnStake(subscription, paymentCurrencyId, stakingAddress);
|
|
861
895
|
},
|
|
862
896
|
options: {
|
|
863
897
|
concurrency: 1,
|
|
@@ -997,3 +1031,40 @@ events.on('customer.stake.revoked', async ({ subscriptionId, tx }: { subscriptio
|
|
|
997
1031
|
await new SubscriptionWillCanceledSchedule().reScheduleSubscriptionTasks([subscription]);
|
|
998
1032
|
await addSubscriptionJob(subscription, 'cancel', true, subscription.current_period_end);
|
|
999
1033
|
});
|
|
1034
|
+
|
|
1035
|
+
events.on('setup_intent.succeeded', async (setupIntent: SetupIntent) => {
|
|
1036
|
+
logger.info('setup intent succeeded', { setupIntent: setupIntent.id });
|
|
1037
|
+
if (setupIntent.metadata?.from_currency && setupIntent?.metadata?.subscription_id) {
|
|
1038
|
+
const subscription = await Subscription.findByPk(setupIntent.metadata.subscription_id);
|
|
1039
|
+
if (!subscription) {
|
|
1040
|
+
logger.info('skip return stake because no subscription found', { setupIntent: setupIntent.id });
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
const stakingInvoice = await Invoice.findOne({
|
|
1044
|
+
where: {
|
|
1045
|
+
subscription_id: subscription.id,
|
|
1046
|
+
billing_reason: 'stake',
|
|
1047
|
+
currency_id: setupIntent.metadata?.from_currency,
|
|
1048
|
+
status: 'paid',
|
|
1049
|
+
},
|
|
1050
|
+
order: [['created_at', 'DESC']],
|
|
1051
|
+
});
|
|
1052
|
+
logger.info('staking invoice', { stakingInvoice });
|
|
1053
|
+
if (stakingInvoice) {
|
|
1054
|
+
returnStakeQueue.push({
|
|
1055
|
+
id: `return-stake-${subscription.id}-${stakingInvoice.id}`,
|
|
1056
|
+
job: {
|
|
1057
|
+
subscriptionId: subscription.id,
|
|
1058
|
+
stakingAddress: stakingInvoice?.metadata?.payment_details?.arcblock?.address,
|
|
1059
|
+
paymentCurrencyId: setupIntent.metadata?.from_currency,
|
|
1060
|
+
},
|
|
1061
|
+
});
|
|
1062
|
+
logger.info('subscription return stake job scheduled', {
|
|
1063
|
+
jobId: `return-stake-${subscription.id}-${stakingInvoice.id}`,
|
|
1064
|
+
subscription: subscription.id,
|
|
1065
|
+
stakingAddress: stakingInvoice?.metadata?.payment_details?.arcblock?.address,
|
|
1066
|
+
paymentCurrencyId: setupIntent.metadata?.from_currency,
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
@@ -17,7 +17,11 @@ import { MetadataSchema } from '../libs/api';
|
|
|
17
17
|
import { checkPassportForPaymentLink } from '../integrations/blocklet/passport';
|
|
18
18
|
import { handleStripePaymentSucceed } from '../integrations/stripe/handlers/payment-intent';
|
|
19
19
|
import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ensureStripePaymentCustomer,
|
|
22
|
+
ensureStripePaymentIntent,
|
|
23
|
+
ensureStripeSubscription,
|
|
24
|
+
} from '../integrations/stripe/resource';
|
|
21
25
|
import dayjs from '../libs/dayjs';
|
|
22
26
|
import logger from '../libs/logger';
|
|
23
27
|
import { isCreditSufficientForPayment, isDelegationSufficientForPayment } from '../libs/payment';
|
|
@@ -141,11 +145,11 @@ const SubscriptionDataSchema = Joi.object({
|
|
|
141
145
|
.items(
|
|
142
146
|
Joi.object({
|
|
143
147
|
name: Joi.string().optional(),
|
|
144
|
-
color: Joi.string().
|
|
145
|
-
variant: Joi.string().
|
|
148
|
+
color: Joi.string().valid('primary', 'secondary', 'success', 'error', 'warning').optional(),
|
|
149
|
+
variant: Joi.string().valid('text', 'contained', 'outlined').optional(),
|
|
146
150
|
text: Joi.object().required(),
|
|
147
151
|
link: Joi.string().uri().required(),
|
|
148
|
-
type: Joi.string().
|
|
152
|
+
type: Joi.string().valid('notification', 'custom').optional(),
|
|
149
153
|
triggerEvents: Joi.array().items(Joi.string()).optional(),
|
|
150
154
|
})
|
|
151
155
|
)
|
|
@@ -1027,6 +1031,18 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1027
1031
|
trialInDays,
|
|
1028
1032
|
trialEnd
|
|
1029
1033
|
);
|
|
1034
|
+
const stripeCustomer = await ensureStripePaymentCustomer(subscription, paymentMethod);
|
|
1035
|
+
if (stripeSubscription) {
|
|
1036
|
+
await subscription.update({
|
|
1037
|
+
payment_details: {
|
|
1038
|
+
stripe: {
|
|
1039
|
+
customer_id: stripeCustomer.id,
|
|
1040
|
+
subscription_id: stripeSubscription.id,
|
|
1041
|
+
setup_intent_id: stripeSubscription.pending_setup_intent?.id,
|
|
1042
|
+
},
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1030
1046
|
logger.info('ensureStripeSubscription', {
|
|
1031
1047
|
subscriptionId: subscription.id,
|
|
1032
1048
|
stripeSubscriptionId: stripeSubscription?.id,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { executeEvmTransaction, waitForEvmTxConfirm } from '../../integrations/ethereum/tx';
|
|
2
2
|
import type { CallbackArgs } from '../../libs/auth';
|
|
3
|
-
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
4
|
-
import { getFastCheckoutAmount } from '../../libs/session';
|
|
5
3
|
import { getTxMetadata } from '../../libs/util';
|
|
6
|
-
import type
|
|
4
|
+
import { Lock, type TLineItemExpanded } from '../../store/models';
|
|
7
5
|
import {
|
|
8
6
|
ensureChangePaymentContext,
|
|
7
|
+
ensureStakeInvoice,
|
|
9
8
|
executeOcapTransactions,
|
|
10
9
|
getAuthPrincipalClaim,
|
|
11
10
|
getDelegationTxClaim,
|
|
12
11
|
getStakeTxClaim,
|
|
12
|
+
updateStripeSubscriptionAfterChangePayment,
|
|
13
13
|
} from './shared';
|
|
14
14
|
|
|
15
15
|
export default {
|
|
@@ -23,42 +23,31 @@ export default {
|
|
|
23
23
|
},
|
|
24
24
|
onConnect: async ({ userDid, userPk, extraParams }: CallbackArgs) => {
|
|
25
25
|
const { subscriptionId } = extraParams;
|
|
26
|
-
const { subscription,
|
|
26
|
+
const { subscription, paymentMethod, paymentCurrency } = await ensureChangePaymentContext(subscriptionId);
|
|
27
27
|
|
|
28
28
|
const claims: { [type: string]: [string, object] } = {};
|
|
29
29
|
|
|
30
30
|
// @ts-ignore
|
|
31
31
|
const items = subscription!.items as TLineItemExpanded[];
|
|
32
|
-
const trialing =
|
|
32
|
+
const trialing = true;
|
|
33
33
|
const billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
34
|
-
const fastCheckoutAmount = getFastCheckoutAmount(items, 'setup', paymentCurrency.id, trialing);
|
|
35
34
|
|
|
36
35
|
if (paymentMethod.type === 'arcblock') {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
nonce: `change-method-${subscription.id}`,
|
|
53
|
-
data: getTxMetadata({ subscriptionId: subscription.id }),
|
|
54
|
-
paymentCurrency,
|
|
55
|
-
paymentMethod,
|
|
56
|
-
trialing,
|
|
57
|
-
billingThreshold,
|
|
58
|
-
items,
|
|
59
|
-
}),
|
|
60
|
-
];
|
|
61
|
-
}
|
|
36
|
+
claims.delegation = [
|
|
37
|
+
'signature',
|
|
38
|
+
await getDelegationTxClaim({
|
|
39
|
+
mode: 'setup',
|
|
40
|
+
userDid,
|
|
41
|
+
userPk,
|
|
42
|
+
nonce: `change-method-${subscription.id}`,
|
|
43
|
+
data: getTxMetadata({ subscriptionId: subscription.id }),
|
|
44
|
+
paymentCurrency,
|
|
45
|
+
paymentMethod,
|
|
46
|
+
trialing,
|
|
47
|
+
billingThreshold,
|
|
48
|
+
items,
|
|
49
|
+
}),
|
|
50
|
+
];
|
|
62
51
|
|
|
63
52
|
// we always need to stake for the subscription
|
|
64
53
|
claims.staking = [
|
|
@@ -105,7 +94,7 @@ export default {
|
|
|
105
94
|
|
|
106
95
|
onAuth: async ({ request, userDid, userPk, claims, extraParams }: CallbackArgs) => {
|
|
107
96
|
const { subscriptionId } = extraParams;
|
|
108
|
-
const { subscription, setupIntent, paymentCurrency, paymentMethod } =
|
|
97
|
+
const { subscription, setupIntent, paymentCurrency, paymentMethod, customer } =
|
|
109
98
|
await ensureChangePaymentContext(subscriptionId);
|
|
110
99
|
|
|
111
100
|
const prepareTxExecution = async () => {
|
|
@@ -135,18 +124,46 @@ export default {
|
|
|
135
124
|
await subscription?.update({
|
|
136
125
|
currency_id: paymentCurrency.id,
|
|
137
126
|
default_payment_method_id: paymentMethod.id,
|
|
127
|
+
payment_details: {
|
|
128
|
+
...subscription.payment_details,
|
|
129
|
+
[paymentMethod.type]: paymentDetails,
|
|
130
|
+
},
|
|
138
131
|
});
|
|
132
|
+
|
|
133
|
+
await Lock.acquire(`${subscription.id}-change-plan`, subscription.current_period_end);
|
|
134
|
+
// update stripe subscription
|
|
135
|
+
await updateStripeSubscriptionAfterChangePayment(setupIntent, subscription);
|
|
139
136
|
};
|
|
140
137
|
|
|
141
138
|
if (paymentMethod.type === 'arcblock') {
|
|
142
139
|
await prepareTxExecution();
|
|
143
|
-
const paymentDetails = await executeOcapTransactions(
|
|
140
|
+
const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
|
|
144
141
|
userDid,
|
|
145
142
|
userPk,
|
|
146
143
|
claims,
|
|
147
144
|
paymentMethod,
|
|
148
145
|
request,
|
|
149
|
-
subscription?.id
|
|
146
|
+
subscription?.id,
|
|
147
|
+
paymentCurrency.contract
|
|
148
|
+
);
|
|
149
|
+
await ensureStakeInvoice(
|
|
150
|
+
{
|
|
151
|
+
total: stakingAmount,
|
|
152
|
+
description: 'Stake for subscription payment change',
|
|
153
|
+
currency_id: paymentCurrency.id,
|
|
154
|
+
metadata: {
|
|
155
|
+
payment_details: {
|
|
156
|
+
arcblock: {
|
|
157
|
+
tx_hash: paymentDetails?.staking?.tx_hash,
|
|
158
|
+
payer: paymentDetails?.payer,
|
|
159
|
+
address: paymentDetails?.staking?.address,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
subscription!,
|
|
165
|
+
paymentMethod,
|
|
166
|
+
customer!
|
|
150
167
|
);
|
|
151
168
|
await afterTxExecution(paymentDetails);
|
|
152
169
|
return { hash: paymentDetails.tx_hash };
|
|
@@ -8,6 +8,7 @@ import { invoiceQueue } from '../../queues/invoice';
|
|
|
8
8
|
import { addSubscriptionJob, subscriptionQueue } from '../../queues/subscription';
|
|
9
9
|
import type { TLineItemExpanded } from '../../store/models';
|
|
10
10
|
import {
|
|
11
|
+
ensureStakeInvoice,
|
|
11
12
|
ensureSubscription,
|
|
12
13
|
executeOcapTransactions,
|
|
13
14
|
getAuthPrincipalClaim,
|
|
@@ -107,7 +108,8 @@ export default {
|
|
|
107
108
|
|
|
108
109
|
onAuth: async ({ request, userDid, userPk, claims, extraParams }: CallbackArgs) => {
|
|
109
110
|
const { subscriptionId } = extraParams;
|
|
110
|
-
const { invoice, paymentMethod, subscription } =
|
|
111
|
+
const { invoice, paymentMethod, subscription, paymentCurrency, customer } =
|
|
112
|
+
await ensureSubscription(subscriptionId);
|
|
111
113
|
|
|
112
114
|
const prepareTxExecution = async () => {
|
|
113
115
|
await subscription?.update({
|
|
@@ -142,13 +144,33 @@ export default {
|
|
|
142
144
|
if (paymentMethod.type === 'arcblock') {
|
|
143
145
|
await prepareTxExecution();
|
|
144
146
|
|
|
145
|
-
const paymentDetails = await executeOcapTransactions(
|
|
147
|
+
const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
|
|
146
148
|
userDid,
|
|
147
149
|
userPk,
|
|
148
150
|
claims,
|
|
149
151
|
paymentMethod,
|
|
150
152
|
request,
|
|
151
|
-
subscription?.id
|
|
153
|
+
subscription?.id,
|
|
154
|
+
paymentCurrency?.contract
|
|
155
|
+
);
|
|
156
|
+
await ensureStakeInvoice(
|
|
157
|
+
{
|
|
158
|
+
total: stakingAmount,
|
|
159
|
+
description: 'Stake for subscription plan change',
|
|
160
|
+
currency_id: paymentCurrency.id,
|
|
161
|
+
metadata: {
|
|
162
|
+
payment_details: {
|
|
163
|
+
arcblock: {
|
|
164
|
+
tx_hash: paymentDetails?.staking?.tx_hash,
|
|
165
|
+
payer: paymentDetails?.payer,
|
|
166
|
+
address: paymentDetails?.staking?.address,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
subscription!,
|
|
172
|
+
paymentMethod,
|
|
173
|
+
customer!
|
|
152
174
|
);
|
|
153
175
|
await afterTxExecution(paymentDetails);
|
|
154
176
|
|
|
@@ -11,6 +11,7 @@ import { addSubscriptionJob } from '../../queues/subscription';
|
|
|
11
11
|
import type { TLineItemExpanded } from '../../store/models';
|
|
12
12
|
import {
|
|
13
13
|
ensureSetupIntent,
|
|
14
|
+
ensureStakeInvoice,
|
|
14
15
|
executeOcapTransactions,
|
|
15
16
|
getAuthPrincipalClaim,
|
|
16
17
|
getDelegationTxClaim,
|
|
@@ -122,10 +123,8 @@ export default {
|
|
|
122
123
|
onAuth: async (args: CallbackArgs) => {
|
|
123
124
|
const { request, userDid, userPk, claims, extraParams } = args;
|
|
124
125
|
const { checkoutSessionId, connectedDid } = extraParams;
|
|
125
|
-
const { setupIntent, checkoutSession, paymentMethod, subscription, invoice } =
|
|
126
|
-
checkoutSessionId,
|
|
127
|
-
connectedDid || userDid
|
|
128
|
-
);
|
|
126
|
+
const { setupIntent, checkoutSession, paymentMethod, subscription, invoice, paymentCurrency, customer } =
|
|
127
|
+
await ensureSetupIntent(checkoutSessionId, connectedDid || userDid);
|
|
129
128
|
|
|
130
129
|
if (!subscription) {
|
|
131
130
|
throw new Error('Subscription for checkoutSession not found');
|
|
@@ -174,13 +173,35 @@ export default {
|
|
|
174
173
|
try {
|
|
175
174
|
await prepareTxExecution();
|
|
176
175
|
|
|
177
|
-
const paymentDetails = await executeOcapTransactions(
|
|
176
|
+
const { stakingAmount, ...paymentDetails } = await executeOcapTransactions(
|
|
178
177
|
userDid,
|
|
179
178
|
userPk,
|
|
180
179
|
claims,
|
|
181
180
|
paymentMethod,
|
|
182
181
|
request,
|
|
183
|
-
subscription?.id
|
|
182
|
+
subscription?.id,
|
|
183
|
+
paymentCurrency?.contract
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await ensureStakeInvoice(
|
|
187
|
+
{
|
|
188
|
+
total: stakingAmount,
|
|
189
|
+
description: 'Stake for subscription',
|
|
190
|
+
checkout_session_id: checkoutSessionId,
|
|
191
|
+
currency_id: paymentCurrency.id,
|
|
192
|
+
metadata: {
|
|
193
|
+
payment_details: {
|
|
194
|
+
arcblock: {
|
|
195
|
+
tx_hash: paymentDetails?.staking?.tx_hash,
|
|
196
|
+
payer: paymentDetails?.payer,
|
|
197
|
+
address: paymentDetails?.staking?.address,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
subscription,
|
|
203
|
+
paymentMethod,
|
|
204
|
+
customer
|
|
184
205
|
);
|
|
185
206
|
await afterTxExecution(paymentDetails);
|
|
186
207
|
|