@misterhomer1992/payment-manager 1.0.0 → 1.0.2
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/dist/collections/payments.js +6 -2
- package/dist/collections/plans.js +7 -3
- package/dist/collections/subscriptions.d.ts +2 -1
- package/dist/collections/subscriptions.js +21 -10
- package/dist/collections/tokenPacks.js +6 -2
- package/dist/collections/userTokenBalances.js +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/services/paymentService.js +14 -10
- package/dist/services/subscriptionService.d.ts +3 -0
- package/dist/services/subscriptionService.js +27 -10
- package/dist/services/webhookService.js +72 -16
- package/dist/types/orderReference.d.ts +2 -2
- package/dist/types/plan.d.ts +1 -0
- package/dist/utils/gracePeriod.d.ts +2 -1
- package/dist/utils/gracePeriod.js +8 -6
- package/dist/utils/orderReference.js +11 -7
- package/dist/utils/proration.js +7 -4
- package/dist/utils/webhookResponse.js +5 -1
- package/package.json +3 -2
- package/src/collections/payments.ts +3 -2
- package/src/collections/plans.ts +4 -3
- package/src/collections/subscriptions.ts +21 -11
- package/src/collections/tokenPacks.ts +3 -2
- package/src/collections/userTokenBalances.ts +4 -3
- package/src/index.ts +4 -0
- package/src/services/deactivationCheckHandler.test.ts +19 -20
- package/src/services/paymentCheckHandler.test.ts +33 -34
- package/src/services/paymentService.ts +11 -10
- package/src/services/subscriptionService.ts +29 -11
- package/src/services/webhookService.ts +78 -17
- package/src/types/orderReference.ts +2 -2
- package/src/types/plan.ts +1 -0
- package/src/utils/gracePeriod.test.ts +11 -13
- package/src/utils/gracePeriod.ts +6 -8
- package/src/utils/orderReference.test.ts +7 -6
- package/src/utils/orderReference.ts +9 -8
- package/src/utils/proration.test.ts +23 -23
- package/src/utils/proration.ts +4 -5
- package/src/utils/webhookResponse.test.ts +6 -5
- package/src/utils/webhookResponse.ts +2 -1
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.create = create;
|
|
4
7
|
exports.update = update;
|
|
5
8
|
exports.findPendingByUserAndPlan = findPendingByUserAndPlan;
|
|
6
9
|
exports.findPendingByUserAndTokenPack = findPendingByUserAndTokenPack;
|
|
7
10
|
exports.findByOrderReference = findByOrderReference;
|
|
11
|
+
const moment_1 = __importDefault(require("moment"));
|
|
8
12
|
const config_js_1 = require("../config.js");
|
|
9
13
|
const COLLECTION = 'payments';
|
|
10
14
|
function collection() {
|
|
11
15
|
return (0, config_js_1.getConfig)().firestore.collection(COLLECTION);
|
|
12
16
|
}
|
|
13
17
|
async function create(payment) {
|
|
14
|
-
const now =
|
|
18
|
+
const now = (0, moment_1.default)().toDate();
|
|
15
19
|
const docRef = await collection().add({
|
|
16
20
|
...payment,
|
|
17
21
|
createdAt: now,
|
|
@@ -24,7 +28,7 @@ async function update(paymentId, data) {
|
|
|
24
28
|
.doc(paymentId)
|
|
25
29
|
.update({
|
|
26
30
|
...data,
|
|
27
|
-
updatedAt:
|
|
31
|
+
updatedAt: (0, moment_1.default)().toDate(),
|
|
28
32
|
});
|
|
29
33
|
}
|
|
30
34
|
async function findPendingByUserAndPlan(userId, planId) {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.getById = getById;
|
|
4
7
|
exports.getAll = getAll;
|
|
5
8
|
exports.create = create;
|
|
6
9
|
exports.update = update;
|
|
7
10
|
exports.upsert = upsert;
|
|
11
|
+
const moment_1 = __importDefault(require("moment"));
|
|
8
12
|
const config_js_1 = require("../config.js");
|
|
9
13
|
const COLLECTION = 'subscriptionPlans';
|
|
10
14
|
function collection() {
|
|
@@ -21,7 +25,7 @@ async function getAll() {
|
|
|
21
25
|
return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
|
|
22
26
|
}
|
|
23
27
|
async function create(plan) {
|
|
24
|
-
const now =
|
|
28
|
+
const now = (0, moment_1.default)().toDate();
|
|
25
29
|
await collection()
|
|
26
30
|
.doc(plan.id)
|
|
27
31
|
.set({
|
|
@@ -35,11 +39,11 @@ async function update(planId, data) {
|
|
|
35
39
|
.doc(planId)
|
|
36
40
|
.update({
|
|
37
41
|
...data,
|
|
38
|
-
updatedAt:
|
|
42
|
+
updatedAt: (0, moment_1.default)().toDate(),
|
|
39
43
|
});
|
|
40
44
|
}
|
|
41
45
|
async function upsert(plan) {
|
|
42
|
-
const now =
|
|
46
|
+
const now = (0, moment_1.default)().toDate();
|
|
43
47
|
await collection()
|
|
44
48
|
.doc(plan.id)
|
|
45
49
|
.set({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SubscriptionEntity } from '../types/index.js';
|
|
2
|
+
export declare function getById(subscriptionId: string): Promise<SubscriptionEntity | null>;
|
|
2
3
|
export declare function getActiveByUserId(userId: string): Promise<SubscriptionEntity | null>;
|
|
3
|
-
export declare function getByUserId(userId: string): Promise<SubscriptionEntity[]>;
|
|
4
4
|
export declare function create(subscription: Omit<SubscriptionEntity, 'id' | 'createdAt' | 'updatedAt'>): Promise<string>;
|
|
5
5
|
export declare function update(subscriptionId: string, data: Partial<Omit<SubscriptionEntity, 'id'>>): Promise<void>;
|
|
6
|
+
export declare function getByUser(userId: string, statuses: SubscriptionEntity['status'][]): Promise<SubscriptionEntity[]>;
|
|
6
7
|
export declare function getAllActiveAndCancelled(): Promise<SubscriptionEntity[]>;
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getById = getById;
|
|
3
7
|
exports.getActiveByUserId = getActiveByUserId;
|
|
4
|
-
exports.getByUserId = getByUserId;
|
|
5
8
|
exports.create = create;
|
|
6
9
|
exports.update = update;
|
|
10
|
+
exports.getByUser = getByUser;
|
|
7
11
|
exports.getAllActiveAndCancelled = getAllActiveAndCancelled;
|
|
12
|
+
const moment_1 = __importDefault(require("moment"));
|
|
8
13
|
const config_js_1 = require("../config.js");
|
|
9
14
|
const COLLECTION = 'subscriptions';
|
|
10
15
|
function collection() {
|
|
11
16
|
return (0, config_js_1.getConfig)().firestore.collection(COLLECTION);
|
|
12
17
|
}
|
|
18
|
+
async function getById(subscriptionId) {
|
|
19
|
+
const doc = await collection().doc(subscriptionId).get();
|
|
20
|
+
if (!doc.exists)
|
|
21
|
+
return null;
|
|
22
|
+
return { id: doc.id, ...doc.data() };
|
|
23
|
+
}
|
|
13
24
|
async function getActiveByUserId(userId) {
|
|
14
25
|
const snapshot = await collection().where('userId', '==', userId).where('status', '==', 'active').limit(1).get();
|
|
15
26
|
if (snapshot.empty)
|
|
@@ -17,15 +28,8 @@ async function getActiveByUserId(userId) {
|
|
|
17
28
|
const doc = snapshot.docs[0];
|
|
18
29
|
return { id: doc.id, ...doc.data() };
|
|
19
30
|
}
|
|
20
|
-
async function getByUserId(userId) {
|
|
21
|
-
const snapshot = await collection()
|
|
22
|
-
.where('userId', '==', userId)
|
|
23
|
-
.where('status', 'in', ['active', 'cancelled'])
|
|
24
|
-
.get();
|
|
25
|
-
return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
|
|
26
|
-
}
|
|
27
31
|
async function create(subscription) {
|
|
28
|
-
const now =
|
|
32
|
+
const now = (0, moment_1.default)().toDate();
|
|
29
33
|
const docRef = await collection().add({
|
|
30
34
|
...subscription,
|
|
31
35
|
createdAt: now,
|
|
@@ -38,9 +42,16 @@ async function update(subscriptionId, data) {
|
|
|
38
42
|
.doc(subscriptionId)
|
|
39
43
|
.update({
|
|
40
44
|
...data,
|
|
41
|
-
updatedAt:
|
|
45
|
+
updatedAt: (0, moment_1.default)().toDate(),
|
|
42
46
|
});
|
|
43
47
|
}
|
|
48
|
+
async function getByUser(userId, statuses) {
|
|
49
|
+
const snapshot = await collection()
|
|
50
|
+
.where('userId', '==', userId)
|
|
51
|
+
.where('status', 'in', statuses)
|
|
52
|
+
.get();
|
|
53
|
+
return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
|
|
54
|
+
}
|
|
44
55
|
async function getAllActiveAndCancelled() {
|
|
45
56
|
const snapshot = await collection().where('status', 'in', ['active', 'cancelled']).get();
|
|
46
57
|
return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.getById = getById;
|
|
4
7
|
exports.getAll = getAll;
|
|
5
8
|
exports.create = create;
|
|
6
9
|
exports.update = update;
|
|
7
10
|
exports.remove = remove;
|
|
11
|
+
const moment_1 = __importDefault(require("moment"));
|
|
8
12
|
const config_js_1 = require("../config.js");
|
|
9
13
|
const COLLECTION = 'tokenPacks';
|
|
10
14
|
function collection() {
|
|
@@ -21,7 +25,7 @@ async function getAll() {
|
|
|
21
25
|
return snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
|
|
22
26
|
}
|
|
23
27
|
async function create(pack) {
|
|
24
|
-
const now =
|
|
28
|
+
const now = (0, moment_1.default)().toDate();
|
|
25
29
|
const docRef = await collection().add({
|
|
26
30
|
...pack,
|
|
27
31
|
createdAt: now,
|
|
@@ -34,7 +38,7 @@ async function update(packId, data) {
|
|
|
34
38
|
.doc(packId)
|
|
35
39
|
.update({
|
|
36
40
|
...data,
|
|
37
|
-
updatedAt:
|
|
41
|
+
updatedAt: (0, moment_1.default)().toDate(),
|
|
38
42
|
});
|
|
39
43
|
}
|
|
40
44
|
async function remove(packId) {
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.getByUserId = getByUserId;
|
|
4
7
|
exports.addTokens = addTokens;
|
|
5
8
|
exports.update = update;
|
|
9
|
+
const moment_1 = __importDefault(require("moment"));
|
|
6
10
|
const config_js_1 = require("../config.js");
|
|
7
11
|
const firestore_1 = require("firebase-admin/firestore");
|
|
8
12
|
const COLLECTION = 'userTokenBalances';
|
|
@@ -12,7 +16,7 @@ function collection() {
|
|
|
12
16
|
const DEFAULT_BALANCE = {
|
|
13
17
|
balance: 0,
|
|
14
18
|
totalPurchased: 0,
|
|
15
|
-
updatedAt:
|
|
19
|
+
updatedAt: (0, moment_1.default)(0).toDate(),
|
|
16
20
|
};
|
|
17
21
|
async function getByUserId(userId) {
|
|
18
22
|
const doc = await collection().doc(userId).get();
|
|
@@ -28,7 +32,7 @@ async function addTokens(userId, amount) {
|
|
|
28
32
|
userId,
|
|
29
33
|
balance: firestore_1.FieldValue.increment(amount),
|
|
30
34
|
totalPurchased: firestore_1.FieldValue.increment(amount),
|
|
31
|
-
updatedAt:
|
|
35
|
+
updatedAt: (0, moment_1.default)().toDate(),
|
|
32
36
|
}, { merge: true });
|
|
33
37
|
}
|
|
34
38
|
async function update(userId, data) {
|
|
@@ -36,6 +40,6 @@ async function update(userId, data) {
|
|
|
36
40
|
.doc(userId)
|
|
37
41
|
.update({
|
|
38
42
|
...data,
|
|
39
|
-
updatedAt:
|
|
43
|
+
updatedAt: (0, moment_1.default)().toDate(),
|
|
40
44
|
});
|
|
41
45
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,9 @@ export interface PaymentManager {
|
|
|
21
21
|
expire: (userId: string) => Promise<void>;
|
|
22
22
|
deactivate: (userId: string) => Promise<void>;
|
|
23
23
|
changePlan: (userId: string, newPlanId: string) => Promise<void>;
|
|
24
|
+
getById: (subscriptionId: string) => Promise<SubscriptionEntity | null>;
|
|
24
25
|
getActiveByUserId: (userId: string) => Promise<SubscriptionEntity | null>;
|
|
26
|
+
getByUser: (userId: string, statuses: SubscriptionEntity['status'][]) => Promise<SubscriptionEntity[]>;
|
|
25
27
|
};
|
|
26
28
|
tokenPacks: {
|
|
27
29
|
create: (pack: Omit<TokenPackEntity, 'id' | 'createdAt' | 'updatedAt'>) => Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,9 @@ function init(config) {
|
|
|
56
56
|
expire: subscriptionService.expire,
|
|
57
57
|
deactivate: subscriptionService.deactivate,
|
|
58
58
|
changePlan: subscriptionService.changePlan,
|
|
59
|
+
getById: subscriptionService.getById,
|
|
59
60
|
getActiveByUserId: subscriptionService.getActiveByUserId,
|
|
61
|
+
getByUser: subscriptionService.getByUser,
|
|
60
62
|
},
|
|
61
63
|
tokenPacks: {
|
|
62
64
|
create: tokenPackService.create,
|
|
@@ -32,17 +32,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.createSubscriptionPayment = createSubscriptionPayment;
|
|
37
40
|
exports.createTokenPackPayment = createTokenPackPayment;
|
|
38
41
|
exports.updatePaymentStatus = updatePaymentStatus;
|
|
42
|
+
const moment_1 = __importDefault(require("moment"));
|
|
39
43
|
const config_js_1 = require("../config.js");
|
|
40
44
|
const paymentsCollection = __importStar(require("../collections/payments.js"));
|
|
41
45
|
const plansCollection = __importStar(require("../collections/plans.js"));
|
|
42
46
|
const orderReference_js_1 = require("../utils/orderReference.js");
|
|
43
47
|
const wayforpay_api_1 = require("@misterhomer1992/wayforpay-api");
|
|
44
48
|
const errors_js_1 = require("../types/errors.js");
|
|
45
|
-
const CACHE_TTL_MS = 5
|
|
49
|
+
const CACHE_TTL_MS = moment_1.default.duration(5, 'minutes').asMilliseconds();
|
|
46
50
|
const ORDER_REF_VERSION = '1';
|
|
47
51
|
function isCacheValid(payment) {
|
|
48
52
|
if (!payment.paymentLink || !payment.paymentLinkCreatedAt)
|
|
@@ -50,7 +54,7 @@ function isCacheValid(payment) {
|
|
|
50
54
|
const createdAt = payment.paymentLinkCreatedAt instanceof Date
|
|
51
55
|
? payment.paymentLinkCreatedAt
|
|
52
56
|
: new Date(payment.paymentLinkCreatedAt);
|
|
53
|
-
return
|
|
57
|
+
return (0, moment_1.default)().diff((0, moment_1.default)(createdAt)) < CACHE_TTL_MS;
|
|
54
58
|
}
|
|
55
59
|
async function createSubscriptionPayment(userId, planId, currency, productName) {
|
|
56
60
|
const { wayforpay, platform, logger } = (0, config_js_1.getConfig)();
|
|
@@ -68,14 +72,14 @@ async function createSubscriptionPayment(userId, planId, currency, productName)
|
|
|
68
72
|
});
|
|
69
73
|
return { paymentUrl: cached.paymentLink };
|
|
70
74
|
}
|
|
71
|
-
const now =
|
|
75
|
+
const now = (0, moment_1.default)();
|
|
72
76
|
const orderReference = (0, orderReference_js_1.buildOrderReference)({
|
|
73
77
|
version: ORDER_REF_VERSION,
|
|
74
78
|
platform,
|
|
75
79
|
userId,
|
|
76
80
|
type: 'sub',
|
|
77
81
|
planId,
|
|
78
|
-
date: now,
|
|
82
|
+
date: now.toDate(),
|
|
79
83
|
});
|
|
80
84
|
let url;
|
|
81
85
|
try {
|
|
@@ -84,7 +88,7 @@ async function createSubscriptionPayment(userId, planId, currency, productName)
|
|
|
84
88
|
merchantSecretKey: wayforpay.merchantSecretKey,
|
|
85
89
|
merchantDomainName: wayforpay.merchantDomainName,
|
|
86
90
|
orderReference,
|
|
87
|
-
orderDate:
|
|
91
|
+
orderDate: now.unix(),
|
|
88
92
|
productName: [productName],
|
|
89
93
|
productPrice: [plan.amount],
|
|
90
94
|
currency: currency,
|
|
@@ -106,7 +110,7 @@ async function createSubscriptionPayment(userId, planId, currency, productName)
|
|
|
106
110
|
status: 'pending',
|
|
107
111
|
orderReference,
|
|
108
112
|
paymentLink: url,
|
|
109
|
-
paymentLinkCreatedAt: now,
|
|
113
|
+
paymentLinkCreatedAt: now.toDate(),
|
|
110
114
|
});
|
|
111
115
|
logger?.info('Payment created', { userId, planId, orderReference });
|
|
112
116
|
return { paymentUrl: url };
|
|
@@ -122,14 +126,14 @@ async function createTokenPackPayment(userId, packId, currency, productName, pri
|
|
|
122
126
|
});
|
|
123
127
|
return { paymentUrl: cached.paymentLink };
|
|
124
128
|
}
|
|
125
|
-
const now =
|
|
129
|
+
const now = (0, moment_1.default)();
|
|
126
130
|
const orderReference = (0, orderReference_js_1.buildOrderReference)({
|
|
127
131
|
version: ORDER_REF_VERSION,
|
|
128
132
|
platform,
|
|
129
133
|
userId,
|
|
130
134
|
type: 'tkn',
|
|
131
135
|
tokenPackId: packId,
|
|
132
|
-
date: now,
|
|
136
|
+
date: now.toDate(),
|
|
133
137
|
});
|
|
134
138
|
let url;
|
|
135
139
|
try {
|
|
@@ -138,7 +142,7 @@ async function createTokenPackPayment(userId, packId, currency, productName, pri
|
|
|
138
142
|
merchantSecretKey: wayforpay.merchantSecretKey,
|
|
139
143
|
merchantDomainName: wayforpay.merchantDomainName,
|
|
140
144
|
orderReference,
|
|
141
|
-
orderDate:
|
|
145
|
+
orderDate: now.unix(),
|
|
142
146
|
productName: [productName],
|
|
143
147
|
productPrice: [price],
|
|
144
148
|
currency: currency,
|
|
@@ -158,7 +162,7 @@ async function createTokenPackPayment(userId, packId, currency, productName, pri
|
|
|
158
162
|
status: 'pending',
|
|
159
163
|
orderReference,
|
|
160
164
|
paymentLink: url,
|
|
161
|
-
paymentLinkCreatedAt: now,
|
|
165
|
+
paymentLinkCreatedAt: now.toDate(),
|
|
162
166
|
});
|
|
163
167
|
logger?.info('Payment created', { userId, packId, orderReference });
|
|
164
168
|
return { paymentUrl: url };
|
|
@@ -10,4 +10,7 @@ export declare function create(params: {
|
|
|
10
10
|
export declare function expire(userId: string): Promise<void>;
|
|
11
11
|
export declare function deactivate(userId: string): Promise<void>;
|
|
12
12
|
export declare function changePlan(userId: string, newPlanId: string): Promise<void>;
|
|
13
|
+
export declare function getById(subscriptionId: string): Promise<SubscriptionEntity | null>;
|
|
13
14
|
export declare function getActiveByUserId(userId: string): Promise<SubscriptionEntity | null>;
|
|
15
|
+
export declare function getByUser(userId: string, statuses: SubscriptionEntity['status'][]): Promise<SubscriptionEntity[]>;
|
|
16
|
+
export declare function getExistingByUserId(userId: string): Promise<SubscriptionEntity | null>;
|
|
@@ -32,12 +32,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.create = create;
|
|
37
40
|
exports.expire = expire;
|
|
38
41
|
exports.deactivate = deactivate;
|
|
39
42
|
exports.changePlan = changePlan;
|
|
43
|
+
exports.getById = getById;
|
|
40
44
|
exports.getActiveByUserId = getActiveByUserId;
|
|
45
|
+
exports.getByUser = getByUser;
|
|
46
|
+
exports.getExistingByUserId = getExistingByUserId;
|
|
47
|
+
const moment_1 = __importDefault(require("moment"));
|
|
41
48
|
const config_js_1 = require("../config.js");
|
|
42
49
|
const subscriptionsCollection = __importStar(require("../collections/subscriptions.js"));
|
|
43
50
|
const plansCollection = __importStar(require("../collections/plans.js"));
|
|
@@ -48,10 +55,10 @@ const errors_js_1 = require("../types/errors.js");
|
|
|
48
55
|
async function create(params) {
|
|
49
56
|
const { logger } = (0, config_js_1.getConfig)();
|
|
50
57
|
const { userId, planId, currency, productName } = params;
|
|
51
|
-
const existing = await subscriptionsCollection.
|
|
52
|
-
|
|
53
|
-
await subscriptionsCollection.update(
|
|
54
|
-
status: '
|
|
58
|
+
const existing = await subscriptionsCollection.getByUser(userId, ['active', 'cancelled']);
|
|
59
|
+
for (const sub of existing) {
|
|
60
|
+
await subscriptionsCollection.update(sub.id, {
|
|
61
|
+
status: 'expired',
|
|
55
62
|
});
|
|
56
63
|
}
|
|
57
64
|
const plan = await plansCollection.getById(planId);
|
|
@@ -117,10 +124,10 @@ async function changePlan(userId, newPlanId) {
|
|
|
117
124
|
if (newPlan.amount === currentPlan.amount) {
|
|
118
125
|
throw new errors_js_1.PaymentManagerError('Cannot change to a plan with the same price', 'SAME_PRICE_PLAN_CHANGE');
|
|
119
126
|
}
|
|
120
|
-
const now =
|
|
127
|
+
const now = (0, moment_1.default)();
|
|
121
128
|
const isUpgrade = newPlan.amount > currentPlan.amount;
|
|
122
129
|
if (isUpgrade) {
|
|
123
|
-
const proration = (0, proration_js_1.calculateUpgradeProration)(currentPlan, newPlan, subscription.startedAt, subscription.expiresAt, now);
|
|
130
|
+
const proration = (0, proration_js_1.calculateUpgradeProration)(currentPlan, newPlan, subscription.startedAt, subscription.expiresAt, now.toDate());
|
|
124
131
|
await subscriptionsCollection.update(subscription.id, {
|
|
125
132
|
planId: newPlanId,
|
|
126
133
|
expiresAt: proration.newExpiresAt,
|
|
@@ -133,7 +140,7 @@ async function changePlan(userId, newPlanId) {
|
|
|
133
140
|
amount: newPlan.amount,
|
|
134
141
|
currency: 'UAH',
|
|
135
142
|
regularMode: newPlan.regularMode,
|
|
136
|
-
dateBegin: now.
|
|
143
|
+
dateBegin: now.format('DD.MM.YYYY'),
|
|
137
144
|
dateEnd: '',
|
|
138
145
|
});
|
|
139
146
|
}
|
|
@@ -145,7 +152,7 @@ async function changePlan(userId, newPlanId) {
|
|
|
145
152
|
subscriptionId: subscription.id,
|
|
146
153
|
oldPlanId: subscription.planId,
|
|
147
154
|
newPlanId,
|
|
148
|
-
newExpiresAt: proration.newExpiresAt.toISOString(),
|
|
155
|
+
newExpiresAt: (0, moment_1.default)(proration.newExpiresAt).toISOString(),
|
|
149
156
|
});
|
|
150
157
|
}
|
|
151
158
|
else {
|
|
@@ -162,7 +169,7 @@ async function changePlan(userId, newPlanId) {
|
|
|
162
169
|
amount: newPlan.amount,
|
|
163
170
|
currency: 'UAH',
|
|
164
171
|
regularMode: newPlan.regularMode,
|
|
165
|
-
dateBegin:
|
|
172
|
+
dateBegin: (0, moment_1.default)(pendingPlanChangeDate).format('DD.MM.YYYY'),
|
|
166
173
|
dateEnd: '',
|
|
167
174
|
});
|
|
168
175
|
}
|
|
@@ -174,10 +181,20 @@ async function changePlan(userId, newPlanId) {
|
|
|
174
181
|
subscriptionId: subscription.id,
|
|
175
182
|
oldPlanId: subscription.planId,
|
|
176
183
|
newPlanId,
|
|
177
|
-
pendingPlanChangeDate: pendingPlanChangeDate.toISOString(),
|
|
184
|
+
pendingPlanChangeDate: (0, moment_1.default)(pendingPlanChangeDate).toISOString(),
|
|
178
185
|
});
|
|
179
186
|
}
|
|
180
187
|
}
|
|
188
|
+
async function getById(subscriptionId) {
|
|
189
|
+
return subscriptionsCollection.getById(subscriptionId);
|
|
190
|
+
}
|
|
181
191
|
async function getActiveByUserId(userId) {
|
|
182
192
|
return subscriptionsCollection.getActiveByUserId(userId);
|
|
183
193
|
}
|
|
194
|
+
async function getByUser(userId, statuses) {
|
|
195
|
+
return subscriptionsCollection.getByUser(userId, statuses);
|
|
196
|
+
}
|
|
197
|
+
async function getExistingByUserId(userId) {
|
|
198
|
+
const results = await subscriptionsCollection.getByUser(userId, ['active', 'cancelled', 'expired']);
|
|
199
|
+
return results[0] ?? null;
|
|
200
|
+
}
|
|
@@ -32,9 +32,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.handlePaymentCheck = handlePaymentCheck;
|
|
37
40
|
exports.handleSubscriptionDeactivationCheck = handleSubscriptionDeactivationCheck;
|
|
41
|
+
const moment_1 = __importDefault(require("moment"));
|
|
38
42
|
const config_js_1 = require("../config.js");
|
|
39
43
|
const orderReference_js_1 = require("../utils/orderReference.js");
|
|
40
44
|
const webhookResponse_js_1 = require("../utils/webhookResponse.js");
|
|
@@ -45,8 +49,8 @@ const subscriptionsCollection = __importStar(require("../collections/subscriptio
|
|
|
45
49
|
const plansCollection = __importStar(require("../collections/plans.js"));
|
|
46
50
|
const tokenPacksCollection = __importStar(require("../collections/tokenPacks.js"));
|
|
47
51
|
const userTokenBalancesCollection = __importStar(require("../collections/userTokenBalances.js"));
|
|
52
|
+
const wayforpay_api_1 = require("@misterhomer1992/wayforpay-api");
|
|
48
53
|
const errors_js_1 = require("../types/errors.js");
|
|
49
|
-
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
50
54
|
const SUCCESS_REASON_CODES = [1100, 4100];
|
|
51
55
|
async function handlePaymentCheck(reqBody, headers) {
|
|
52
56
|
const { wayforpay, webhookSecret, logger } = (0, config_js_1.getConfig)();
|
|
@@ -68,6 +72,9 @@ async function handlePaymentCheck(reqBody, headers) {
|
|
|
68
72
|
if (ref.type === 'sub') {
|
|
69
73
|
await handleSubscriptionPayment(ref.userId, ref.planId, orderReference);
|
|
70
74
|
}
|
|
75
|
+
else if (ref.type === 'upg') {
|
|
76
|
+
await handleUpgradePayment(ref.userId, ref.planId, orderReference);
|
|
77
|
+
}
|
|
71
78
|
else {
|
|
72
79
|
await handleTokenPackPayment(ref.userId, ref.tokenPackId, orderReference);
|
|
73
80
|
}
|
|
@@ -83,17 +90,17 @@ async function handleSubscriptionPayment(userId, planId, orderReference) {
|
|
|
83
90
|
const existing = await subscriptionsCollection.getActiveByUserId(userId);
|
|
84
91
|
if (existing) {
|
|
85
92
|
// Recurrent payment — check for pending downgrade first
|
|
86
|
-
const now =
|
|
87
|
-
if (existing.pendingPlanId && existing.pendingPlanChangeDate && existing.pendingPlanChangeDate
|
|
93
|
+
const now = (0, moment_1.default)();
|
|
94
|
+
if (existing.pendingPlanId && existing.pendingPlanChangeDate && (0, moment_1.default)(existing.pendingPlanChangeDate).isSameOrBefore(now)) {
|
|
88
95
|
// Apply pending downgrade
|
|
89
96
|
const newPlanId = existing.pendingPlanId;
|
|
90
97
|
const plan = await plansCollection.getById(newPlanId);
|
|
91
98
|
const days = plan ? (0, proration_js_1.cycleDays)(plan.regularMode) : (0, proration_js_1.cycleDays)('monthly');
|
|
92
|
-
const newExpiresAt =
|
|
99
|
+
const newExpiresAt = now.clone().add(days, 'days').toDate();
|
|
93
100
|
await subscriptionsCollection.update(existing.id, {
|
|
94
101
|
planId: newPlanId,
|
|
95
102
|
expiresAt: newExpiresAt,
|
|
96
|
-
startedAt: now,
|
|
103
|
+
startedAt: now.toDate(),
|
|
97
104
|
pendingPlanId: undefined,
|
|
98
105
|
pendingPlanChangeDate: undefined,
|
|
99
106
|
});
|
|
@@ -108,14 +115,14 @@ async function handleSubscriptionPayment(userId, planId, orderReference) {
|
|
|
108
115
|
// Extend subscription
|
|
109
116
|
const plan = await plansCollection.getById(existing.planId);
|
|
110
117
|
const days = plan ? (0, proration_js_1.cycleDays)(plan.regularMode) : (0, proration_js_1.cycleDays)('monthly');
|
|
111
|
-
const newExpiresAt =
|
|
118
|
+
const newExpiresAt = (0, moment_1.default)(existing.expiresAt).add(days, 'days').toDate();
|
|
112
119
|
await subscriptionsCollection.update(existing.id, {
|
|
113
120
|
expiresAt: newExpiresAt,
|
|
114
121
|
});
|
|
115
122
|
logger?.info('Subscription renewed', {
|
|
116
123
|
userId,
|
|
117
124
|
subscriptionId: existing.id,
|
|
118
|
-
newExpiresAt: newExpiresAt.toISOString(),
|
|
125
|
+
newExpiresAt: (0, moment_1.default)(newExpiresAt).toISOString(),
|
|
119
126
|
});
|
|
120
127
|
}
|
|
121
128
|
}
|
|
@@ -123,22 +130,71 @@ async function handleSubscriptionPayment(userId, planId, orderReference) {
|
|
|
123
130
|
// First payment — create new subscription
|
|
124
131
|
const plan = await plansCollection.getById(planId);
|
|
125
132
|
const days = plan ? (0, proration_js_1.cycleDays)(plan.regularMode) : (0, proration_js_1.cycleDays)('monthly');
|
|
126
|
-
const now =
|
|
127
|
-
const expiresAt =
|
|
133
|
+
const now = (0, moment_1.default)();
|
|
134
|
+
const expiresAt = now.clone().add(days, 'days').toDate();
|
|
128
135
|
const subscriptionId = await subscriptionsCollection.create({
|
|
129
136
|
userId,
|
|
130
137
|
planId,
|
|
131
138
|
status: 'active',
|
|
132
|
-
startedAt: now,
|
|
139
|
+
startedAt: now.toDate(),
|
|
133
140
|
expiresAt,
|
|
134
141
|
});
|
|
135
142
|
logger?.info('Subscription created', {
|
|
136
143
|
userId,
|
|
137
144
|
subscriptionId,
|
|
138
145
|
planId,
|
|
139
|
-
expiresAt: expiresAt.toISOString(),
|
|
146
|
+
expiresAt: (0, moment_1.default)(expiresAt).toISOString(),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function handleUpgradePayment(userId, newPlanId, orderReference) {
|
|
151
|
+
const { wayforpay, logger } = (0, config_js_1.getConfig)();
|
|
152
|
+
const existing = await subscriptionsCollection.getActiveByUserId(userId);
|
|
153
|
+
if (!existing) {
|
|
154
|
+
logger?.error('No active subscription found for upgrade', { userId, orderReference });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const newPlan = await plansCollection.getById(newPlanId);
|
|
158
|
+
if (!newPlan) {
|
|
159
|
+
logger?.error('New plan not found for upgrade', { newPlanId, orderReference });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const expiresAt = existing.expiresAt;
|
|
163
|
+
// Expire the old subscription
|
|
164
|
+
await subscriptionsCollection.update(existing.id, { status: 'expired' });
|
|
165
|
+
// Create a new subscription with the upgraded plan, keeping the same expiresAt
|
|
166
|
+
const now = (0, moment_1.default)();
|
|
167
|
+
const subscriptionId = await subscriptionsCollection.create({
|
|
168
|
+
userId,
|
|
169
|
+
planId: newPlanId,
|
|
170
|
+
status: 'active',
|
|
171
|
+
startedAt: now.toDate(),
|
|
172
|
+
expiresAt,
|
|
173
|
+
});
|
|
174
|
+
// Update recurring billing to the new plan
|
|
175
|
+
try {
|
|
176
|
+
await (0, wayforpay_api_1.editRegularPurchase)({
|
|
177
|
+
merchantAccount: wayforpay.merchantAccount,
|
|
178
|
+
merchantPassword: wayforpay.merchantSecretKey,
|
|
179
|
+
orderReference: subscriptionId,
|
|
180
|
+
amount: newPlan.amount,
|
|
181
|
+
currency: 'UAH',
|
|
182
|
+
regularMode: newPlan.regularMode,
|
|
183
|
+
dateBegin: (0, moment_1.default)(expiresAt).format('DD.MM.YYYY'),
|
|
184
|
+
dateEnd: '',
|
|
140
185
|
});
|
|
141
186
|
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
throw new errors_js_1.PaymentProviderError(`Failed to edit regular purchase for upgrade: ${error instanceof Error ? error.message : String(error)}`);
|
|
189
|
+
}
|
|
190
|
+
logger?.info('Subscription upgraded via payment', {
|
|
191
|
+
userId,
|
|
192
|
+
oldSubscriptionId: existing.id,
|
|
193
|
+
newSubscriptionId: subscriptionId,
|
|
194
|
+
oldPlanId: existing.planId,
|
|
195
|
+
newPlanId,
|
|
196
|
+
expiresAt: (0, moment_1.default)(expiresAt).toISOString(),
|
|
197
|
+
});
|
|
142
198
|
}
|
|
143
199
|
async function handleTokenPackPayment(userId, tokenPackId, orderReference) {
|
|
144
200
|
const { logger } = (0, config_js_1.getConfig)();
|
|
@@ -162,7 +218,7 @@ async function handleSubscriptionDeactivationCheck(headers) {
|
|
|
162
218
|
throw new errors_js_1.InvalidWebhookSecretError();
|
|
163
219
|
}
|
|
164
220
|
const subscriptions = await subscriptionsCollection.getAllActiveAndCancelled();
|
|
165
|
-
const now =
|
|
221
|
+
const now = (0, moment_1.default)();
|
|
166
222
|
const expired = [];
|
|
167
223
|
const downgraded = [];
|
|
168
224
|
for (const subscription of subscriptions) {
|
|
@@ -170,7 +226,7 @@ async function handleSubscriptionDeactivationCheck(headers) {
|
|
|
170
226
|
if (!plan)
|
|
171
227
|
continue;
|
|
172
228
|
const expirationWithGrace = (0, gracePeriod_js_1.getExpirationWithGrace)(subscription.expiresAt, plan.regularMode);
|
|
173
|
-
if (now
|
|
229
|
+
if (now.isAfter(expirationWithGrace)) {
|
|
174
230
|
await subscriptionsCollection.update(subscription.id, { status: 'expired' });
|
|
175
231
|
expired.push(subscription.userId);
|
|
176
232
|
logger?.info('Subscription expired by deactivation check', {
|
|
@@ -181,15 +237,15 @@ async function handleSubscriptionDeactivationCheck(headers) {
|
|
|
181
237
|
}
|
|
182
238
|
if (subscription.pendingPlanId &&
|
|
183
239
|
subscription.pendingPlanChangeDate &&
|
|
184
|
-
subscription.pendingPlanChangeDate
|
|
240
|
+
(0, moment_1.default)(subscription.pendingPlanChangeDate).isSameOrBefore(now)) {
|
|
185
241
|
const newPlanId = subscription.pendingPlanId;
|
|
186
242
|
const newPlan = await plansCollection.getById(newPlanId);
|
|
187
243
|
const days = newPlan ? (0, proration_js_1.cycleDays)(newPlan.regularMode) : (0, proration_js_1.cycleDays)('monthly');
|
|
188
|
-
const newExpiresAt =
|
|
244
|
+
const newExpiresAt = now.clone().add(days, 'days').toDate();
|
|
189
245
|
await subscriptionsCollection.update(subscription.id, {
|
|
190
246
|
planId: newPlanId,
|
|
191
247
|
expiresAt: newExpiresAt,
|
|
192
|
-
startedAt: now,
|
|
248
|
+
startedAt: now.toDate(),
|
|
193
249
|
pendingPlanId: undefined,
|
|
194
250
|
pendingPlanChangeDate: undefined,
|
|
195
251
|
});
|
|
@@ -2,7 +2,7 @@ export interface OrderReferenceParams {
|
|
|
2
2
|
version: string;
|
|
3
3
|
platform: string;
|
|
4
4
|
userId: string;
|
|
5
|
-
type: 'sub' | 'tkn';
|
|
5
|
+
type: 'sub' | 'tkn' | 'upg';
|
|
6
6
|
planId?: string;
|
|
7
7
|
tokenPackId?: string;
|
|
8
8
|
date: Date;
|
|
@@ -11,7 +11,7 @@ export interface ParsedOrderReference {
|
|
|
11
11
|
version: string;
|
|
12
12
|
platform: string;
|
|
13
13
|
userId: string;
|
|
14
|
-
type: 'sub' | 'tkn';
|
|
14
|
+
type: 'sub' | 'tkn' | 'upg';
|
|
15
15
|
planId?: string;
|
|
16
16
|
tokenPackId?: string;
|
|
17
17
|
date: Date;
|
package/dist/types/plan.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import moment from 'moment';
|
|
1
2
|
import type { SubscriptionPlanEntity } from '../types/plan.js';
|
|
2
|
-
export declare function getGracePeriod(regularMode: SubscriptionPlanEntity['regularMode']):
|
|
3
|
+
export declare function getGracePeriod(regularMode: SubscriptionPlanEntity['regularMode']): moment.Duration;
|
|
3
4
|
export declare function getExpirationWithGrace(expiresAt: Date, regularMode: SubscriptionPlanEntity['regularMode']): Date;
|