payment-kit 1.13.97 → 1.13.99
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Op } from 'sequelize';
|
|
2
2
|
|
|
3
|
+
import { createEvent } from '../libs/audit';
|
|
3
4
|
import dayjs from '../libs/dayjs';
|
|
4
5
|
import logger from '../libs/logger';
|
|
5
6
|
import createQueue from '../libs/queue';
|
|
@@ -57,9 +58,18 @@ export const handleInvoice = async (job: InvoiceJob) => {
|
|
|
57
58
|
|
|
58
59
|
if (invoice.subscription_id) {
|
|
59
60
|
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
60
|
-
if (subscription
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
if (subscription) {
|
|
62
|
+
if (subscription.status === 'incomplete') {
|
|
63
|
+
await subscription.start();
|
|
64
|
+
logger.info('invoice subscription updated', subscription.id);
|
|
65
|
+
} else {
|
|
66
|
+
if (invoice.billing_reason === 'subscription_cycle') {
|
|
67
|
+
createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(console.error);
|
|
68
|
+
}
|
|
69
|
+
if (invoice.billing_reason === 'subscription_update') {
|
|
70
|
+
createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(console.error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
63
73
|
}
|
|
64
74
|
}
|
|
65
75
|
|
|
@@ -78,6 +78,9 @@ export const handlePaymentSucceed = async (paymentIntent: PaymentIntent, invoice
|
|
|
78
78
|
if (invoice.billing_reason === 'subscription_cycle') {
|
|
79
79
|
createEvent('Subscription', 'customer.subscription.renewed', subscription).catch(console.error);
|
|
80
80
|
}
|
|
81
|
+
if (invoice.billing_reason === 'subscription_update') {
|
|
82
|
+
createEvent('Subscription', 'customer.subscription.upgraded', subscription).catch(console.error);
|
|
83
|
+
}
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
if (invoice.checkout_session_id) {
|
|
@@ -469,9 +469,8 @@ export async function ensureInvoiceForCollect(invoiceId: string) {
|
|
|
469
469
|
if (paymentIntent.status === 'succeeded') {
|
|
470
470
|
throw new Error(`Payment intent already succeeded for invoice ${invoiceId}`);
|
|
471
471
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
472
|
+
|
|
473
|
+
await paymentIntent.update({ status: 'requires_capture' });
|
|
475
474
|
|
|
476
475
|
const paymentCurrency = await PaymentCurrency.findByPk(paymentIntent.currency_id);
|
|
477
476
|
const paymentMethod = await PaymentMethod.findByPk(paymentIntent.payment_method_id);
|
|
@@ -405,35 +405,18 @@ const validateSubscriptionUpdateRequest = async (subscription: Subscription, ite
|
|
|
405
405
|
throw new Error('Subscription item should not have duplicate price_id');
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
if (addedItems.some((x) => !x.price.active)) {
|
|
413
|
-
throw new Error('Subscription item should not use archived price');
|
|
414
|
-
}
|
|
415
|
-
for (let i = 0; i < addedItems.length; i++) {
|
|
416
|
-
const result = isLineItemAligned(addedItems, i);
|
|
417
|
-
if (result.currency === false) {
|
|
418
|
-
throw new Error('New subscription items should have same currency');
|
|
419
|
-
}
|
|
420
|
-
if (result.recurring === false) {
|
|
421
|
-
throw new Error('New subscription items should have same recurring');
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
408
|
+
// split items into added, deleted
|
|
409
|
+
const addedItems = items.filter((x: any) => x.price_id && !x.id);
|
|
410
|
+
const deletedItems = items.filter((x: any) => x.deleted && x.id);
|
|
411
|
+
const updatedItems = items.filter((x: any) => !x.deleted && x.id);
|
|
425
412
|
const existingItems = await SubscriptionItem.findAll({ where: { subscription_id: subscription.id } });
|
|
426
|
-
const deletedItems = items.filter((x: any) => x.deleted && x.id).map((x: any) => x.id);
|
|
427
|
-
if (existingItems.length - deletedItems.length + addedItems.length === 0) {
|
|
428
|
-
throw new Error('Subscription should have at least one subscription item');
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const existingExpanded = await Price.expand(
|
|
432
|
-
existingItems.filter((x) => deletedItems.includes(x.id) === false).map((x) => x.toJSON())
|
|
433
|
-
);
|
|
434
413
|
|
|
435
414
|
// try handle cross-sell with different interval, just replace with new price that have same interval
|
|
436
|
-
|
|
415
|
+
let addedExpanded = await Price.expand(addedItems as LineItem[]);
|
|
416
|
+
let existingExpanded = await Price.expand(
|
|
417
|
+
existingItems.filter((x) => deletedItems.some((y) => y.id === x.id) === false).map((x) => x.toJSON())
|
|
418
|
+
);
|
|
419
|
+
const newRecurring = addedExpanded.find((x) => x.price.type === 'recurring')?.price.recurring;
|
|
437
420
|
await Promise.all(
|
|
438
421
|
existingExpanded.map(async (x: TLineItemExpanded) => {
|
|
439
422
|
const oldRecurring = x.price.recurring;
|
|
@@ -453,14 +436,23 @@ const validateSubscriptionUpdateRequest = async (subscription: Subscription, ite
|
|
|
453
436
|
);
|
|
454
437
|
|
|
455
438
|
if (newPrice) {
|
|
439
|
+
deletedItems.push({
|
|
440
|
+
id: x.id,
|
|
441
|
+
deleted: true,
|
|
442
|
+
clear_usage: false,
|
|
443
|
+
});
|
|
444
|
+
addedItems.push({
|
|
445
|
+
price_id: newPrice.id,
|
|
446
|
+
quantity: x.quantity,
|
|
447
|
+
});
|
|
448
|
+
|
|
456
449
|
logger.info('Replace subscription item on update', {
|
|
457
450
|
subscription: subscription.id,
|
|
458
451
|
item: x.id,
|
|
459
452
|
oldPrice: x.price.id,
|
|
460
453
|
newPrice: newPrice.id,
|
|
461
454
|
});
|
|
462
|
-
|
|
463
|
-
x.price = newPrice.toJSON();
|
|
455
|
+
|
|
464
456
|
return x;
|
|
465
457
|
}
|
|
466
458
|
|
|
@@ -471,8 +463,22 @@ const validateSubscriptionUpdateRequest = async (subscription: Subscription, ite
|
|
|
471
463
|
})
|
|
472
464
|
);
|
|
473
465
|
|
|
474
|
-
|
|
475
|
-
|
|
466
|
+
addedExpanded = await Price.expand(addedItems as LineItem[]);
|
|
467
|
+
existingExpanded = await Price.expand(
|
|
468
|
+
existingItems.filter((x) => deletedItems.some((y) => y.id === x.id) === false).map((x) => x.toJSON())
|
|
469
|
+
);
|
|
470
|
+
if (addedExpanded.some((x) => !x.price)) {
|
|
471
|
+
throw new Error('Subscription item should not use non-exist price');
|
|
472
|
+
}
|
|
473
|
+
if (addedExpanded.some((x) => !x.price.active)) {
|
|
474
|
+
throw new Error('Subscription item should not use archived price');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const updatedExpanded = await Price.expand(updatedItems as LineItem[]);
|
|
478
|
+
const newItems: any[] = [...existingExpanded, ...addedExpanded, ...updatedExpanded];
|
|
479
|
+
if (newItems.length === 0) {
|
|
480
|
+
throw new Error('Subscription should have at least one subscription item');
|
|
481
|
+
}
|
|
476
482
|
if (newItems.length > MAX_SUBSCRIPTION_ITEM_COUNT) {
|
|
477
483
|
throw new Error(`Subscription should not have more than ${MAX_SUBSCRIPTION_ITEM_COUNT} items`);
|
|
478
484
|
}
|
|
@@ -489,6 +495,9 @@ const validateSubscriptionUpdateRequest = async (subscription: Subscription, ite
|
|
|
489
495
|
return {
|
|
490
496
|
existingItems,
|
|
491
497
|
addedItems,
|
|
498
|
+
updatedItems,
|
|
499
|
+
deletedItems,
|
|
500
|
+
addedExpanded,
|
|
492
501
|
newItems,
|
|
493
502
|
};
|
|
494
503
|
};
|
|
@@ -684,40 +693,40 @@ router.put('/:id', authPortal, async (req, res) => {
|
|
|
684
693
|
// handle subscription item changes
|
|
685
694
|
if (Array.isArray(req.body.items) && req.body.items.length > 0) {
|
|
686
695
|
// validate the request
|
|
687
|
-
const { existingItems, addedItems, newItems } =
|
|
688
|
-
subscription,
|
|
689
|
-
req.body.items
|
|
690
|
-
);
|
|
696
|
+
const { existingItems, addedItems, updatedItems, deletedItems, newItems } =
|
|
697
|
+
await validateSubscriptionUpdateRequest(subscription, req.body.items);
|
|
691
698
|
|
|
692
699
|
// update subscription items
|
|
693
|
-
for (const item of
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
700
|
+
for (const item of addedItems) {
|
|
701
|
+
await SubscriptionItem.create({
|
|
702
|
+
price_id: item.price_id as string,
|
|
703
|
+
quantity: item.quantity as number,
|
|
704
|
+
livemode: subscription.livemode,
|
|
705
|
+
subscription_id: subscription.id,
|
|
706
|
+
metadata: {},
|
|
707
|
+
});
|
|
708
|
+
logger.info('subscription item added', { subscription: req.params.id, item: item.id });
|
|
709
|
+
}
|
|
710
|
+
for (const item of updatedItems) {
|
|
711
|
+
const exist = existingItems.find((x) => x.id === item.id);
|
|
712
|
+
if (exist) {
|
|
713
|
+
await exist.update(pick(item, ['quantity', 'metadata', 'billing_thresholds']));
|
|
714
|
+
logger.info('subscription item updated', { subscription: req.params.id, item: item.id });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
for (const item of deletedItems) {
|
|
718
|
+
if (item.clear_usage) {
|
|
719
|
+
await UsageRecord.destroy({ where: { subscription_item_id: item.id } });
|
|
720
|
+
logger.info('subscription item usage cleared', { subscription: req.params.id, item: item.id });
|
|
714
721
|
}
|
|
722
|
+
await SubscriptionItem.destroy({ where: { id: item.id } });
|
|
723
|
+
logger.info('subscription item deleted', { subscription: req.params.id, item: item.id });
|
|
715
724
|
}
|
|
716
725
|
|
|
717
726
|
// update subscription period settings
|
|
718
727
|
// HINT: if we are adding new items, we need to reset the anchor to now
|
|
719
728
|
const setup = getSubscriptionCreateSetup(newItems, paymentCurrency.id, 0);
|
|
720
|
-
if (
|
|
729
|
+
if (newItems.some((x) => x.price.type === 'recurring' && addedItems.find((y) => y.price_id === x.price_id))) {
|
|
721
730
|
updates.pending_invoice_item_interval = setup.recurring;
|
|
722
731
|
updates.current_period_start = setup.period.start;
|
|
723
732
|
updates.current_period_end = setup.period.end;
|
|
@@ -980,7 +989,10 @@ router.post('/:id/update', authPortal, async (req, res) => {
|
|
|
980
989
|
}
|
|
981
990
|
|
|
982
991
|
// validate the request
|
|
983
|
-
const { newItems } = await validateSubscriptionUpdateRequest(
|
|
992
|
+
const { newItems, addedItems, deletedItems, updatedItems } = await validateSubscriptionUpdateRequest(
|
|
993
|
+
subscription,
|
|
994
|
+
req.body.items
|
|
995
|
+
);
|
|
984
996
|
|
|
985
997
|
// do the simulation
|
|
986
998
|
const setup = getSubscriptionCreateSetup(newItems, subscription.currency_id, 0);
|
|
@@ -992,6 +1004,9 @@ router.post('/:id/update', authPortal, async (req, res) => {
|
|
|
992
1004
|
credit: result.credit,
|
|
993
1005
|
prorations: result.prorations,
|
|
994
1006
|
items: newItems,
|
|
1007
|
+
addedItems,
|
|
1008
|
+
deletedItems,
|
|
1009
|
+
updatedItems,
|
|
995
1010
|
});
|
|
996
1011
|
} catch (err) {
|
|
997
1012
|
console.error(err);
|
|
@@ -413,6 +413,7 @@ export type EventType = LiteralUnion<
|
|
|
413
413
|
| 'customer.subscription.pending_update_expired'
|
|
414
414
|
| 'customer.subscription.resumed'
|
|
415
415
|
| 'customer.subscription.renewed'
|
|
416
|
+
| 'customer.subscription.upgraded'
|
|
416
417
|
| 'customer.subscription.renew_failed'
|
|
417
418
|
| 'customer.subscription.trial_start'
|
|
418
419
|
| 'customer.subscription.trial_will_end'
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.99",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"@abtnode/types": "1.16.21",
|
|
111
111
|
"@arcblock/eslint-config": "^0.2.4",
|
|
112
112
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
113
|
-
"@did-pay/types": "1.13.
|
|
113
|
+
"@did-pay/types": "1.13.99",
|
|
114
114
|
"@types/cookie-parser": "^1.4.6",
|
|
115
115
|
"@types/cors": "^2.8.17",
|
|
116
116
|
"@types/dotenv-flow": "^3.3.3",
|
|
@@ -149,5 +149,5 @@
|
|
|
149
149
|
"parser": "typescript"
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
|
-
"gitHead": "
|
|
152
|
+
"gitHead": "abb8f6d451350b07f5755fe4d599e8a438b3cfe1"
|
|
153
153
|
}
|