payment-kit 1.18.29 → 1.18.31
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/crons/index.ts +8 -0
- package/api/src/crons/metering-subscription-detection.ts +9 -0
- package/api/src/integrations/arcblock/nft.ts +1 -0
- package/api/src/integrations/blocklet/passport.ts +1 -1
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/setup-intent.ts +29 -1
- package/api/src/integrations/stripe/handlers/subscription.ts +19 -15
- package/api/src/integrations/stripe/resource.ts +81 -1
- package/api/src/libs/audit.ts +42 -0
- package/api/src/libs/constants.ts +2 -0
- package/api/src/libs/env.ts +2 -2
- package/api/src/libs/invoice.ts +54 -7
- package/api/src/libs/notification/index.ts +72 -4
- package/api/src/libs/notification/template/base.ts +2 -0
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -5
- package/api/src/libs/notification/template/subscription-renewed.ts +1 -5
- package/api/src/libs/notification/template/subscription-succeeded.ts +8 -18
- package/api/src/libs/notification/template/subscription-trial-start.ts +2 -10
- package/api/src/libs/notification/template/subscription-upgraded.ts +1 -5
- package/api/src/libs/payment.ts +48 -8
- package/api/src/libs/product.ts +1 -4
- package/api/src/libs/session.ts +600 -8
- package/api/src/libs/setting.ts +172 -0
- package/api/src/libs/subscription.ts +7 -69
- package/api/src/libs/ws.ts +5 -0
- package/api/src/queues/checkout-session.ts +42 -36
- package/api/src/queues/notification.ts +3 -2
- package/api/src/queues/payment.ts +56 -8
- package/api/src/queues/usage-record.ts +2 -10
- package/api/src/routes/checkout-sessions.ts +324 -187
- package/api/src/routes/connect/shared.ts +160 -38
- package/api/src/routes/connect/subscribe.ts +123 -64
- package/api/src/routes/payment-currencies.ts +11 -0
- package/api/src/routes/payment-links.ts +11 -1
- package/api/src/routes/payment-stats.ts +2 -2
- package/api/src/routes/payouts.ts +2 -1
- package/api/src/routes/settings.ts +45 -0
- package/api/src/routes/subscriptions.ts +1 -2
- package/api/src/store/migrations/20250408-subscription-grouping.ts +39 -0
- package/api/src/store/migrations/20250419-subscription-grouping.ts +69 -0
- package/api/src/store/models/checkout-session.ts +52 -0
- package/api/src/store/models/index.ts +1 -0
- package/api/src/store/models/payment-link.ts +6 -0
- package/api/src/store/models/subscription.ts +8 -6
- package/api/src/store/models/types.ts +32 -1
- package/api/tests/libs/session.spec.ts +423 -0
- package/api/tests/libs/subscription.spec.ts +0 -110
- package/blocklet.yml +3 -1
- package/package.json +25 -24
- package/scripts/sdk.js +486 -155
- package/src/locales/en.tsx +4 -0
- package/src/locales/zh.tsx +3 -0
- package/src/pages/admin/settings/vault-config/edit-form.tsx +58 -3
- package/src/pages/admin/settings/vault-config/index.tsx +35 -1
- package/src/pages/customer/subscription/change-payment.tsx +8 -3
- package/src/pages/integrations/overview.tsx +1 -1
|
@@ -7,23 +7,30 @@ import { BN, fromTokenToUnit, toBase58 } from '@ocap/util';
|
|
|
7
7
|
import { fromPublicKey } from '@ocap/wallet';
|
|
8
8
|
import type { Request } from 'express';
|
|
9
9
|
import { isEmpty } from 'lodash';
|
|
10
|
-
|
|
10
|
+
import dayjs from '../../libs/dayjs';
|
|
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
14
|
import logger from '../../libs/logger';
|
|
15
15
|
import { getGasPayerExtra, getTokenLimitsForDelegation } from '../../libs/payment';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
getCheckoutAmount,
|
|
18
|
+
getCheckoutSessionSubscriptionIds,
|
|
19
|
+
getFastCheckoutAmount,
|
|
20
|
+
getStatementDescriptor,
|
|
21
|
+
getSubscriptionCreateSetup,
|
|
22
|
+
isDonationCheckoutSession,
|
|
23
|
+
getSubscriptionLineItems
|
|
24
|
+
} from '../../libs/session';
|
|
17
25
|
import {
|
|
18
26
|
expandSubscriptionItems,
|
|
19
|
-
getSubscriptionCreateSetup,
|
|
20
27
|
getSubscriptionPaymentAddress,
|
|
21
28
|
getSubscriptionStakeSetup,
|
|
22
29
|
} from '../../libs/subscription';
|
|
23
30
|
import { getCustomerStakeAddress, OCAP_PAYMENT_TX_TYPE } from '../../libs/util';
|
|
24
31
|
|
|
25
32
|
import { invoiceQueue } from '../../queues/invoice';
|
|
26
|
-
import type
|
|
33
|
+
import { type TLineItemExpanded } from '../../store/models';
|
|
27
34
|
import { CheckoutSession } from '../../store/models/checkout-session';
|
|
28
35
|
import { Customer } from '../../store/models/customer';
|
|
29
36
|
import { Invoice, TInvoice } from '../../store/models/invoice';
|
|
@@ -42,6 +49,7 @@ type Result = {
|
|
|
42
49
|
customer: Customer;
|
|
43
50
|
paymentIntent?: PaymentIntent;
|
|
44
51
|
subscription?: Subscription;
|
|
52
|
+
subscriptions?: Subscription[];
|
|
45
53
|
paymentCurrency: PaymentCurrency;
|
|
46
54
|
paymentMethod: PaymentMethod;
|
|
47
55
|
invoice?: Invoice;
|
|
@@ -85,20 +93,32 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
|
|
|
85
93
|
paymentMethodId = paymentIntent.payment_method_id;
|
|
86
94
|
}
|
|
87
95
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
const subscriptionIds = getCheckoutSessionSubscriptionIds(checkoutSession);
|
|
97
|
+
|
|
98
|
+
let subscriptions: Subscription[] = [];
|
|
99
|
+
let primarySubscription: Subscription | null = null;
|
|
100
|
+
if (subscriptionIds.length > 0) {
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
subscriptions = await Promise.all(
|
|
103
|
+
subscriptionIds.filter(Boolean)
|
|
104
|
+
.map(id => Subscription.findByPk(id))
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
subscriptions = subscriptions.filter(Boolean);
|
|
108
|
+
for (const subscription of subscriptions) {
|
|
109
|
+
if (subscription && subscription.status !== 'incomplete') {
|
|
110
|
+
throw new Error(`Subscription ${subscription.id} is not in incomplete status: ${subscription.status}`);
|
|
111
|
+
}
|
|
93
112
|
}
|
|
94
|
-
if (
|
|
95
|
-
|
|
113
|
+
if (subscriptions.length > 0) {
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
primarySubscription = subscriptions.find((sub) => sub.metadata?.is_primary_subscription) || subscriptions[0];
|
|
116
|
+
paymentCurrencyId = primarySubscription?.currency_id;
|
|
117
|
+
paymentMethodId = primarySubscription?.default_payment_method_id;
|
|
96
118
|
}
|
|
97
|
-
|
|
98
|
-
paymentCurrencyId = subscription.currency_id;
|
|
99
|
-
paymentMethodId = subscription.default_payment_method_id;
|
|
100
119
|
}
|
|
101
120
|
let customer = null;
|
|
121
|
+
|
|
102
122
|
if (!skipCustomer) {
|
|
103
123
|
// 检查是否为打赏场景
|
|
104
124
|
const isDonation = isDonationCheckoutSession(checkoutSession);
|
|
@@ -195,7 +215,8 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
|
|
|
195
215
|
checkoutSession,
|
|
196
216
|
paymentIntent,
|
|
197
217
|
customer: customer as Customer,
|
|
198
|
-
subscription,
|
|
218
|
+
subscription: primarySubscription as Subscription,
|
|
219
|
+
subscriptions,
|
|
199
220
|
paymentMethod,
|
|
200
221
|
paymentCurrency,
|
|
201
222
|
};
|
|
@@ -345,6 +366,8 @@ type Args = {
|
|
|
345
366
|
paymentIntent?: PaymentIntent;
|
|
346
367
|
subscription?: Subscription;
|
|
347
368
|
props?: Partial<TInvoice>;
|
|
369
|
+
subscriptions?: Subscription[];
|
|
370
|
+
lineItems?: TLineItemExpanded[];
|
|
348
371
|
};
|
|
349
372
|
|
|
350
373
|
export async function ensureInvoiceForCheckout({
|
|
@@ -353,27 +376,32 @@ export async function ensureInvoiceForCheckout({
|
|
|
353
376
|
paymentIntent,
|
|
354
377
|
subscription,
|
|
355
378
|
props,
|
|
379
|
+
subscriptions,
|
|
380
|
+
lineItems,
|
|
356
381
|
}: Args): Promise<{ invoice: Invoice | null; items: InvoiceItem[] }> {
|
|
382
|
+
|
|
383
|
+
const isGroupInvoice = subscriptions && subscriptions.length > 1;
|
|
357
384
|
// invoices are optional when checkout session is in payment mode
|
|
358
385
|
if (checkoutSession.mode === 'payment' && !checkoutSession.invoice_creation?.enabled) {
|
|
359
386
|
logger.warn('Invoice creation disabled for payment mode');
|
|
360
387
|
return { invoice: null, items: [] };
|
|
361
388
|
}
|
|
362
389
|
|
|
390
|
+
const existingInvoice = checkoutSession.invoice_id || (isGroupInvoice && subscription?.latest_invoice_id);
|
|
363
391
|
// Do not create invoice if it's already created
|
|
364
|
-
if (
|
|
365
|
-
logger.info(`Invoice already created for checkout session ${checkoutSession.id}: ${
|
|
366
|
-
const invoice = await Invoice.findByPk(
|
|
392
|
+
if (existingInvoice) {
|
|
393
|
+
logger.info(`Invoice already created for checkout session ${checkoutSession.id}: ${existingInvoice}`);
|
|
394
|
+
const invoice = await Invoice.findByPk(existingInvoice);
|
|
367
395
|
if (invoice) {
|
|
368
396
|
if (invoice.status === 'paid') {
|
|
369
|
-
logger.info(`Invoice already paid for checkout session ${checkoutSession.id}: ${
|
|
397
|
+
logger.info(`Invoice already paid for checkout session ${checkoutSession.id}: ${existingInvoice}`);
|
|
370
398
|
throw new Error('Invoice already paid');
|
|
371
399
|
}
|
|
372
400
|
|
|
373
401
|
// invoice currency is aligned
|
|
374
402
|
if (invoice.currency_id === checkoutSession.currency_id) {
|
|
375
403
|
await invoice.update({ status: 'open' });
|
|
376
|
-
logger.info(`Invoice status reset for checkout session ${checkoutSession.id}: ${
|
|
404
|
+
logger.info(`Invoice status reset for checkout session ${checkoutSession.id}: ${existingInvoice}`);
|
|
377
405
|
|
|
378
406
|
if (invoice.payment_intent_id) {
|
|
379
407
|
await PaymentIntent.update({ status: 'requires_capture' }, { where: { id: invoice.payment_intent_id } });
|
|
@@ -384,13 +412,13 @@ export async function ensureInvoiceForCheckout({
|
|
|
384
412
|
|
|
385
413
|
return {
|
|
386
414
|
invoice,
|
|
387
|
-
items: await InvoiceItem.findAll({ where: { invoice_id:
|
|
415
|
+
items: await InvoiceItem.findAll({ where: { invoice_id: existingInvoice } }),
|
|
388
416
|
};
|
|
389
417
|
}
|
|
390
418
|
|
|
391
419
|
// invalid currency not aligned: we should generate new invoice
|
|
392
420
|
await invoice.update({ status: 'void' });
|
|
393
|
-
logger.info(`Invoice marked void for checkout session ${checkoutSession.id}: ${
|
|
421
|
+
logger.info(`Invoice marked void for checkout session ${checkoutSession.id}: ${existingInvoice}`);
|
|
394
422
|
const method = await PaymentMethod.findByPk(invoice.default_payment_method_id);
|
|
395
423
|
if (method?.type === 'stripe' && invoice.metadata?.stripe_id) {
|
|
396
424
|
const client = method.getStripeClient();
|
|
@@ -398,12 +426,12 @@ export async function ensureInvoiceForCheckout({
|
|
|
398
426
|
.voidInvoice(invoice.metadata.stripe_id)
|
|
399
427
|
.then(() => {
|
|
400
428
|
logger.info(
|
|
401
|
-
`Invoice marked void on stripe for checkout session ${checkoutSession.id}: ${
|
|
429
|
+
`Invoice marked void on stripe for checkout session ${checkoutSession.id}: ${existingInvoice}`
|
|
402
430
|
);
|
|
403
431
|
})
|
|
404
432
|
.catch((err) => {
|
|
405
433
|
logger.error(
|
|
406
|
-
`Invoice marked void on stripe failed for checkout session ${checkoutSession.id}: ${
|
|
434
|
+
`Invoice marked void on stripe failed for checkout session ${checkoutSession.id}: ${existingInvoice}`,
|
|
407
435
|
err
|
|
408
436
|
);
|
|
409
437
|
});
|
|
@@ -412,11 +440,26 @@ export async function ensureInvoiceForCheckout({
|
|
|
412
440
|
}
|
|
413
441
|
|
|
414
442
|
const currency = await PaymentCurrency.findByPk(checkoutSession.currency_id);
|
|
443
|
+
|
|
444
|
+
const metadata = {
|
|
445
|
+
...(checkoutSession.invoice_creation?.invoice_data?.metadata || {}),
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
if (isGroupInvoice) {
|
|
449
|
+
metadata.subscription_ids = subscriptions.map(sub => sub.id);
|
|
450
|
+
metadata.is_group_invoice = true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const trialInDays = Number(checkoutSession.subscription_data?.trial_period_days || 0);
|
|
454
|
+
const trialEnd = Number(checkoutSession.subscription_data?.trial_end || 0);
|
|
455
|
+
const now = dayjs().unix();
|
|
456
|
+
const invoiceItems = lineItems || (await Price.expand(checkoutSession.line_items, { product: true }));
|
|
457
|
+
const totalAmount = getCheckoutAmount(invoiceItems, checkoutSession.currency_id, trialInDays > 0 || trialEnd > now);
|
|
415
458
|
const { invoice, items } = await ensureInvoiceAndItems({
|
|
416
459
|
customer,
|
|
417
460
|
currency: currency as PaymentCurrency,
|
|
418
461
|
subscription,
|
|
419
|
-
lineItems:
|
|
462
|
+
lineItems: invoiceItems,
|
|
420
463
|
trialing: !!checkoutSession.subscription_data?.trial_period_days,
|
|
421
464
|
metered: false,
|
|
422
465
|
props: {
|
|
@@ -436,21 +479,23 @@ export async function ensureInvoiceForCheckout({
|
|
|
436
479
|
payment_intent_id: paymentIntent?.id,
|
|
437
480
|
checkout_session_id: checkoutSession.id,
|
|
438
481
|
|
|
439
|
-
total:
|
|
482
|
+
total: totalAmount.subtotal,
|
|
440
483
|
|
|
441
484
|
default_payment_method_id: (subscription?.default_payment_method_id ||
|
|
442
485
|
paymentIntent?.payment_method_id) as string,
|
|
443
486
|
|
|
444
487
|
custom_fields: checkoutSession.invoice_creation?.invoice_data?.custom_fields || [],
|
|
445
488
|
footer: checkoutSession.invoice_creation?.invoice_data?.footer || '',
|
|
446
|
-
metadata
|
|
489
|
+
metadata,
|
|
447
490
|
...(props || {}),
|
|
448
491
|
} as Invoice,
|
|
449
492
|
});
|
|
450
493
|
logger.info('Invoice created for checkoutSession', { checkoutSessionId: checkoutSession.id, invoiceId: invoice.id });
|
|
451
494
|
|
|
452
|
-
//
|
|
453
|
-
|
|
495
|
+
// only update invoice_id for single invoice
|
|
496
|
+
if (!isGroupInvoice) {
|
|
497
|
+
await checkoutSession.update({ invoice_id: invoice.id });
|
|
498
|
+
}
|
|
454
499
|
if (paymentIntent) {
|
|
455
500
|
await paymentIntent.update({ invoice_id: invoice.id });
|
|
456
501
|
}
|
|
@@ -461,6 +506,46 @@ export async function ensureInvoiceForCheckout({
|
|
|
461
506
|
return { invoice, items };
|
|
462
507
|
}
|
|
463
508
|
|
|
509
|
+
|
|
510
|
+
export async function ensureInvoicesForSubscriptions({
|
|
511
|
+
checkoutSession,
|
|
512
|
+
customer,
|
|
513
|
+
subscriptions,
|
|
514
|
+
}: Omit<Args, 'subscription'> & { subscriptions: Subscription[] }): Promise<{
|
|
515
|
+
invoices: Invoice[];
|
|
516
|
+
}> {
|
|
517
|
+
if (!subscriptions?.length) {
|
|
518
|
+
logger.warn('No subscriptions provided for invoice creation');
|
|
519
|
+
return { invoices: [] };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const lineItems = await Price.expand(checkoutSession.line_items, { product: true });
|
|
523
|
+
|
|
524
|
+
const primarySubscription = (subscriptions.find((x) => x.metadata.is_primary_subscription) || subscriptions[0]) as Subscription;
|
|
525
|
+
const invoices = await Promise.all(
|
|
526
|
+
subscriptions.map(async (subscription) => {
|
|
527
|
+
const subItems = await getSubscriptionLineItems(subscription, lineItems, primarySubscription);
|
|
528
|
+
const { invoice } = await ensureInvoiceForCheckout({
|
|
529
|
+
checkoutSession,
|
|
530
|
+
customer,
|
|
531
|
+
subscription,
|
|
532
|
+
subscriptions,
|
|
533
|
+
lineItems: subItems
|
|
534
|
+
});
|
|
535
|
+
return invoice;
|
|
536
|
+
})
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const createdInvoices = invoices.filter(Boolean);
|
|
540
|
+
|
|
541
|
+
logger.info(`Created ${createdInvoices.length} invoices for subscriptions`, {
|
|
542
|
+
checkoutSessionId: checkoutSession.id,
|
|
543
|
+
invoiceIds: createdInvoices.map(inv => inv?.id)
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
return { invoices: createdInvoices as Invoice[] };
|
|
547
|
+
}
|
|
548
|
+
|
|
464
549
|
export async function ensureInvoiceForCollect(invoiceId: string) {
|
|
465
550
|
const invoice = await Invoice.findByPk(invoiceId);
|
|
466
551
|
if (!invoice) {
|
|
@@ -701,10 +786,11 @@ export async function getDelegationTxClaim({
|
|
|
701
786
|
export async function getStakeTxClaim({
|
|
702
787
|
userDid,
|
|
703
788
|
userPk,
|
|
704
|
-
items,
|
|
705
789
|
subscription,
|
|
790
|
+
items,
|
|
706
791
|
paymentCurrency,
|
|
707
792
|
paymentMethod,
|
|
793
|
+
subscriptions,
|
|
708
794
|
}: {
|
|
709
795
|
userDid: string;
|
|
710
796
|
userPk: string;
|
|
@@ -712,15 +798,33 @@ export async function getStakeTxClaim({
|
|
|
712
798
|
items: TLineItemExpanded[];
|
|
713
799
|
paymentCurrency: PaymentCurrency;
|
|
714
800
|
paymentMethod: PaymentMethod;
|
|
801
|
+
subscriptions?: Subscription[];
|
|
715
802
|
}) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
803
|
+
let billingThreshold = Number(subscription.billing_thresholds?.amount_gte || 0);
|
|
804
|
+
let minStakeAmount = Number(subscription.billing_thresholds?.stake_gte || 0);
|
|
805
|
+
|
|
806
|
+
const hasGrouping = subscriptions && subscriptions.length > 1;
|
|
807
|
+
if (hasGrouping) {
|
|
808
|
+
const primarySubscription = subscriptions[0] as Subscription;
|
|
809
|
+
// use the settings of the primary subscription, not the scattered staking
|
|
810
|
+
billingThreshold = Number(primarySubscription.billing_thresholds?.amount_gte || 0);
|
|
811
|
+
minStakeAmount = Number(primarySubscription.billing_thresholds?.stake_gte || 0);
|
|
812
|
+
|
|
813
|
+
logger.info('Using primary subscription for staking', {
|
|
814
|
+
primarySubscriptionId: primarySubscription.id,
|
|
815
|
+
billingThreshold,
|
|
816
|
+
minStakeAmount,
|
|
817
|
+
allSubscriptionIds: subscriptions.map(s => s.id)
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
|
|
719
821
|
const threshold = fromTokenToUnit(Math.max(billingThreshold, minStakeAmount), paymentCurrency.decimal);
|
|
720
822
|
const staking = getSubscriptionStakeSetup(items, paymentCurrency.id, threshold.toString());
|
|
721
823
|
const amount = staking.licensed.add(staking.metered).toString();
|
|
824
|
+
|
|
722
825
|
logger.info('getStakeTxClaim', {
|
|
723
826
|
subscriptionId: subscription.id,
|
|
827
|
+
allSubscriptions: subscriptions?.map(s => s.id) || [],
|
|
724
828
|
billingThreshold,
|
|
725
829
|
minStakeAmount,
|
|
726
830
|
threshold: threshold.toString(),
|
|
@@ -731,7 +835,12 @@ export async function getStakeTxClaim({
|
|
|
731
835
|
if (paymentMethod.type === 'arcblock') {
|
|
732
836
|
// create staking data
|
|
733
837
|
const client = paymentMethod.getOcapClient();
|
|
734
|
-
|
|
838
|
+
|
|
839
|
+
const stakeId = hasGrouping
|
|
840
|
+
? `stake-group-${subscription.id}`
|
|
841
|
+
: subscription.id;
|
|
842
|
+
|
|
843
|
+
const address = await getCustomerStakeAddress(userDid, stakeId);
|
|
735
844
|
const { state } = await client.getStakeState({ address });
|
|
736
845
|
const data = {
|
|
737
846
|
type: 'json',
|
|
@@ -739,6 +848,10 @@ export async function getStakeTxClaim({
|
|
|
739
848
|
{
|
|
740
849
|
appId: wallet.address,
|
|
741
850
|
subscriptionId: subscription.id,
|
|
851
|
+
...(hasGrouping ? {
|
|
852
|
+
subscriptionGroup: true,
|
|
853
|
+
subscriptionIds: subscriptions.map(s => s.id)
|
|
854
|
+
} : {})
|
|
742
855
|
},
|
|
743
856
|
JSON.parse(state?.data?.value || '{}')
|
|
744
857
|
),
|
|
@@ -748,7 +861,9 @@ export async function getStakeTxClaim({
|
|
|
748
861
|
|
|
749
862
|
return {
|
|
750
863
|
type: 'StakeTx',
|
|
751
|
-
description:
|
|
864
|
+
description: hasGrouping
|
|
865
|
+
? `Stake for ${subscriptions.length} subscriptions`
|
|
866
|
+
: `Stake for subscription ${subscription.id}`,
|
|
752
867
|
partialTx: {
|
|
753
868
|
from: userDid,
|
|
754
869
|
pk: userPk,
|
|
@@ -757,8 +872,10 @@ export async function getStakeTxClaim({
|
|
|
757
872
|
receiver: wallet.address,
|
|
758
873
|
slashers: [wallet.address],
|
|
759
874
|
revokeWaitingPeriod: setup.cycle.duration / 1000, // wait for at least 1 billing cycle
|
|
760
|
-
message:
|
|
761
|
-
|
|
875
|
+
message: hasGrouping
|
|
876
|
+
? `Stake for ${subscriptions.length} subscriptions`
|
|
877
|
+
: `Stake for subscription ${subscription.id}`,
|
|
878
|
+
nonce: stakeId,
|
|
762
879
|
inputs: [],
|
|
763
880
|
data,
|
|
764
881
|
},
|
|
@@ -767,10 +884,15 @@ export async function getStakeTxClaim({
|
|
|
767
884
|
requirement: {
|
|
768
885
|
tokens: [{ address: paymentCurrency.contract as string, value: amount }],
|
|
769
886
|
},
|
|
770
|
-
nonce: `stake-${
|
|
887
|
+
nonce: `stake-${stakeId}`,
|
|
771
888
|
meta: {
|
|
772
889
|
purpose: 'staking',
|
|
773
890
|
address,
|
|
891
|
+
...(hasGrouping ? {
|
|
892
|
+
subscriptionGroup: true,
|
|
893
|
+
primarySubscriptionId: subscription.id,
|
|
894
|
+
allSubscriptionIds: subscriptions.map(s => s.id)
|
|
895
|
+
} : {})
|
|
774
896
|
},
|
|
775
897
|
chainInfo: {
|
|
776
898
|
host: paymentMethod.settings?.arcblock?.api_host as string,
|