@misterhomer1992/miit-bot-payment 1.1.6 → 2.0.4

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 (248) hide show
  1. package/dist/config/ConfigurationManager.d.ts +64 -0
  2. package/dist/config/ConfigurationManager.d.ts.map +1 -0
  3. package/dist/config/ConfigurationManager.js +144 -0
  4. package/dist/config/ConfigurationManager.js.map +1 -0
  5. package/dist/config/defaults.d.ts +18 -0
  6. package/dist/config/defaults.d.ts.map +1 -0
  7. package/dist/config/defaults.js +26 -0
  8. package/dist/config/defaults.js.map +1 -0
  9. package/dist/config/environment.d.ts +38 -0
  10. package/dist/config/environment.d.ts.map +1 -0
  11. package/dist/config/environment.js +91 -0
  12. package/dist/config/environment.js.map +1 -0
  13. package/dist/config/index.d.ts +5 -0
  14. package/dist/config/index.d.ts.map +1 -0
  15. package/dist/config/index.js +18 -0
  16. package/dist/config/index.js.map +1 -0
  17. package/dist/config/types.d.ts +53 -0
  18. package/dist/config/types.d.ts.map +1 -0
  19. package/dist/config/types.js +3 -0
  20. package/dist/config/types.js.map +1 -0
  21. package/dist/index.d.ts +21 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +23 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/modules/cache/InMemoryCache.d.ts +17 -0
  26. package/dist/modules/cache/InMemoryCache.d.ts.map +1 -0
  27. package/dist/modules/cache/InMemoryCache.js +77 -0
  28. package/dist/modules/cache/InMemoryCache.js.map +1 -0
  29. package/dist/modules/cache/index.d.ts +3 -0
  30. package/dist/modules/cache/index.d.ts.map +1 -0
  31. package/dist/modules/cache/index.js +19 -0
  32. package/dist/modules/cache/index.js.map +1 -0
  33. package/dist/modules/cache/types.d.ts +52 -0
  34. package/dist/modules/cache/types.d.ts.map +1 -0
  35. package/dist/modules/cache/types.js +3 -0
  36. package/dist/modules/cache/types.js.map +1 -0
  37. package/dist/modules/errors/index.d.ts +2 -0
  38. package/dist/modules/errors/index.d.ts.map +1 -0
  39. package/dist/modules/errors/index.js +19 -0
  40. package/dist/modules/errors/index.js.map +1 -0
  41. package/dist/modules/errors/types.d.ts +112 -0
  42. package/dist/modules/errors/types.d.ts.map +1 -0
  43. package/dist/modules/errors/types.js +174 -0
  44. package/dist/modules/errors/types.js.map +1 -0
  45. package/dist/modules/payments/api.d.ts +63 -1
  46. package/dist/modules/payments/api.d.ts.map +1 -1
  47. package/dist/modules/payments/api.js +103 -1
  48. package/dist/modules/payments/api.js.map +1 -1
  49. package/dist/modules/payments/const.d.ts.map +1 -1
  50. package/dist/modules/payments/const.js +1 -0
  51. package/dist/modules/payments/const.js.map +1 -1
  52. package/dist/modules/payments/index.d.ts +8 -0
  53. package/dist/modules/payments/index.d.ts.map +1 -1
  54. package/dist/modules/payments/index.js +8 -0
  55. package/dist/modules/payments/index.js.map +1 -1
  56. package/dist/modules/payments/service.d.ts +42 -2
  57. package/dist/modules/payments/service.d.ts.map +1 -1
  58. package/dist/modules/payments/service.js +132 -3
  59. package/dist/modules/payments/service.js.map +1 -1
  60. package/dist/modules/payments/subscription-check-webhook.handler.d.ts +85 -0
  61. package/dist/modules/payments/subscription-check-webhook.handler.d.ts.map +1 -0
  62. package/dist/modules/payments/subscription-check-webhook.handler.js +155 -0
  63. package/dist/modules/payments/subscription-check-webhook.handler.js.map +1 -0
  64. package/dist/modules/payments/subscription-check-webhook.service.d.ts +59 -0
  65. package/dist/modules/payments/subscription-check-webhook.service.d.ts.map +1 -0
  66. package/dist/modules/payments/subscription-check-webhook.service.js +330 -0
  67. package/dist/modules/payments/subscription-check-webhook.service.js.map +1 -0
  68. package/dist/modules/payments/subscription-check-webhook.types.d.ts +25 -0
  69. package/dist/modules/payments/subscription-check-webhook.types.d.ts.map +1 -0
  70. package/dist/modules/payments/subscription-check-webhook.types.js +3 -0
  71. package/dist/modules/payments/subscription-check-webhook.types.js.map +1 -0
  72. package/dist/modules/payments/types.d.ts +69 -2
  73. package/dist/modules/payments/types.d.ts.map +1 -1
  74. package/dist/modules/payments/utils.d.ts +151 -5
  75. package/dist/modules/payments/utils.d.ts.map +1 -1
  76. package/dist/modules/payments/utils.js +253 -9
  77. package/dist/modules/payments/utils.js.map +1 -1
  78. package/dist/modules/payments/wayforpay.service.d.ts +39 -0
  79. package/dist/modules/payments/wayforpay.service.d.ts.map +1 -0
  80. package/dist/modules/payments/wayforpay.service.js +217 -0
  81. package/dist/modules/payments/wayforpay.service.js.map +1 -0
  82. package/dist/modules/payments/wayforpay.types.d.ts +115 -0
  83. package/dist/modules/payments/wayforpay.types.d.ts.map +1 -0
  84. package/dist/modules/payments/wayforpay.types.js +3 -0
  85. package/dist/modules/payments/wayforpay.types.js.map +1 -0
  86. package/dist/modules/payments/webhook.handler.d.ts +98 -0
  87. package/dist/modules/payments/webhook.handler.d.ts.map +1 -0
  88. package/dist/modules/payments/webhook.handler.js +153 -0
  89. package/dist/modules/payments/webhook.handler.js.map +1 -0
  90. package/dist/modules/payments/webhook.service.d.ts +99 -0
  91. package/dist/modules/payments/webhook.service.d.ts.map +1 -0
  92. package/dist/modules/payments/webhook.service.js +672 -0
  93. package/dist/modules/payments/webhook.service.js.map +1 -0
  94. package/dist/modules/payments/webhook.types.d.ts +35 -0
  95. package/dist/modules/payments/webhook.types.d.ts.map +1 -0
  96. package/dist/modules/payments/webhook.types.js +3 -0
  97. package/dist/modules/payments/webhook.types.js.map +1 -0
  98. package/dist/modules/subscription/change.service.d.ts +80 -0
  99. package/dist/modules/subscription/change.service.d.ts.map +1 -0
  100. package/dist/modules/subscription/change.service.js +226 -0
  101. package/dist/modules/subscription/change.service.js.map +1 -0
  102. package/dist/modules/subscription/index.d.ts +2 -0
  103. package/dist/modules/subscription/index.d.ts.map +1 -1
  104. package/dist/modules/subscription/index.js +2 -0
  105. package/dist/modules/subscription/index.js.map +1 -1
  106. package/dist/modules/subscription/service.d.ts +8 -1
  107. package/dist/modules/subscription/service.d.ts.map +1 -1
  108. package/dist/modules/subscription/service.js +59 -2
  109. package/dist/modules/subscription/service.js.map +1 -1
  110. package/dist/modules/subscription/status-check.handler.d.ts +117 -0
  111. package/dist/modules/subscription/status-check.handler.d.ts.map +1 -0
  112. package/dist/modules/subscription/status-check.handler.js +164 -0
  113. package/dist/modules/subscription/status-check.handler.js.map +1 -0
  114. package/dist/modules/subscription/types.d.ts +37 -1
  115. package/dist/modules/subscription/types.d.ts.map +1 -1
  116. package/dist/modules/subscriptionPlan/const.d.ts +5 -0
  117. package/dist/modules/subscriptionPlan/const.d.ts.map +1 -0
  118. package/dist/modules/subscriptionPlan/const.js +106 -0
  119. package/dist/modules/subscriptionPlan/const.js.map +1 -0
  120. package/dist/modules/subscriptionPlan/index.d.ts +5 -0
  121. package/dist/modules/subscriptionPlan/index.d.ts.map +1 -0
  122. package/dist/modules/subscriptionPlan/index.js +21 -0
  123. package/dist/modules/subscriptionPlan/index.js.map +1 -0
  124. package/dist/modules/subscriptionPlan/repository.d.ts +22 -0
  125. package/dist/modules/subscriptionPlan/repository.d.ts.map +1 -0
  126. package/dist/modules/subscriptionPlan/repository.js +95 -0
  127. package/dist/modules/subscriptionPlan/repository.js.map +1 -0
  128. package/dist/modules/subscriptionPlan/service.d.ts +21 -0
  129. package/dist/modules/subscriptionPlan/service.d.ts.map +1 -0
  130. package/dist/modules/subscriptionPlan/service.js +128 -0
  131. package/dist/modules/subscriptionPlan/service.js.map +1 -0
  132. package/dist/modules/subscriptionPlan/types.d.ts +40 -0
  133. package/dist/modules/subscriptionPlan/types.d.ts.map +1 -0
  134. package/dist/modules/subscriptionPlan/types.js +3 -0
  135. package/dist/modules/subscriptionPlan/types.js.map +1 -0
  136. package/dist/modules/token/const.d.ts +7 -0
  137. package/dist/modules/token/const.d.ts.map +1 -0
  138. package/dist/modules/token/const.js +66 -0
  139. package/dist/modules/token/const.js.map +1 -0
  140. package/dist/modules/token/index.d.ts +4 -0
  141. package/dist/modules/token/index.d.ts.map +1 -0
  142. package/dist/modules/token/index.js +20 -0
  143. package/dist/modules/token/index.js.map +1 -0
  144. package/dist/modules/token/service.d.ts +46 -0
  145. package/dist/modules/token/service.d.ts.map +1 -0
  146. package/dist/modules/token/service.js +249 -0
  147. package/dist/modules/token/service.js.map +1 -0
  148. package/dist/modules/token/types.d.ts +109 -0
  149. package/dist/modules/token/types.d.ts.map +1 -0
  150. package/dist/modules/token/types.js +3 -0
  151. package/dist/modules/token/types.js.map +1 -0
  152. package/dist/modules/tokenPack/const.d.ts +4 -0
  153. package/dist/modules/tokenPack/const.d.ts.map +1 -0
  154. package/dist/modules/tokenPack/const.js +10 -0
  155. package/dist/modules/tokenPack/const.js.map +1 -0
  156. package/dist/modules/tokenPack/index.d.ts +5 -0
  157. package/dist/modules/tokenPack/index.d.ts.map +1 -0
  158. package/dist/modules/tokenPack/index.js +21 -0
  159. package/dist/modules/tokenPack/index.js.map +1 -0
  160. package/dist/modules/tokenPack/repository.d.ts +32 -0
  161. package/dist/modules/tokenPack/repository.d.ts.map +1 -0
  162. package/dist/modules/tokenPack/repository.js +103 -0
  163. package/dist/modules/tokenPack/repository.js.map +1 -0
  164. package/dist/modules/tokenPack/service.d.ts +28 -0
  165. package/dist/modules/tokenPack/service.d.ts.map +1 -0
  166. package/dist/modules/tokenPack/service.js +106 -0
  167. package/dist/modules/tokenPack/service.js.map +1 -0
  168. package/dist/modules/tokenPack/types.d.ts +124 -0
  169. package/dist/modules/tokenPack/types.d.ts.map +1 -0
  170. package/dist/modules/tokenPack/types.js +3 -0
  171. package/dist/modules/tokenPack/types.js.map +1 -0
  172. package/package.json +9 -5
  173. package/src/config/ConfigurationManager.ts +159 -0
  174. package/src/config/defaults.ts +27 -0
  175. package/src/config/environment.ts +94 -0
  176. package/src/config/index.ts +22 -0
  177. package/src/config/types.ts +56 -0
  178. package/src/index.ts +29 -0
  179. package/src/modules/cache/InMemoryCache.ts +98 -0
  180. package/src/modules/cache/index.ts +2 -0
  181. package/src/modules/cache/types.ts +60 -0
  182. package/src/modules/cancellableAPI/utils.ts +60 -0
  183. package/src/modules/errors/index.ts +16 -0
  184. package/src/modules/errors/types.ts +201 -0
  185. package/src/modules/invoice/const.ts +7 -0
  186. package/src/modules/invoice/index.ts +4 -0
  187. package/src/modules/invoice/repository.ts +52 -0
  188. package/src/modules/invoice/service.ts +44 -0
  189. package/src/modules/invoice/types.ts +47 -0
  190. package/src/modules/logger/types.ts +8 -0
  191. package/src/modules/network/utils.ts +24 -0
  192. package/src/modules/payments/api.ts +289 -0
  193. package/src/modules/payments/const.ts +11 -0
  194. package/src/modules/payments/index.ts +14 -0
  195. package/src/modules/payments/repository.ts +125 -0
  196. package/src/modules/payments/service.test.ts +400 -0
  197. package/src/modules/payments/service.ts +365 -0
  198. package/src/modules/payments/subscription-check-webhook.handler.integration.test.ts +935 -0
  199. package/src/modules/payments/subscription-check-webhook.handler.ts +211 -0
  200. package/src/modules/payments/subscription-check-webhook.service.ts +398 -0
  201. package/src/modules/payments/subscription-check-webhook.types.ts +29 -0
  202. package/src/modules/payments/types.ts +193 -0
  203. package/src/modules/payments/utils.ts +428 -0
  204. package/src/modules/payments/wayforpay.service.test.ts +375 -0
  205. package/src/modules/payments/wayforpay.service.ts +284 -0
  206. package/src/modules/payments/wayforpay.types.ts +138 -0
  207. package/src/modules/payments/webhook.handler.integration.test.ts +975 -0
  208. package/src/modules/payments/webhook.handler.ts +219 -0
  209. package/src/modules/payments/webhook.service.ts +812 -0
  210. package/src/modules/payments/webhook.types.ts +38 -0
  211. package/src/modules/subscription/change.service.ts +317 -0
  212. package/src/modules/subscription/const.ts +9 -0
  213. package/src/modules/subscription/index.ts +5 -0
  214. package/src/modules/subscription/repository.ts +277 -0
  215. package/src/modules/subscription/service.test.ts +665 -0
  216. package/src/modules/subscription/service.ts +328 -0
  217. package/src/modules/subscription/status-check.handler.ts +254 -0
  218. package/src/modules/subscription/types.ts +267 -0
  219. package/src/modules/subscription/utils.ts +5 -0
  220. package/src/modules/subscriptionPlan/const.ts +106 -0
  221. package/src/modules/subscriptionPlan/index.ts +4 -0
  222. package/src/modules/subscriptionPlan/repository.ts +129 -0
  223. package/src/modules/subscriptionPlan/service.test.ts +401 -0
  224. package/src/modules/subscriptionPlan/service.ts +148 -0
  225. package/src/modules/subscriptionPlan/types.ts +67 -0
  226. package/src/modules/token/const.ts +64 -0
  227. package/src/modules/token/index.ts +3 -0
  228. package/src/modules/token/service.test.ts +499 -0
  229. package/src/modules/token/service.ts +297 -0
  230. package/src/modules/token/types.ts +124 -0
  231. package/src/modules/tokenPack/const.ts +9 -0
  232. package/src/modules/tokenPack/index.ts +4 -0
  233. package/src/modules/tokenPack/repository.ts +144 -0
  234. package/src/modules/tokenPack/service.ts +119 -0
  235. package/src/modules/tokenPack/types.ts +131 -0
  236. package/src/modules/user/index.ts +3 -0
  237. package/src/modules/user/types.ts +143 -0
  238. package/src/modules/user/userRepository.ts +64 -0
  239. package/src/modules/user/userService.ts +68 -0
  240. package/src/types/extend-express.d.ts +16 -0
  241. package/src/types/function.ts +5 -0
  242. package/src/types/utilities.ts +22 -0
  243. package/src/utils.ts +53 -0
  244. package/tsconfig.json +29 -0
  245. package/dist/modules/subscription/subscriptionPlan.d.ts +0 -4
  246. package/dist/modules/subscription/subscriptionPlan.d.ts.map +0 -1
  247. package/dist/modules/subscription/subscriptionPlan.js +0 -67
  248. package/dist/modules/subscription/subscriptionPlan.js.map +0 -1
