@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.
Files changed (41) hide show
  1. package/dist/collections/payments.js +6 -2
  2. package/dist/collections/plans.js +7 -3
  3. package/dist/collections/subscriptions.d.ts +2 -1
  4. package/dist/collections/subscriptions.js +21 -10
  5. package/dist/collections/tokenPacks.js +6 -2
  6. package/dist/collections/userTokenBalances.js +7 -3
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +2 -0
  9. package/dist/services/paymentService.js +14 -10
  10. package/dist/services/subscriptionService.d.ts +3 -0
  11. package/dist/services/subscriptionService.js +27 -10
  12. package/dist/services/webhookService.js +72 -16
  13. package/dist/types/orderReference.d.ts +2 -2
  14. package/dist/types/plan.d.ts +1 -0
  15. package/dist/utils/gracePeriod.d.ts +2 -1
  16. package/dist/utils/gracePeriod.js +8 -6
  17. package/dist/utils/orderReference.js +11 -7
  18. package/dist/utils/proration.js +7 -4
  19. package/dist/utils/webhookResponse.js +5 -1
  20. package/package.json +3 -2
  21. package/src/collections/payments.ts +3 -2
  22. package/src/collections/plans.ts +4 -3
  23. package/src/collections/subscriptions.ts +21 -11
  24. package/src/collections/tokenPacks.ts +3 -2
  25. package/src/collections/userTokenBalances.ts +4 -3
  26. package/src/index.ts +4 -0
  27. package/src/services/deactivationCheckHandler.test.ts +19 -20
  28. package/src/services/paymentCheckHandler.test.ts +33 -34
  29. package/src/services/paymentService.ts +11 -10
  30. package/src/services/subscriptionService.ts +29 -11
  31. package/src/services/webhookService.ts +78 -17
  32. package/src/types/orderReference.ts +2 -2
  33. package/src/types/plan.ts +1 -0
  34. package/src/utils/gracePeriod.test.ts +11 -13
  35. package/src/utils/gracePeriod.ts +6 -8
  36. package/src/utils/orderReference.test.ts +7 -6
  37. package/src/utils/orderReference.ts +9 -8
  38. package/src/utils/proration.test.ts +23 -23
  39. package/src/utils/proration.ts +4 -5
  40. package/src/utils/webhookResponse.test.ts +6 -5
  41. 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 = new Date();
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: new Date(),
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 = new Date();
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: new Date(),
42
+ updatedAt: (0, moment_1.default)().toDate(),
39
43
  });
40
44
  }
41
45
  async function upsert(plan) {
42
- const now = new Date();
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 = new Date();
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: new Date(),
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 = new Date();
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: new Date(),
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: new Date(0),
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: new Date(),
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: new Date(),
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 * 60 * 1000; // 5 minutes
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 Date.now() - createdAt.getTime() < CACHE_TTL_MS;
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 = new Date();
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: Math.floor(now.getTime() / 1000),
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 = new Date();
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: Math.floor(now.getTime() / 1000),
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.getActiveByUserId(userId);
52
- if (existing) {
53
- await subscriptionsCollection.update(existing.id, {
54
- status: 'cancelled',
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 = new Date();
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.toISOString().split('T')[0],
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: pendingPlanChangeDate.toISOString().split('T')[0],
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 = new Date();
87
- if (existing.pendingPlanId && existing.pendingPlanChangeDate && existing.pendingPlanChangeDate <= now) {
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 = new Date(now.getTime() + days * MS_PER_DAY);
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 = new Date(existing.expiresAt.getTime() + days * MS_PER_DAY);
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 = new Date();
127
- const expiresAt = new Date(now.getTime() + days * MS_PER_DAY);
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 = new Date();
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 > expirationWithGrace) {
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 <= now) {
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 = new Date(now.getTime() + days * MS_PER_DAY);
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;
@@ -2,6 +2,7 @@ export interface SubscriptionPlanEntity {
2
2
  id: string;
3
3
  name: string;
4
4
  amount: number;
5
+ credits: number;
5
6
  regularMode: 'daily' | 'monthly' | 'yearly';
6
7
  description?: string;
7
8
  isActive: boolean;
@@ -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']): number;
3
+ export declare function getGracePeriod(regularMode: SubscriptionPlanEntity['regularMode']): moment.Duration;
3
4
  export declare function getExpirationWithGrace(expiresAt: Date, regularMode: SubscriptionPlanEntity['regularMode']): Date;