payment-kit 1.14.36 → 1.14.38
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/arcblock/stake.ts +1 -0
- package/api/src/integrations/stripe/resource.ts +3 -3
- package/api/src/libs/subscription.ts +53 -17
- package/api/src/libs/util.ts +12 -0
- package/api/src/queues/payment.ts +3 -3
- package/api/src/queues/refund.ts +5 -4
- package/api/src/queues/subscription.ts +4 -4
- package/api/src/routes/checkout-sessions.ts +15 -9
- package/api/src/routes/connect/setup.ts +8 -3
- package/api/src/routes/connect/shared.ts +4 -4
- package/api/src/routes/connect/subscribe.ts +6 -3
- package/api/src/routes/subscriptions.ts +1 -1
- package/api/src/store/models/types.ts +1 -0
- package/api/tests/libs/subscription.spec.ts +30 -6
- package/blocklet.yml +1 -2
- package/package.json +4 -4
|
@@ -189,6 +189,7 @@ export async function getStakeSummaryByDid(did: string, livemode: boolean = true
|
|
|
189
189
|
return {};
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// FIXME: should use listStakes to find all stakes and summarize here
|
|
192
193
|
const address = toStakeAddress(did, wallet.address);
|
|
193
194
|
const results: GroupedBN = {};
|
|
194
195
|
await Promise.all(
|
|
@@ -204,7 +204,7 @@ export async function ensureStripeSubscription(
|
|
|
204
204
|
currency: PaymentCurrency,
|
|
205
205
|
items: TLineItemExpanded[],
|
|
206
206
|
trialInDays: number = 0,
|
|
207
|
-
|
|
207
|
+
trialEnd: number = 0
|
|
208
208
|
) {
|
|
209
209
|
const client = method.getStripeClient();
|
|
210
210
|
|
|
@@ -255,8 +255,8 @@ export async function ensureStripeSubscription(
|
|
|
255
255
|
|
|
256
256
|
if (trialInDays) {
|
|
257
257
|
props.trial_period_days = trialInDays;
|
|
258
|
-
} else if (
|
|
259
|
-
props.trial_end =
|
|
258
|
+
} else if (trialEnd) {
|
|
259
|
+
props.trial_end = trialEnd;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
stripeSubscription = await client.subscriptions.create(props);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import component from '@blocklet/sdk/lib/component';
|
|
3
3
|
import { BN } from '@ocap/util';
|
|
4
4
|
import isEmpty from 'lodash/isEmpty';
|
|
5
|
+
import trim from 'lodash/trim';
|
|
5
6
|
import pick from 'lodash/pick';
|
|
6
7
|
import type { LiteralUnion } from 'type-fest';
|
|
7
8
|
import { withQuery } from 'ufo';
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
PriceRecurring,
|
|
19
20
|
Refund,
|
|
20
21
|
Subscription,
|
|
22
|
+
SubscriptionData,
|
|
21
23
|
SubscriptionItem,
|
|
22
24
|
SubscriptionUpdateItem,
|
|
23
25
|
TLineItemExpanded,
|
|
@@ -28,7 +30,7 @@ import dayjs from './dayjs';
|
|
|
28
30
|
import env from './env';
|
|
29
31
|
import logger from './logger';
|
|
30
32
|
import { getPriceCurrencyOptions, getPriceUintAmountByCurrency, getRecurringPeriod } from './session';
|
|
31
|
-
import { getConnectQueryParam } from './util';
|
|
33
|
+
import { getConnectQueryParam, getCustomerStakeAddress } from './util';
|
|
32
34
|
|
|
33
35
|
export function getCustomerSubscriptionPageUrl({
|
|
34
36
|
subscriptionId,
|
|
@@ -147,7 +149,7 @@ export function getSubscriptionCreateSetup(
|
|
|
147
149
|
items: TLineItemExpanded[],
|
|
148
150
|
currencyId: string,
|
|
149
151
|
trialInDays = 0,
|
|
150
|
-
|
|
152
|
+
trialEnd = 0
|
|
151
153
|
) {
|
|
152
154
|
let setup = new BN(0);
|
|
153
155
|
|
|
@@ -170,18 +172,18 @@ export function getSubscriptionCreateSetup(
|
|
|
170
172
|
const recurring = (item?.upsell_price || item?.price)?.recurring as PriceRecurring;
|
|
171
173
|
const cycle = getRecurringPeriod(recurring);
|
|
172
174
|
|
|
173
|
-
let
|
|
174
|
-
let
|
|
175
|
-
if (+
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
let trialStartAt = 0;
|
|
176
|
+
let trialEndAt = 0;
|
|
177
|
+
if (+trialEnd && trialEnd > now) {
|
|
178
|
+
trialStartAt = now;
|
|
179
|
+
trialEndAt = trialEnd;
|
|
178
180
|
} else if (trialInDays) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
trialStartAt = now;
|
|
182
|
+
trialEndAt = dayjs().add(trialInDays, 'day').unix();
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
const periodStart =
|
|
184
|
-
const periodEnd =
|
|
185
|
+
const periodStart = trialStartAt || now;
|
|
186
|
+
const periodEnd = trialEndAt || dayjs().add(cycle, 'millisecond').unix();
|
|
185
187
|
|
|
186
188
|
return {
|
|
187
189
|
recurring,
|
|
@@ -190,8 +192,8 @@ export function getSubscriptionCreateSetup(
|
|
|
190
192
|
anchor: periodEnd,
|
|
191
193
|
},
|
|
192
194
|
trial: {
|
|
193
|
-
start:
|
|
194
|
-
end:
|
|
195
|
+
start: trialStartAt,
|
|
196
|
+
end: trialEndAt,
|
|
195
197
|
},
|
|
196
198
|
period: {
|
|
197
199
|
start: periodStart,
|
|
@@ -698,16 +700,25 @@ export async function getSubscriptionRemainingStakeSetup(
|
|
|
698
700
|
paymentMethod: PaymentMethod,
|
|
699
701
|
action: 'return' | 'slash' = 'return'
|
|
700
702
|
) {
|
|
703
|
+
const currency = await PaymentCurrency.findByPk(subscription.currency_id);
|
|
704
|
+
if (!currency) {
|
|
705
|
+
return {
|
|
706
|
+
total: '0',
|
|
707
|
+
return_amount: '0',
|
|
708
|
+
sender: '',
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
701
712
|
const client = paymentMethod.getOcapClient();
|
|
702
713
|
const { state } = await client.getStakeState({ address });
|
|
703
|
-
|
|
704
|
-
if (!state.tokens || !currency) {
|
|
714
|
+
if (!state) {
|
|
705
715
|
return {
|
|
706
716
|
total: '0',
|
|
707
717
|
return_amount: '0',
|
|
708
718
|
sender: '',
|
|
709
719
|
};
|
|
710
720
|
}
|
|
721
|
+
|
|
711
722
|
let total = new BN(state.tokens.find((x: any) => x.address === currency.contract)?.value || '0');
|
|
712
723
|
if (action === 'slash') {
|
|
713
724
|
// add revoked tokens to total
|
|
@@ -761,17 +772,18 @@ export async function checkRemainingStake(
|
|
|
761
772
|
revoked: '0',
|
|
762
773
|
};
|
|
763
774
|
}
|
|
775
|
+
|
|
764
776
|
const client = paymentMethod.getOcapClient();
|
|
765
777
|
const { state } = await client.getStakeState({ address });
|
|
766
|
-
|
|
767
778
|
if (!state) {
|
|
768
|
-
logger.warn('getStakeState failed in checkRemainingStake', { address
|
|
779
|
+
logger.warn('getStakeState failed in checkRemainingStake', { address });
|
|
769
780
|
return {
|
|
770
781
|
enough: false,
|
|
771
782
|
staked: '0',
|
|
772
783
|
revoked: '0',
|
|
773
784
|
};
|
|
774
785
|
}
|
|
786
|
+
|
|
775
787
|
const staked = state.tokens?.find((x: any) => x.address === paymentCurrency.contract);
|
|
776
788
|
const revoked = state.revokedTokens?.find((x: any) => x.address === paymentCurrency.contract);
|
|
777
789
|
let total = new BN(0);
|
|
@@ -781,9 +793,33 @@ export async function checkRemainingStake(
|
|
|
781
793
|
if (revoked) {
|
|
782
794
|
total = total.add(new BN(revoked?.value || '0'));
|
|
783
795
|
}
|
|
796
|
+
|
|
784
797
|
return {
|
|
785
798
|
enough: total.gte(new BN(amount)),
|
|
786
799
|
staked,
|
|
787
800
|
revoked,
|
|
788
801
|
};
|
|
789
802
|
}
|
|
803
|
+
|
|
804
|
+
// trialing can be customized with currency_id list
|
|
805
|
+
export function getSubscriptionTrialSetup(data: Partial<SubscriptionData>, currencyId: string) {
|
|
806
|
+
let trialInDays = Number(data?.trial_period_days || 0);
|
|
807
|
+
let trialEnd = Number(data?.trial_end || 0);
|
|
808
|
+
const trialCurrencyIds = (data?.trial_currency || '').split(',').map(trim).filter(Boolean);
|
|
809
|
+
if (trialCurrencyIds.length > 0 && trialCurrencyIds.includes(currencyId) === false) {
|
|
810
|
+
trialEnd = 0;
|
|
811
|
+
trialInDays = 0;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return {
|
|
815
|
+
trialInDays,
|
|
816
|
+
trialEnd,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export async function getSubscriptionStakeAddress(subscription: Subscription, customerDid: string) {
|
|
821
|
+
return (
|
|
822
|
+
subscription.payment_details?.arcblock?.staking?.address ||
|
|
823
|
+
(await getCustomerStakeAddress(customerDid, subscription.id))
|
|
824
|
+
);
|
|
825
|
+
}
|
package/api/src/libs/util.ts
CHANGED
|
@@ -2,11 +2,14 @@ import crypto from 'crypto';
|
|
|
2
2
|
|
|
3
3
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
4
4
|
import env from '@blocklet/sdk/lib/env';
|
|
5
|
+
import { getWalletDid } from '@blocklet/sdk/lib/did';
|
|
6
|
+
import { toStakeAddress } from '@arcblock/did-util';
|
|
5
7
|
import { customAlphabet } from 'nanoid';
|
|
6
8
|
import type { LiteralUnion } from 'type-fest';
|
|
7
9
|
import { withQuery } from 'ufo';
|
|
8
10
|
|
|
9
11
|
import dayjs from './dayjs';
|
|
12
|
+
import { blocklet, wallet } from './auth';
|
|
10
13
|
|
|
11
14
|
export const OCAP_PAYMENT_TX_TYPE = 'fg:t:transfer_v2';
|
|
12
15
|
|
|
@@ -260,3 +263,12 @@ export function formatAmountPrecisionLimit(
|
|
|
260
263
|
}
|
|
261
264
|
return '';
|
|
262
265
|
}
|
|
266
|
+
|
|
267
|
+
export async function getCustomerStakeAddress(customerDid: string, nonce?: string) {
|
|
268
|
+
const { user } = await blocklet.getUser(customerDid, { enableConnectedAccount: true });
|
|
269
|
+
if (user) {
|
|
270
|
+
return toStakeAddress(getWalletDid(user), wallet.address, nonce);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return toStakeAddress(customerDid, wallet.address, nonce);
|
|
274
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import isEmpty from 'lodash/isEmpty';
|
|
2
2
|
|
|
3
|
-
import { toStakeAddress } from '@arcblock/did-util';
|
|
4
3
|
import { ensureStakedForGas } from '../integrations/arcblock/stake';
|
|
5
4
|
import { transferErc20FromUser } from '../integrations/ethereum/token';
|
|
6
5
|
import { createEvent } from '../libs/audit';
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
getMaxRetryCount,
|
|
20
19
|
getMinRetryMail,
|
|
21
20
|
getSubscriptionCreateSetup,
|
|
21
|
+
getSubscriptionStakeAddress,
|
|
22
22
|
shouldCancelSubscription,
|
|
23
23
|
} from '../libs/subscription';
|
|
24
24
|
import { MAX_RETRY_COUNT, MIN_RETRY_MAIL, getNextRetry } from '../libs/util';
|
|
@@ -402,7 +402,7 @@ const handleStakeSlash = async (
|
|
|
402
402
|
return;
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
-
const address =
|
|
405
|
+
const address = await getSubscriptionStakeAddress(subscription, customer.did);
|
|
406
406
|
const slashAmount = paymentIntent.amount;
|
|
407
407
|
const stakeEnough = await checkRemainingStake(paymentMethod, paymentCurrency, address, slashAmount);
|
|
408
408
|
if (!stakeEnough.enough) {
|
|
@@ -432,7 +432,7 @@ const handleStakeSlash = async (
|
|
|
432
432
|
const signed = await client.signSlashStakeTx({
|
|
433
433
|
tx: {
|
|
434
434
|
itx: {
|
|
435
|
-
address
|
|
435
|
+
address,
|
|
436
436
|
outputs: [{ owner: wallet.address, tokens: [{ address: paymentCurrency.contract, value: slashAmount }] }],
|
|
437
437
|
message: 'stake_slash_on_subscription_cancel',
|
|
438
438
|
data: {
|
package/api/src/queues/refund.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { checkRemainingStake } from '@api/libs/subscription';
|
|
1
|
+
import { isRefundReasonSupportedByStripe } from '../libs/refund';
|
|
2
|
+
import { checkRemainingStake, getSubscriptionStakeAddress } from '../libs/subscription';
|
|
4
3
|
import { sendErc20ToUser } from '../integrations/ethereum/token';
|
|
5
4
|
import { wallet } from '../libs/auth';
|
|
6
5
|
import CustomError from '../libs/error';
|
|
@@ -14,6 +13,7 @@ import { PaymentCurrency } from '../store/models/payment-currency';
|
|
|
14
13
|
import { PaymentIntent } from '../store/models/payment-intent';
|
|
15
14
|
import { PaymentMethod } from '../store/models/payment-method';
|
|
16
15
|
import { Refund } from '../store/models/refund';
|
|
16
|
+
import { Subscription } from '../store/models/subscription';
|
|
17
17
|
import type { PaymentError } from '../store/models/types';
|
|
18
18
|
|
|
19
19
|
type RefundJob = {
|
|
@@ -313,7 +313,8 @@ const handleStakeReturnJob = async (
|
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
315
|
const client = paymentMethod.getOcapClient();
|
|
316
|
-
const
|
|
316
|
+
const subscription = await Subscription.findByPk(refund.subscription_id);
|
|
317
|
+
const address = await getSubscriptionStakeAddress(subscription!, customer.did);
|
|
317
318
|
const stakeEnough = await checkRemainingStake(paymentMethod, paymentCurrency, address, refund.amount);
|
|
318
319
|
if (!stakeEnough.enough) {
|
|
319
320
|
logger.warn('Stake return aborted because stake is not enough ', {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { toStakeAddress } from '@arcblock/did-util';
|
|
2
1
|
import type { LiteralUnion } from 'type-fest';
|
|
3
2
|
|
|
4
3
|
import { ensurePassportRevoked } from '../integrations/blocklet/passport';
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
checkRemainingStake,
|
|
16
15
|
getSubscriptionCycleAmount,
|
|
17
16
|
getSubscriptionCycleSetup,
|
|
17
|
+
getSubscriptionStakeAddress,
|
|
18
18
|
getSubscriptionStakeReturnSetup,
|
|
19
19
|
getSubscriptionStakeSlashSetup,
|
|
20
20
|
shouldCancelSubscription,
|
|
@@ -329,7 +329,7 @@ const handleStakeSlashAfterCancel = async (subscription: Subscription) => {
|
|
|
329
329
|
|
|
330
330
|
// check the staking
|
|
331
331
|
const client = method.getOcapClient();
|
|
332
|
-
const address =
|
|
332
|
+
const address = await getSubscriptionStakeAddress(subscription, customer.did);
|
|
333
333
|
const { state } = await client.getStakeState({ address });
|
|
334
334
|
if (!state || !state.data?.value) {
|
|
335
335
|
logger.warn('Stake slashing aborted because no staking state', {
|
|
@@ -374,7 +374,7 @@ const handleStakeSlashAfterCancel = async (subscription: Subscription) => {
|
|
|
374
374
|
const signed = await client.signSlashStakeTx({
|
|
375
375
|
tx: {
|
|
376
376
|
itx: {
|
|
377
|
-
address
|
|
377
|
+
address,
|
|
378
378
|
outputs: [{ owner: wallet.address, tokens: [{ address: currency.contract, value: invoice.amount_remaining }] }],
|
|
379
379
|
message: 'uncollectible_past_due_invoice',
|
|
380
380
|
data: {
|
|
@@ -530,7 +530,6 @@ const slashStakeOnCancel = async (subscription: Subscription) => {
|
|
|
530
530
|
});
|
|
531
531
|
return;
|
|
532
532
|
}
|
|
533
|
-
const address = toStakeAddress(customer.did, wallet.address, subscription.id);
|
|
534
533
|
const currency = await PaymentCurrency.findByPk(subscription.currency_id);
|
|
535
534
|
if (!currency) {
|
|
536
535
|
logger.warn('Stake slashing skipped because currency not found', {
|
|
@@ -539,6 +538,7 @@ const slashStakeOnCancel = async (subscription: Subscription) => {
|
|
|
539
538
|
});
|
|
540
539
|
return;
|
|
541
540
|
}
|
|
541
|
+
const address = await getSubscriptionStakeAddress(subscription, customer.did);
|
|
542
542
|
const result = await getSubscriptionStakeSlashSetup(subscription, address, paymentMethod);
|
|
543
543
|
const stakeEnough = await checkRemainingStake(paymentMethod, currency, address, result.return_amount);
|
|
544
544
|
if (!stakeEnough.enough) {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
getDaysUntilCancel,
|
|
43
43
|
getDaysUntilDue,
|
|
44
44
|
getSubscriptionCreateSetup,
|
|
45
|
+
getSubscriptionTrialSetup,
|
|
45
46
|
} from '../libs/subscription';
|
|
46
47
|
import { CHECKOUT_SESSION_TTL, formatAmountPrecisionLimit, formatMetadata, getDataObjectFromQuery } from '../libs/util';
|
|
47
48
|
import { invoiceQueue } from '../queues/invoice';
|
|
@@ -249,8 +250,8 @@ export async function getCheckoutSessionAmounts(checkoutSession: CheckoutSession
|
|
|
249
250
|
const now = dayjs().unix();
|
|
250
251
|
const items = await Price.expand(checkoutSession.line_items);
|
|
251
252
|
const trialInDays = Number(checkoutSession.subscription_data?.trial_period_days || 0);
|
|
252
|
-
const
|
|
253
|
-
const amount = getCheckoutAmount(items, checkoutSession.currency_id, trialInDays > 0 ||
|
|
253
|
+
const trialEnd = Number(checkoutSession.subscription_data?.trial_end || 0);
|
|
254
|
+
const amount = getCheckoutAmount(items, checkoutSession.currency_id, trialInDays > 0 || trialEnd > now);
|
|
254
255
|
return {
|
|
255
256
|
amount_subtotal: amount.subtotal,
|
|
256
257
|
amount_total: amount.total,
|
|
@@ -574,11 +575,16 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
574
575
|
// always update payment amount in case currency has changed
|
|
575
576
|
const now = dayjs().unix();
|
|
576
577
|
const lineItems = await Price.expand(checkoutSession.line_items, { product: true, upsell: true });
|
|
577
|
-
|
|
578
|
-
|
|
578
|
+
|
|
579
|
+
// trialing can be customized with currency_id list
|
|
580
|
+
const { trialEnd, trialInDays } = getSubscriptionTrialSetup(
|
|
581
|
+
checkoutSession.subscription_data as any,
|
|
582
|
+
paymentCurrency.id
|
|
583
|
+
);
|
|
584
|
+
|
|
579
585
|
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
580
586
|
const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);
|
|
581
|
-
const amount = getCheckoutAmount(lineItems, paymentCurrency.id, trialInDays > 0 ||
|
|
587
|
+
const amount = getCheckoutAmount(lineItems, paymentCurrency.id, trialInDays > 0 || trialEnd > now);
|
|
582
588
|
await checkoutSession.update({
|
|
583
589
|
amount_subtotal: amount.subtotal,
|
|
584
590
|
amount_total: amount.total,
|
|
@@ -770,7 +776,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
770
776
|
.status(403)
|
|
771
777
|
.json({ code: 'SUBSCRIPTION_INVALID', error: 'Checkout session subscription status unexpected' });
|
|
772
778
|
}
|
|
773
|
-
const setup = getSubscriptionCreateSetup(lineItems, paymentCurrency.id, trialInDays,
|
|
779
|
+
const setup = getSubscriptionCreateSetup(lineItems, paymentCurrency.id, trialInDays, trialEnd);
|
|
774
780
|
subscription = await subscription.update({
|
|
775
781
|
currency_id: paymentCurrency.id,
|
|
776
782
|
customer_id: customer.id,
|
|
@@ -813,7 +819,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
813
819
|
const recoveredFrom = recoveredFromId ? await Subscription.findByPk(recoveredFromId) : null;
|
|
814
820
|
|
|
815
821
|
// FIXME: @wangshijun respect all checkoutSession.subscription_data fields
|
|
816
|
-
const setup = getSubscriptionCreateSetup(lineItems, paymentCurrency.id, trialInDays,
|
|
822
|
+
const setup = getSubscriptionCreateSetup(lineItems, paymentCurrency.id, trialInDays, trialEnd);
|
|
817
823
|
subscription = await Subscription.create({
|
|
818
824
|
livemode: !!checkoutSession.livemode,
|
|
819
825
|
currency_id: paymentCurrency.id,
|
|
@@ -887,7 +893,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
887
893
|
lineItems,
|
|
888
894
|
checkoutSession.mode,
|
|
889
895
|
paymentCurrency.id,
|
|
890
|
-
trialInDays > 0 ||
|
|
896
|
+
trialInDays > 0 || trialEnd > now
|
|
891
897
|
);
|
|
892
898
|
const paymentSettings = {
|
|
893
899
|
payment_method_types: checkoutSession.payment_method_types,
|
|
@@ -964,7 +970,7 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
964
970
|
paymentCurrency,
|
|
965
971
|
lineItems,
|
|
966
972
|
trialInDays,
|
|
967
|
-
|
|
973
|
+
trialEnd
|
|
968
974
|
);
|
|
969
975
|
if (stripeSubscription && subscription?.payment_details?.stripe?.subscription_id === stripeSubscription.id) {
|
|
970
976
|
if (['active', 'trialing'].includes(stripeSubscription.status) && subscription.status === 'incomplete') {
|
|
@@ -3,6 +3,7 @@ import type { CallbackArgs } from '../../libs/auth';
|
|
|
3
3
|
import dayjs from '../../libs/dayjs';
|
|
4
4
|
import logger from '../../libs/logger';
|
|
5
5
|
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
6
|
+
import { getSubscriptionTrialSetup } from '../../libs/subscription';
|
|
6
7
|
import { getFastCheckoutAmount } from '../../libs/session';
|
|
7
8
|
import { getTxMetadata } from '../../libs/util';
|
|
8
9
|
import { invoiceQueue } from '../../queues/invoice';
|
|
@@ -39,9 +40,13 @@ export default {
|
|
|
39
40
|
const claims: { [type: string]: [string, object] } = {};
|
|
40
41
|
const now = dayjs().unix();
|
|
41
42
|
const items = checkoutSession.line_items as TLineItemExpanded[];
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
|
|
44
|
+
const { trialEnd, trialInDays } = getSubscriptionTrialSetup(
|
|
45
|
+
checkoutSession.subscription_data as any,
|
|
46
|
+
paymentCurrency.id
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const trialing = trialInDays > 0 || trialEnd > now;
|
|
45
50
|
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
46
51
|
const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);
|
|
47
52
|
const fastCheckoutAmount = getFastCheckoutAmount(items, checkoutSession.mode, paymentCurrency.id, trialing);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/indent */
|
|
2
2
|
/* eslint-disable prettier/prettier */
|
|
3
3
|
import { toTypeInfo } from '@arcblock/did';
|
|
4
|
-
import { toDelegateAddress
|
|
4
|
+
import { toDelegateAddress } from '@arcblock/did-util';
|
|
5
5
|
import type { Transaction } from '@ocap/client';
|
|
6
6
|
import { BN, fromTokenToUnit, toBase58 } from '@ocap/util';
|
|
7
7
|
import { fromPublicKey } from '@ocap/wallet';
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
getSubscriptionItemPrice,
|
|
22
22
|
getSubscriptionStakeSetup,
|
|
23
23
|
} from '../../libs/subscription';
|
|
24
|
-
import { OCAP_PAYMENT_TX_TYPE } from '../../libs/util';
|
|
24
|
+
import { getCustomerStakeAddress, OCAP_PAYMENT_TX_TYPE } from '../../libs/util';
|
|
25
25
|
import { invoiceQueue } from '../../queues/invoice';
|
|
26
26
|
import type { TLineItemExpanded } from '../../store/models';
|
|
27
27
|
import { CheckoutSession } from '../../store/models/checkout-session';
|
|
@@ -745,7 +745,7 @@ export async function getStakeTxClaim({
|
|
|
745
745
|
if (paymentMethod.type === 'arcblock') {
|
|
746
746
|
// create staking data
|
|
747
747
|
const client = paymentMethod.getOcapClient();
|
|
748
|
-
const address =
|
|
748
|
+
const address = await getCustomerStakeAddress(userDid, subscription.id);
|
|
749
749
|
const { state } = await client.getStakeState({ address });
|
|
750
750
|
const data = {
|
|
751
751
|
type: 'json',
|
|
@@ -1026,7 +1026,7 @@ export async function executeOcapTransactions(
|
|
|
1026
1026
|
type: 'delegate',
|
|
1027
1027
|
staking: {
|
|
1028
1028
|
tx_hash: stakingTxHash,
|
|
1029
|
-
address:
|
|
1029
|
+
address: await getCustomerStakeAddress(userDid, nonce),
|
|
1030
1030
|
},
|
|
1031
1031
|
};
|
|
1032
1032
|
}
|
|
@@ -3,6 +3,7 @@ import type { CallbackArgs } from '../../libs/auth';
|
|
|
3
3
|
import dayjs from '../../libs/dayjs';
|
|
4
4
|
import logger from '../../libs/logger';
|
|
5
5
|
import { isDelegationSufficientForPayment } from '../../libs/payment';
|
|
6
|
+
import { getSubscriptionTrialSetup } from '../../libs/subscription';
|
|
6
7
|
import { getFastCheckoutAmount } from '../../libs/session';
|
|
7
8
|
import { getTxMetadata } from '../../libs/util';
|
|
8
9
|
import { invoiceQueue } from '../../queues/invoice';
|
|
@@ -40,9 +41,11 @@ export default {
|
|
|
40
41
|
const claims: { [type: string]: [string, object] } = {};
|
|
41
42
|
const now = dayjs().unix();
|
|
42
43
|
const items = checkoutSession.line_items as TLineItemExpanded[];
|
|
43
|
-
const trialInDays =
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const { trialEnd, trialInDays } = getSubscriptionTrialSetup(
|
|
45
|
+
checkoutSession.subscription_data as any,
|
|
46
|
+
paymentCurrency.id
|
|
47
|
+
);
|
|
48
|
+
const trialing = trialInDays > 0 || trialEnd > now;
|
|
46
49
|
const billingThreshold = Number(checkoutSession.subscription_data?.billing_threshold_amount || 0);
|
|
47
50
|
const minStakeAmount = Number(checkoutSession.subscription_data?.min_stake_amount || 0);
|
|
48
51
|
const fastCheckoutAmount = getFastCheckoutAmount(items, checkoutSession.mode, paymentCurrency.id, trialing);
|
|
@@ -1644,7 +1644,7 @@ router.put('/:id/slash-stake', auth, async (req, res) => {
|
|
|
1644
1644
|
});
|
|
1645
1645
|
return res.json(result);
|
|
1646
1646
|
} catch (err) {
|
|
1647
|
-
logger.error('subscription slash stake failed', { subscription: subscription.id, error: err
|
|
1647
|
+
logger.error('subscription slash stake failed', { subscription: subscription.id, error: err });
|
|
1648
1648
|
return res.status(400).json({ error: err.message });
|
|
1649
1649
|
}
|
|
1650
1650
|
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getMinRetryMail,
|
|
8
8
|
getSubscriptionCreateSetup,
|
|
9
9
|
getSubscriptionStakeSetup,
|
|
10
|
+
getSubscriptionTrialSetup,
|
|
10
11
|
shouldCancelSubscription,
|
|
11
12
|
} from '../../src/libs/subscription';
|
|
12
13
|
|
|
@@ -183,7 +184,7 @@ describe('getSubscriptionCreateSetup', () => {
|
|
|
183
184
|
);
|
|
184
185
|
});
|
|
185
186
|
|
|
186
|
-
it('should
|
|
187
|
+
it('should trialEnd overwrite trialInDays', () => {
|
|
187
188
|
const items = [
|
|
188
189
|
{
|
|
189
190
|
price: { type: 'recurring', currency_options: currencies, recurring: { interval: 'day', interval_count: '1' } },
|
|
@@ -200,7 +201,7 @@ describe('getSubscriptionCreateSetup', () => {
|
|
|
200
201
|
);
|
|
201
202
|
});
|
|
202
203
|
|
|
203
|
-
it('should calculate trial period when only
|
|
204
|
+
it('should calculate trial period when only trialEnd is provided', () => {
|
|
204
205
|
const items = [
|
|
205
206
|
{
|
|
206
207
|
price: { type: 'recurring', currency_options: currencies, recurring: { interval: 'day', interval_count: '1' } },
|
|
@@ -371,8 +372,8 @@ describe('getSubscriptionStakeSetup', () => {
|
|
|
371
372
|
|
|
372
373
|
it('should calculate staking for recurring metered price type when billingThreshold is 0 #1', () => {
|
|
373
374
|
const result = getSubscriptionStakeSetup(items.slice(2, 3), 'usd', '10');
|
|
374
|
-
expect(result.licensed.toString()).toBe('
|
|
375
|
-
expect(result.metered.toString()).toBe('
|
|
375
|
+
expect(result.licensed.toString()).toBe('10');
|
|
376
|
+
expect(result.metered.toString()).toBe('0');
|
|
376
377
|
});
|
|
377
378
|
|
|
378
379
|
it('should calculate staking for recurring metered price type when billingThreshold is 0 #2', () => {
|
|
@@ -383,7 +384,30 @@ describe('getSubscriptionStakeSetup', () => {
|
|
|
383
384
|
|
|
384
385
|
it('should calculate staking for recurring metered price type when billingThreshold is greater than 0', () => {
|
|
385
386
|
const result = getSubscriptionStakeSetup(items, 'usd', '10');
|
|
386
|
-
expect(result.licensed.toString()).toBe('
|
|
387
|
-
expect(result.metered.toString()).toBe('
|
|
387
|
+
expect(result.licensed.toString()).toBe('10');
|
|
388
|
+
expect(result.metered.toString()).toBe('0');
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('getSubscriptionTrialSetup', () => {
|
|
393
|
+
it('should return trialInDays and trialEnd when data is provided', () => {
|
|
394
|
+
const data: any = { trial_period_days: '10', trial_end: '20', trial_currency: 'USD,EUR' };
|
|
395
|
+
const currencyId = 'USD';
|
|
396
|
+
const result = getSubscriptionTrialSetup(data, currencyId);
|
|
397
|
+
expect(result).toEqual({ trialInDays: 10, trialEnd: 20 });
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should set trialInDays and trialEnd to 0 if currencyId is not in the trialCurrencyIds list', () => {
|
|
401
|
+
const data: any = { trial_period_days: '10', trial_end: '20', trial_currency: 'USD,EUR' };
|
|
402
|
+
const currencyId = 'JPY'; // currency code not included in the trialCurrencyIds list
|
|
403
|
+
const result = getSubscriptionTrialSetup(data, currencyId);
|
|
404
|
+
expect(result).toEqual({ trialInDays: 0, trialEnd: 0 });
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should set trialInDays and trialEnd to 0 when values are not provided', () => {
|
|
408
|
+
const data: any = {};
|
|
409
|
+
const currencyId = 'USD';
|
|
410
|
+
const result = getSubscriptionTrialSetup(data, currencyId);
|
|
411
|
+
expect(result).toEqual({ trialInDays: 0, trialEnd: 0 });
|
|
388
412
|
});
|
|
389
413
|
});
|
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.14.
|
|
17
|
+
version: 1.14.38
|
|
18
18
|
logo: logo.png
|
|
19
19
|
files:
|
|
20
20
|
- dist
|
|
@@ -39,7 +39,6 @@ interfaces:
|
|
|
39
39
|
profileFields:
|
|
40
40
|
- fullName
|
|
41
41
|
- email
|
|
42
|
-
- phone
|
|
43
42
|
- avatar
|
|
44
43
|
allowSwitchProfile: true
|
|
45
44
|
ignoreUrls:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.38",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@arcblock/validator": "^1.18.132",
|
|
53
53
|
"@blocklet/js-sdk": "1.16.30",
|
|
54
54
|
"@blocklet/logger": "1.16.30",
|
|
55
|
-
"@blocklet/payment-react": "1.14.
|
|
55
|
+
"@blocklet/payment-react": "1.14.38",
|
|
56
56
|
"@blocklet/sdk": "1.16.30",
|
|
57
57
|
"@blocklet/ui-react": "^2.10.23",
|
|
58
58
|
"@blocklet/uploader": "^0.1.27",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@abtnode/types": "1.16.30",
|
|
121
121
|
"@arcblock/eslint-config-ts": "^0.3.2",
|
|
122
|
-
"@blocklet/payment-types": "1.14.
|
|
122
|
+
"@blocklet/payment-types": "1.14.38",
|
|
123
123
|
"@types/cookie-parser": "^1.4.7",
|
|
124
124
|
"@types/cors": "^2.8.17",
|
|
125
125
|
"@types/debug": "^4.1.12",
|
|
@@ -161,5 +161,5 @@
|
|
|
161
161
|
"parser": "typescript"
|
|
162
162
|
}
|
|
163
163
|
},
|
|
164
|
-
"gitHead": "
|
|
164
|
+
"gitHead": "f572122a40f30619bd736d8df20e362b6cdab420"
|
|
165
165
|
}
|