@@ -0,0 +1,672 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WebhookHandlerService = void 0;
7
+ const moment_1 = __importDefault(require("moment"));
8
+ const APPROVED_STATUS = 'Approved';
9
+ const DECLINED_STATUS = 'Declined';
10
+ const EXPIRED_STATUS = 'Expired';
11
+ const wayforpay_service_1 = require("./wayforpay.service");
12
+ const service_1 = require("./service");
13
+ const service_2 = require("../invoice/service");
14
+ const service_3 = require("../subscription/service");
15
+ const service_4 = require("../subscriptionPlan/service");
16
+ const service_5 = require("../tokenPack/service");
17
+ const utils_1 = require("./utils");
18
+ const const_1 = require("../token/const");
19
+ const config_1 = require("../../config");
20
+ /**
21
+ * WebhookHandlerService processes payment confirmation webhooks from WayForPay.
22
+ * It handles signature verification, payment status updates, subscription activation,
23
+ * and user token balance initialization.
24
+ */
25
+ class WebhookHandlerService {
26
+ constructor({ logger, wayForPayService, paymentService, invoiceService, subscriptionService, subscriptionPlanService, tokenPackService, tokenPackPlans, gracePeriodConfig, }) {
27
+ this.logger = logger;
28
+ this.wayForPayService = wayForPayService || new wayforpay_service_1.WayForPayService({ logger });
29
+ this.paymentService = paymentService || new service_1.PaymentService({ logger });
30
+ this.invoiceService = invoiceService || new service_2.InvoiceService({ logger });
31
+ this.subscriptionService = subscriptionService || new service_3.SubscriptionService({ logger });
32
+ this.subscriptionPlanService = subscriptionPlanService || new service_4.SubscriptionPlanService({ logger });
33
+ this.tokenPackService = tokenPackService || new service_5.TokenPackService({ logger });
34
+ this.tokenPackPlans = tokenPackPlans || const_1.DEFAULT_TOKEN_PACK_PLANS;
35
+ this.gracePeriodConfig = gracePeriodConfig || this.loadGracePeriodConfig();
36
+ }
37
+ loadGracePeriodConfig() {
38
+ try {
39
+ const configManager = config_1.ConfigurationManager.getInstance();
40
+ return configManager.getGracePeriodConfig();
41
+ }
42
+ catch {
43
+ return config_1.DEFAULT_GRACE_PERIOD_CONFIG;
44
+ }
45
+ }
46
+ /**
47
+ * Processes a payment confirmation webhook from WayForPay.
48
+ * This method handles the complete webhook flow:
49
+ * 1. Verifies the signature to validate request authenticity
50
+ * 2. Creates an invoice record
51
+ * 3. Updates payment status
52
+ * 4. Activates subscription or renews existing one
53
+ * 5. Initializes user token balance based on plan
54
+ *
55
+ * @param callbackData - The callback data from WayForPay
56
+ * @returns Processing result with response to send back to WayForPay
57
+ */
58
+ async processPaymentWebhook(callbackData) {
59
+ const { orderReference, transactionStatus } = callbackData;
60
+ this.logger.info({
61
+ message: 'Processing WayForPay webhook',
62
+ payload: {
63
+ orderReference,
64
+ transactionStatus,
65
+ },
66
+ });
67
+ try {
68
+ // Step 1: Verify signature to validate request authenticity
69
+ const signatureResult = this.wayForPayService.verifyCallbackSignature(callbackData);
70
+ if (!signatureResult.isValid) {
71
+ this.logger.warning({
72
+ message: 'Invalid webhook signature',
73
+ payload: {
74
+ orderReference,
75
+ error: signatureResult.error,
76
+ },
77
+ });
78
+ return {
79
+ success: false,
80
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'decline'),
81
+ error: signatureResult.error || 'Invalid signature',
82
+ };
83
+ }
84
+ // Step 2: Check if this is an approved transaction
85
+ if (transactionStatus !== APPROVED_STATUS) {
86
+ this.logger.info({
87
+ message: 'Webhook received for non-approved transaction',
88
+ payload: {
89
+ orderReference,
90
+ transactionStatus,
91
+ },
92
+ });
93
+ // Handle declined or other statuses
94
+ if (transactionStatus === DECLINED_STATUS || transactionStatus === EXPIRED_STATUS) {
95
+ await this.handleFailedPayment(callbackData);
96
+ }
97
+ return {
98
+ success: true,
99
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'accept'),
100
+ };
101
+ }
102
+ // Step 3: Process based on payment type
103
+ if ((0, utils_1.isUpgradeOrderReference)(orderReference)) {
104
+ // Process upgrade payment
105
+ await this.processApprovedUpgradePayment(callbackData);
106
+ }
107
+ else if ((0, utils_1.isSubscriptionOrderReference)(orderReference)) {
108
+ // Process subscription payment
109
+ await this.processApprovedSubscriptionPayment(callbackData);
110
+ }
111
+ else if ((0, utils_1.isTokenPackOrderReference)(orderReference)) {
112
+ // Process token pack payment
113
+ await this.processApprovedTokenPackPayment(callbackData);
114
+ }
115
+ else {
116
+ this.logger.info({
117
+ message: 'Webhook received for unknown payment type',
118
+ payload: { orderReference },
119
+ });
120
+ }
121
+ return {
122
+ success: true,
123
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'accept'),
124
+ };
125
+ }
126
+ catch (error) {
127
+ this.logger.error({
128
+ message: 'Error processing webhook',
129
+ payload: {
130
+ orderReference,
131
+ error: JSON.stringify(error),
132
+ },
133
+ });
134
+ return {
135
+ success: false,
136
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'decline'),
137
+ error: error instanceof Error ? error.message : 'Unknown error',
138
+ };
139
+ }
140
+ }
141
+ /**
142
+ * Processes an approved subscription payment.
143
+ * Creates invoice, updates payment status, activates subscription,
144
+ * and initializes token balance.
145
+ */
146
+ async processApprovedSubscriptionPayment(callbackData) {
147
+ const { orderReference, merchantAccount, merchantSignature, reasonCode, reason, amount, currency } = callbackData;
148
+ // Parse order reference to extract user and plan info
149
+ const parsedReference = (0, utils_1.parseOrderReference)(orderReference);
150
+ if (!parsedReference) {
151
+ throw new Error(`Invalid order reference format: ${orderReference}`);
152
+ }
153
+ const userId = (0, utils_1.getOrderReferenceUserId)(orderReference);
154
+ const planId = (0, utils_1.getOrderReferenceItemId)(orderReference);
155
+ const { platform } = parsedReference;
156
+ if (!userId || !planId) {
157
+ throw new Error(`Could not extract userId or planId from order reference: ${orderReference}`);
158
+ }
159
+ // Check for duplicate webhook (idempotency)
160
+ const existingInvoice = await this.invoiceService.getByOrderReference(orderReference);
161
+ if (existingInvoice) {
162
+ this.logger.info({
163
+ message: 'Duplicate webhook received, already processed',
164
+ payload: { orderReference },
165
+ });
166
+ return;
167
+ }
168
+ // Create invoice record
169
+ // reasonCode from WayForPay is a string like '1100', convert to number for storage
170
+ const reasonCodeNumber = typeof reasonCode === 'string' ? parseInt(reasonCode, 10) : Number(reasonCode);
171
+ const invoice = await this.invoiceService.create({
172
+ merchantAccount,
173
+ orderReference,
174
+ merchantSignature,
175
+ reasonCode: reasonCodeNumber,
176
+ reason,
177
+ createdDate: moment_1.default.unix(callbackData.createdDate).toISOString(),
178
+ processingDate: moment_1.default.unix(callbackData.processingDate).toISOString(),
179
+ currency,
180
+ amount,
181
+ });
182
+ this.logger.info({
183
+ message: 'Invoice created',
184
+ payload: { invoiceId: invoice.id, orderReference },
185
+ });
186
+ // Update payment status to completed
187
+ const payment = await this.paymentService.getByOrderReference(orderReference);
188
+ if (payment && payment.id) {
189
+ await this.paymentService.changeStatus({
190
+ id: payment.id,
191
+ status: 'completed',
192
+ });
193
+ this.logger.info({
194
+ message: 'Payment status updated to completed',
195
+ payload: { paymentId: payment.id, orderReference },
196
+ });
197
+ }
198
+ // Get subscription plan to calculate expiration and tokens
199
+ const subscriptionPlan = await this.subscriptionPlanService.getById(planId);
200
+ if (!subscriptionPlan) {
201
+ throw new Error(`Subscription plan not found: ${planId}`);
202
+ }
203
+ // Calculate subscription expiration date
204
+ const startedAt = moment_1.default.utc().toISOString();
205
+ const expiresAt = this.calculateExpirationDate(subscriptionPlan.regularMode, subscriptionPlan.count);
206
+ // Check for existing active subscription
207
+ const existingSubscription = await this.subscriptionService.getByUser({
208
+ userId,
209
+ platform,
210
+ status: 'active',
211
+ });
212
+ if (existingSubscription) {
213
+ // Renew existing subscription
214
+ await this.renewSubscription(existingSubscription, expiresAt);
215
+ }
216
+ else {
217
+ // Activate new subscription
218
+ await this.activateNewSubscription({
219
+ userId,
220
+ platform,
221
+ planId,
222
+ startedAt,
223
+ expiresAt,
224
+ });
225
+ }
226
+ // Initialize user token balance based on plan credits
227
+ await this.initializeTokenBalance({
228
+ userId,
229
+ platform,
230
+ planId,
231
+ tokens: subscriptionPlan.credits,
232
+ });
233
+ this.logger.info({
234
+ message: 'Subscription activated successfully',
235
+ payload: {
236
+ userId,
237
+ platform,
238
+ planId,
239
+ expiresAt,
240
+ tokens: subscriptionPlan.credits,
241
+ },
242
+ });
243
+ }
244
+ /**
245
+ * Processes an approved token pack payment.
246
+ * Creates invoice, updates payment status, and adds tokens to user balance.
247
+ */
248
+ async processApprovedTokenPackPayment(callbackData) {
249
+ const { orderReference, merchantAccount, merchantSignature, reasonCode, reason, amount, currency } = callbackData;
250
+ // Parse order reference to extract user and pack info
251
+ const parsedReference = (0, utils_1.parseOrderReference)(orderReference);
252
+ if (!parsedReference) {
253
+ throw new Error(`Invalid order reference format: ${orderReference}`);
254
+ }
255
+ const userId = (0, utils_1.getOrderReferenceUserId)(orderReference);
256
+ const packId = (0, utils_1.getOrderReferenceItemId)(orderReference);
257
+ const { platform } = parsedReference;
258
+ if (!userId || !packId) {
259
+ throw new Error(`Could not extract userId or packId from order reference: ${orderReference}`);
260
+ }
261
+ // Check for duplicate webhook (idempotency)
262
+ const existingInvoice = await this.invoiceService.getByOrderReference(orderReference);
263
+ if (existingInvoice) {
264
+ this.logger.info({
265
+ message: 'Duplicate webhook received for token pack, already processed',
266
+ payload: { orderReference },
267
+ });
268
+ return;
269
+ }
270
+ // Create invoice record
271
+ const reasonCodeNumber = typeof reasonCode === 'string' ? parseInt(reasonCode, 10) : Number(reasonCode);
272
+ const invoice = await this.invoiceService.create({
273
+ merchantAccount,
274
+ orderReference,
275
+ merchantSignature,
276
+ reasonCode: reasonCodeNumber,
277
+ reason,
278
+ createdDate: moment_1.default.unix(callbackData.createdDate).toISOString(),
279
+ processingDate: moment_1.default.unix(callbackData.processingDate).toISOString(),
280
+ currency,
281
+ amount,
282
+ });
283
+ this.logger.info({
284
+ message: 'Invoice created for token pack',
285
+ payload: { invoiceId: invoice.id, orderReference },
286
+ });
287
+ // Update payment status to completed
288
+ const payment = await this.paymentService.getByOrderReference(orderReference);
289
+ if (payment && payment.id) {
290
+ await this.paymentService.changeStatus({
291
+ id: payment.id,
292
+ status: 'completed',
293
+ });
294
+ this.logger.info({
295
+ message: 'Token pack payment status updated to completed',
296
+ payload: { paymentId: payment.id, orderReference },
297
+ });
298
+ }
299
+ // Get token pack plan to determine token count
300
+ const tokenPackPlan = this.tokenPackPlans.find((p) => p.id === packId);
301
+ if (!tokenPackPlan) {
302
+ throw new Error(`Token pack plan not found: ${packId}`);
303
+ }
304
+ // Create token pack for user
305
+ await this.tokenPackService.create({
306
+ userId,
307
+ platform,
308
+ packId,
309
+ tokens: tokenPackPlan.tokens,
310
+ purchasedAt: moment_1.default.utc().toISOString(),
311
+ provider: 'wayforpay',
312
+ });
313
+ this.logger.info({
314
+ message: 'Token pack activated successfully',
315
+ payload: {
316
+ userId,
317
+ platform,
318
+ packId,
319
+ tokens: tokenPackPlan.tokens,
320
+ },
321
+ });
322
+ }
323
+ /**
324
+ * Processes an approved subscription upgrade payment.
325
+ * Updates subscription's planId, resets expiresAt, and adds new plan's tokens to existing balance.
326
+ */
327
+ async processApprovedUpgradePayment(callbackData) {
328
+ const { orderReference, merchantAccount, merchantSignature, reasonCode, reason, amount, currency } = callbackData;
329
+ // Parse upgrade order reference to extract both plan IDs
330
+ const upgradeParsed = (0, utils_1.parseUpgradeOrderReference)(orderReference);
331
+ if (!upgradeParsed) {
332
+ throw new Error(`Invalid upgrade order reference format: ${orderReference}`);
333
+ }
334
+ const { userId, platform, currentPlanId, newPlanId } = upgradeParsed;
335
+ if (!userId || !currentPlanId || !newPlanId) {
336
+ throw new Error(`Could not extract userId or plan IDs from upgrade order reference: ${orderReference}`);
337
+ }
338
+ // Check for duplicate webhook (idempotency)
339
+ const existingInvoice = await this.invoiceService.getByOrderReference(orderReference);
340
+ if (existingInvoice) {
341
+ this.logger.info({
342
+ message: 'Duplicate webhook received for upgrade, already processed',
343
+ payload: { orderReference },
344
+ });
345
+ return;
346
+ }
347
+ // Create invoice record
348
+ const reasonCodeNumber = typeof reasonCode === 'string' ? parseInt(reasonCode, 10) : Number(reasonCode);
349
+ const invoice = await this.invoiceService.create({
350
+ merchantAccount,
351
+ orderReference,
352
+ merchantSignature,
353
+ reasonCode: reasonCodeNumber,
354
+ reason,
355
+ createdDate: moment_1.default.unix(callbackData.createdDate).toISOString(),
356
+ processingDate: moment_1.default.unix(callbackData.processingDate).toISOString(),
357
+ currency,
358
+ amount,
359
+ });
360
+ this.logger.info({
361
+ message: 'Invoice created for upgrade',
362
+ payload: { invoiceId: invoice.id, orderReference },
363
+ });
364
+ // Update payment status to completed
365
+ const payment = await this.paymentService.getByOrderReference(orderReference);
366
+ if (payment && payment.id) {
367
+ await this.paymentService.changeStatus({
368
+ id: payment.id,
369
+ status: 'completed',
370
+ });
371
+ this.logger.info({
372
+ message: 'Upgrade payment status updated to completed',
373
+ payload: { paymentId: payment.id, orderReference },
374
+ });
375
+ }
376
+ // Get the new subscription plan
377
+ const newPlan = await this.subscriptionPlanService.getById(newPlanId);
378
+ if (!newPlan) {
379
+ throw new Error(`New subscription plan not found: ${newPlanId}`);
380
+ }
381
+ // Get existing active subscription
382
+ const existingSubscription = await this.subscriptionService.getByUser({
383
+ userId,
384
+ platform,
385
+ status: 'active',
386
+ });
387
+ if (!existingSubscription) {
388
+ this.logger.warning({
389
+ message: 'No active subscription found for upgrade, treating as new subscription',
390
+ payload: { userId, platform, orderReference },
391
+ });
392
+ // Fall back to creating a new subscription
393
+ const startedAt = moment_1.default.utc().toISOString();
394
+ const expiresAt = this.calculateExpirationDate(newPlan.regularMode, newPlan.count);
395
+ await this.activateNewSubscription({
396
+ userId,
397
+ platform,
398
+ planId: newPlanId,
399
+ startedAt,
400
+ expiresAt,
401
+ });
402
+ // Initialize token balance with new plan's credits
403
+ await this.initializeTokenBalance({
404
+ userId,
405
+ platform,
406
+ planId: newPlanId,
407
+ tokens: newPlan.credits,
408
+ });
409
+ return;
410
+ }
411
+ // Calculate new expiration date (full new period from now)
412
+ const startedAt = moment_1.default.utc().toISOString();
413
+ const expiresAt = this.calculateExpirationDate(newPlan.regularMode, newPlan.count);
414
+ // Update subscription: change planId, reset expiresAt and startedAt, ensure active status
415
+ await this.subscriptionService.updateFieldsById({
416
+ subscriptionId: existingSubscription.id,
417
+ fields: [
418
+ ['planId', newPlanId],
419
+ ['expiresAt', expiresAt],
420
+ ['startedAt', startedAt],
421
+ ['status', 'active'],
422
+ ],
423
+ });
424
+ this.logger.info({
425
+ message: 'Subscription upgraded',
426
+ payload: {
427
+ subscriptionId: existingSubscription.id,
428
+ previousPlanId: existingSubscription.planId,
429
+ newPlanId,
430
+ previousExpiresAt: existingSubscription.expiresAt,
431
+ newExpiresAt: expiresAt,
432
+ },
433
+ });
434
+ // Add new plan's tokens to existing balance (keep existing + add new)
435
+ await this.initializeTokenBalance({
436
+ userId,
437
+ platform,
438
+ planId: newPlanId,
439
+ tokens: newPlan.credits,
440
+ });
441
+ this.logger.info({
442
+ message: 'Subscription upgrade completed successfully',
443
+ payload: {
444
+ userId,
445
+ platform,
446
+ previousPlanId: currentPlanId,
447
+ newPlanId,
448
+ expiresAt,
449
+ tokensAdded: newPlan.credits,
450
+ },
451
+ });
452
+ }
453
+ /**
454
+ * Activates a new subscription for the user.
455
+ */
456
+ async activateNewSubscription(params) {
457
+ const subscriptionData = {
458
+ userId: params.userId,
459
+ platform: params.platform,
460
+ planId: params.planId,
461
+ startedAt: params.startedAt,
462
+ expiresAt: params.expiresAt,
463
+ provider: 'wayforpay',
464
+ };
465
+ await this.subscriptionService.activateSubscription(subscriptionData);
466
+ this.logger.info({
467
+ message: 'New subscription activated',
468
+ payload: {
469
+ userId: params.userId,
470
+ platform: params.platform,
471
+ planId: params.planId,
472
+ },
473
+ });
474
+ }
475
+ /**
476
+ * Renews an existing subscription by extending its expiration date.
477
+ */
478
+ async renewSubscription(existingSubscription, newExpiresAt) {
479
+ // If the subscription hasn't expired yet, extend from current expiration
480
+ const currentExpiration = moment_1.default.utc(existingSubscription.expiresAt);
481
+ const now = moment_1.default.utc();
482
+ let extendedExpiresAt;
483
+ if (currentExpiration.isAfter(now)) {
484
+ // Extend from current expiration date
485
+ const durationToAdd = moment_1.default.utc(newExpiresAt).diff(moment_1.default.utc(), 'milliseconds');
486
+ extendedExpiresAt = currentExpiration.add(durationToAdd, 'milliseconds').toISOString();
487
+ }
488
+ else {
489
+ // Subscription has expired, use the new expiration date
490
+ extendedExpiresAt = newExpiresAt;
491
+ }
492
+ await this.subscriptionService.renewSubscription({
493
+ userId: existingSubscription.userId,
494
+ platform: existingSubscription.platform,
495
+ expiresAt: extendedExpiresAt,
496
+ });
497
+ this.logger.info({
498
+ message: 'Subscription renewed',
499
+ payload: {
500
+ subscriptionId: existingSubscription.id,
501
+ previousExpiresAt: existingSubscription.expiresAt,
502
+ newExpiresAt: extendedExpiresAt,
503
+ },
504
+ });
505
+ }
506
+ /**
507
+ * Initializes user token balance by creating a token pack.
508
+ */
509
+ async initializeTokenBalance(params) {
510
+ const { userId, platform, planId, tokens } = params;
511
+ await this.tokenPackService.create({
512
+ userId,
513
+ platform,
514
+ packId: planId,
515
+ tokens,
516
+ purchasedAt: moment_1.default.utc().toISOString(),
517
+ provider: 'wayforpay',
518
+ });
519
+ this.logger.info({
520
+ message: 'Token balance initialized',
521
+ payload: {
522
+ userId,
523
+ platform,
524
+ packId: planId,
525
+ tokens,
526
+ },
527
+ });
528
+ }
529
+ /**
530
+ * Calculates the subscription expiration date based on the regular mode.
531
+ */
532
+ calculateExpirationDate(regularMode, count) {
533
+ const now = moment_1.default.utc();
534
+ switch (regularMode) {
535
+ case 'daily':
536
+ return now.add(count, 'days').toISOString();
537
+ case 'monthly':
538
+ return now.add(count, 'months').toISOString();
539
+ case 'yearly':
540
+ return now.add(count, 'years').toISOString();
541
+ default:
542
+ return now.add(count, 'months').toISOString();
543
+ }
544
+ }
545
+ /**
546
+ * Handles failed or declined payments.
547
+ * For recurring subscription renewals, this triggers deactivation of the subscription.
548
+ */
549
+ async handleFailedPayment(callbackData) {
550
+ const { orderReference } = callbackData;
551
+ const payment = await this.paymentService.getByOrderReference(orderReference);
552
+ if (payment && payment.id) {
553
+ await this.paymentService.changeStatus({
554
+ id: payment.id,
555
+ status: 'failed',
556
+ });
557
+ this.logger.info({
558
+ message: 'Payment status updated to failed',
559
+ payload: {
560
+ paymentId: payment.id,
561
+ orderReference,
562
+ reason: callbackData.reason,
563
+ },
564
+ });
565
+ }
566
+ // Handle failed recurring renewal - deactivate subscription
567
+ if (this.isRecurringRenewal(callbackData) && (0, utils_1.isSubscriptionOrderReference)(orderReference)) {
568
+ await this.handleFailedRenewal(callbackData);
569
+ }
570
+ }
571
+ /**
572
+ * Checks if the payment callback is for a recurring renewal payment.
573
+ * WayForPay sets regularOn field for recurring payments.
574
+ */
575
+ isRecurringRenewal(callbackData) {
576
+ // regularOn is set by WayForPay for recurring payment callbacks
577
+ return !!callbackData.regularOn;
578
+ }
579
+ /**
580
+ * Handles a failed recurring renewal by cancelling or deactivating the subscription.
581
+ * Respects grace period configuration before full deactivation.
582
+ */
583
+ async handleFailedRenewal(callbackData) {
584
+ const { orderReference, reason, reasonCode } = callbackData;
585
+ const parsedReference = (0, utils_1.parseOrderReference)(orderReference);
586
+ if (!parsedReference) {
587
+ this.logger.warning({
588
+ message: 'Could not parse order reference for failed renewal',
589
+ payload: { orderReference },
590
+ });
591
+ return;
592
+ }
593
+ const userId = (0, utils_1.getOrderReferenceUserId)(orderReference);
594
+ const { platform } = parsedReference;
595
+ if (!userId) {
596
+ this.logger.warning({
597
+ message: 'Could not extract userId from order reference for failed renewal',
598
+ payload: { orderReference },
599
+ });
600
+ return;
601
+ }
602
+ // Find active subscription for user
603
+ const existingSubscription = await this.subscriptionService.getByUser({
604
+ userId,
605
+ platform,
606
+ status: 'active',
607
+ });
608
+ if (!existingSubscription) {
609
+ this.logger.info({
610
+ message: 'No active subscription found for failed renewal',
611
+ payload: { userId, platform, orderReference },
612
+ });
613
+ return;
614
+ }
615
+ // Check if subscription has expired past the grace period
616
+ const now = moment_1.default.utc();
617
+ const expiresAt = moment_1.default.utc(existingSubscription.expiresAt);
618
+ const gracePeriodMs = this.gracePeriodConfig.enabled ? this.gracePeriodConfig.durationMs : 0;
619
+ const gracePeriodEnd = expiresAt.clone().add(gracePeriodMs, 'milliseconds');
620
+ if (now.isAfter(gracePeriodEnd)) {
621
+ // Subscription has expired past grace period - deactivate it
622
+ await this.subscriptionService.deactivateSubscription({ id: existingSubscription.id });
623
+ this.logger.info({
624
+ message: 'Subscription deactivated due to failed renewal (past grace period)',
625
+ payload: {
626
+ subscriptionId: existingSubscription.id,
627
+ userId,
628
+ platform,
629
+ orderReference,
630
+ reason,
631
+ reasonCode,
632
+ expiresAt: existingSubscription.expiresAt,
633
+ gracePeriodMs,
634
+ gracePeriodEnd: gracePeriodEnd.toISOString(),
635
+ },
636
+ });
637
+ }
638
+ else if (expiresAt.isBefore(now)) {
639
+ // Subscription has expired but within grace period - cancel (mark for future deactivation)
640
+ await this.subscriptionService.cancelSubscription({ id: existingSubscription.id });
641
+ this.logger.info({
642
+ message: 'Subscription cancelled due to failed renewal (within grace period)',
643
+ payload: {
644
+ subscriptionId: existingSubscription.id,
645
+ userId,
646
+ platform,
647
+ orderReference,
648
+ expiresAt: existingSubscription.expiresAt,
649
+ gracePeriodEnd: gracePeriodEnd.toISOString(),
650
+ reason,
651
+ reasonCode,
652
+ },
653
+ });
654
+ }
655
+ else {
656
+ this.logger.info({
657
+ message: 'Failed renewal for subscription that has not yet expired',
658
+ payload: {
659
+ subscriptionId: existingSubscription.id,
660
+ userId,
661
+ platform,
662
+ orderReference,
663
+ expiresAt: existingSubscription.expiresAt,
664
+ reason,
665
+ reasonCode,
666
+ },
667
+ });
668
+ }
669
+ }
670
+ }
671
+ exports.WebhookHandlerService = WebhookHandlerService;
672
+ //# sourceMappingURL=webhook.service.js.map