payment-kit 1.14.36 → 1.14.37
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/resource.ts +3 -3
- package/api/src/libs/subscription.ts +30 -12
- package/api/src/routes/checkout-sessions.ts +15 -9
- package/api/src/routes/connect/setup.ts +8 -3
- 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
|
@@ -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,
|
|
@@ -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,
|
|
@@ -787,3 +789,19 @@ export async function checkRemainingStake(
|
|
|
787
789
|
revoked,
|
|
788
790
|
};
|
|
789
791
|
}
|
|
792
|
+
|
|
793
|
+
// trialing can be customized with currency_id list
|
|
794
|
+
export function getSubscriptionTrialSetup(data: Partial<SubscriptionData>, currencyId: string) {
|
|
795
|
+
let trialInDays = Number(data?.trial_period_days || 0);
|
|
796
|
+
let trialEnd = Number(data?.trial_end || 0);
|
|
797
|
+
const trialCurrencyIds = (data?.trial_currency || '').split(',').map(trim).filter(Boolean);
|
|
798
|
+
if (trialCurrencyIds.length > 0 && trialCurrencyIds.includes(currencyId) === false) {
|
|
799
|
+
trialEnd = 0;
|
|
800
|
+
trialInDays = 0;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
trialInDays,
|
|
805
|
+
trialEnd,
|
|
806
|
+
};
|
|
807
|
+
}
|
|
@@ -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);
|
|
@@ -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.37
|
|
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.37",
|
|
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.37",
|
|
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.37",
|
|
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": "645ae0bdbab463eec878af0521eb982b51499639"
|
|
165
165
|
}
|