@misterhomer1992/miit-bot-payment 1.1.7 → 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 +58 -1
  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,211 @@
1
+ import { Logger } from '../logger/types';
2
+ import { SubscriptionCheckWebhookService } from './subscription-check-webhook.service';
3
+ import type { WayForPayCallbackData } from './wayforpay.types';
4
+ import type { WebhookRequest, WebhookResponse } from './webhook.handler';
5
+ import type {
6
+ ISubscriptionCheckWebhookService,
7
+ SubscriptionCheckWebhookResult,
8
+ } from './subscription-check-webhook.types';
9
+
10
+ /**
11
+ * Configuration options for the subscription check webhook handler.
12
+ */
13
+ interface SubscriptionCheckWebhookHandlerConfig {
14
+ logger: Logger;
15
+ webhookService?: ISubscriptionCheckWebhookService;
16
+ }
17
+
18
+ /**
19
+ * Result of parsing the webhook request body.
20
+ */
21
+ interface ParseResult {
22
+ success: boolean;
23
+ data?: WayForPayCallbackData;
24
+ error?: string;
25
+ }
26
+
27
+ /**
28
+ * SubscriptionCheckWebhookHandler provides HTTP request handling for WayForPay
29
+ * subscription status check callbacks. It validates incoming requests and delegates
30
+ * processing to the SubscriptionCheckWebhookService.
31
+ *
32
+ * Usage with Express:
33
+ * ```typescript
34
+ * const handler = new SubscriptionCheckWebhookHandler({ logger });
35
+ * app.post('/webhook/subscription-check', (req, res) => handler.handleRequest(req, res));
36
+ * ```
37
+ *
38
+ * Usage with Cloud Functions:
39
+ * ```typescript
40
+ * const handler = new SubscriptionCheckWebhookHandler({ logger });
41
+ * export const subscriptionCheckWebhook = (req, res) => handler.handleRequest(req, res);
42
+ * ```
43
+ */
44
+ export class SubscriptionCheckWebhookHandler {
45
+ private readonly logger: Logger;
46
+ private readonly webhookService: ISubscriptionCheckWebhookService;
47
+
48
+ constructor(config: SubscriptionCheckWebhookHandlerConfig) {
49
+ this.logger = config.logger;
50
+ this.webhookService =
51
+ config.webhookService ||
52
+ new SubscriptionCheckWebhookService({
53
+ logger: config.logger,
54
+ });
55
+ }
56
+
57
+ /**
58
+ * Handles an incoming HTTP request from WayForPay for subscription checks.
59
+ * Validates the request body, processes the subscription check callback,
60
+ * and returns the appropriate response.
61
+ *
62
+ * @param req - The HTTP request object
63
+ * @param res - The HTTP response object
64
+ */
65
+ public async handleRequest(req: WebhookRequest, res: WebhookResponse): Promise<void> {
66
+ this.logger.info({
67
+ message: 'Received WayForPay subscription check webhook request',
68
+ payload: {},
69
+ });
70
+
71
+ // Parse and validate the request body
72
+ const parseResult = this.parseRequestBody(req.body);
73
+
74
+ if (!parseResult.success || !parseResult.data) {
75
+ this.logger.warning({
76
+ message: 'Invalid subscription check webhook request body',
77
+ payload: { error: parseResult.error },
78
+ });
79
+
80
+ res.status(400).json({
81
+ error: parseResult.error || 'Invalid request body',
82
+ });
83
+ return;
84
+ }
85
+
86
+ const callbackData = parseResult.data;
87
+
88
+ // Process the webhook
89
+ const result = await this.webhookService.processSubscriptionCheckWebhook(callbackData);
90
+
91
+ // Return the response to WayForPay
92
+ this.sendResponse(res, result);
93
+ }
94
+
95
+ /**
96
+ * Processes callback data directly without HTTP request parsing.
97
+ * Useful for testing or when data is already parsed.
98
+ *
99
+ * @param callbackData - The parsed callback data from WayForPay
100
+ * @returns Processing result with response to send back to WayForPay
101
+ */
102
+ public async processCallback(
103
+ callbackData: WayForPayCallbackData,
104
+ ): Promise<SubscriptionCheckWebhookResult> {
105
+ return this.webhookService.processSubscriptionCheckWebhook(callbackData);
106
+ }
107
+
108
+ /**
109
+ * Parses and validates the webhook request body.
110
+ */
111
+ private parseRequestBody(body: unknown): ParseResult {
112
+ if (!body || typeof body !== 'object') {
113
+ return {
114
+ success: false,
115
+ error: 'Request body must be a JSON object',
116
+ };
117
+ }
118
+
119
+ const data = body as Record<string, unknown>;
120
+
121
+ // Validate required fields for subscription check webhook
122
+ const requiredFields = [
123
+ 'merchantAccount',
124
+ 'orderReference',
125
+ 'merchantSignature',
126
+ 'amount',
127
+ 'currency',
128
+ 'transactionStatus',
129
+ ];
130
+
131
+ const missingFields = requiredFields.filter(
132
+ (field) => data[field] === undefined || data[field] === null,
133
+ );
134
+
135
+ if (missingFields.length > 0) {
136
+ return {
137
+ success: false,
138
+ error: `Missing required fields: ${missingFields.join(', ')}`,
139
+ };
140
+ }
141
+
142
+ // Validate transaction status
143
+ const validStatuses = ['Approved', 'Declined', 'Expired', 'Pending', 'InProcessing', 'Refunded'];
144
+ if (!validStatuses.includes(data.transactionStatus as string)) {
145
+ return {
146
+ success: false,
147
+ error: `Invalid transaction status: ${data.transactionStatus}`,
148
+ };
149
+ }
150
+
151
+ return {
152
+ success: true,
153
+ data: data as unknown as WayForPayCallbackData,
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Sends the response back to WayForPay.
159
+ * WayForPay expects a 200 status with a signed response body.
160
+ */
161
+ private sendResponse(res: WebhookResponse, result: SubscriptionCheckWebhookResult): void {
162
+ // WayForPay expects a 200 status code even for declined transactions
163
+ // The accept/decline status is communicated in the response body
164
+ res.status(200).json(result.response);
165
+
166
+ this.logger.info({
167
+ message: 'Subscription check webhook response sent',
168
+ payload: {
169
+ success: result.success,
170
+ status: result.response.status,
171
+ orderReference: result.response.orderReference,
172
+ },
173
+ });
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Creates a subscription check webhook handler function for use with serverless platforms.
179
+ * Returns a function that can be directly used as a Cloud Function or Lambda handler.
180
+ *
181
+ * @param config - Configuration options for the webhook handler
182
+ * @returns An async function that handles webhook requests
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * // Firebase Cloud Functions
187
+ * import * as functions from 'firebase-functions';
188
+ * import { createSubscriptionCheckWebhookHandler } from 'miit-bot-payment';
189
+ *
190
+ * const logger = { info: console.log, warning: console.warn, error: console.error };
191
+ * export const subscriptionCheckWebhook = functions.https.onRequest(
192
+ * createSubscriptionCheckWebhookHandler({ logger })
193
+ * );
194
+ * ```
195
+ */
196
+ export function createSubscriptionCheckWebhookHandler(
197
+ config: SubscriptionCheckWebhookHandlerConfig,
198
+ ): (req: WebhookRequest, res: WebhookResponse) => Promise<void> {
199
+ const handler = new SubscriptionCheckWebhookHandler(config);
200
+ return (req: WebhookRequest, res: WebhookResponse) => handler.handleRequest(req, res);
201
+ }
202
+
203
+ /**
204
+ * Type export for the webhook handler function signature.
205
+ */
206
+ export type SubscriptionCheckWebhookHandlerFunction = (
207
+ req: WebhookRequest,
208
+ res: WebhookResponse,
209
+ ) => Promise<void>;
210
+
211
+ export type { SubscriptionCheckWebhookHandlerConfig };
@@ -0,0 +1,398 @@
1
+ import moment from 'moment';
2
+ import type { ResponseTransactionStatus } from '@misterhomer1992/wayforpay-api';
3
+ import { Logger } from '../logger/types';
4
+
5
+ const APPROVED_STATUS: ResponseTransactionStatus = 'Approved';
6
+ const DECLINED_STATUS: ResponseTransactionStatus = 'Declined';
7
+ const EXPIRED_STATUS: ResponseTransactionStatus = 'Expired';
8
+
9
+ import { WayForPayService } from './wayforpay.service';
10
+ import { SubscriptionService } from '../subscription/service';
11
+ import { SubscriptionPlanService } from '../subscriptionPlan/service';
12
+ import {
13
+ parseOrderReference,
14
+ isSubscriptionOrderReference,
15
+ getOrderReferenceUserId,
16
+ getOrderReferenceItemId,
17
+ } from './utils';
18
+ import type { WayForPayCallbackData, IWayForPayService } from './wayforpay.types';
19
+ import type { ISubscriptionService, SubscriptionEntity } from '../subscription/types';
20
+ import type { ISubscriptionPlanService } from '../subscriptionPlan/types';
21
+ import type {
22
+ SubscriptionCheckWebhookResult,
23
+ ISubscriptionCheckWebhookService,
24
+ } from './subscription-check-webhook.types';
25
+ import { ConfigurationManager, GracePeriodConfig, DEFAULT_GRACE_PERIOD_CONFIG } from '../../config';
26
+
27
+ /**
28
+ * SubscriptionCheckWebhookService processes subscription status check webhooks from WayForPay.
29
+ * It handles signature verification, renewal status checks, and subscription updates.
30
+ *
31
+ * WayForPay sends subscription check webhooks for recurring payments to verify
32
+ * if the subscription should be renewed or deactivated.
33
+ */
34
+ export class SubscriptionCheckWebhookService implements ISubscriptionCheckWebhookService {
35
+ private readonly logger: Logger;
36
+ private readonly wayForPayService: IWayForPayService;
37
+ private readonly subscriptionService: ISubscriptionService;
38
+ private readonly subscriptionPlanService: ISubscriptionPlanService;
39
+ private readonly gracePeriodConfig: GracePeriodConfig;
40
+
41
+ constructor({
42
+ logger,
43
+ wayForPayService,
44
+ subscriptionService,
45
+ subscriptionPlanService,
46
+ gracePeriodConfig,
47
+ }: {
48
+ logger: Logger;
49
+ wayForPayService?: IWayForPayService;
50
+ subscriptionService?: ISubscriptionService;
51
+ subscriptionPlanService?: ISubscriptionPlanService;
52
+ gracePeriodConfig?: GracePeriodConfig;
53
+ }) {
54
+ this.logger = logger;
55
+ this.wayForPayService = wayForPayService || new WayForPayService({ logger });
56
+ this.subscriptionService = subscriptionService || new SubscriptionService({ logger });
57
+ this.subscriptionPlanService = subscriptionPlanService || new SubscriptionPlanService({ logger });
58
+ this.gracePeriodConfig = gracePeriodConfig || this.loadGracePeriodConfig();
59
+ }
60
+
61
+ private loadGracePeriodConfig(): GracePeriodConfig {
62
+ try {
63
+ const configManager = ConfigurationManager.getInstance();
64
+ return configManager.getGracePeriodConfig();
65
+ } catch {
66
+ return DEFAULT_GRACE_PERIOD_CONFIG;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Processes a subscription check webhook from WayForPay.
72
+ * This method handles the complete webhook flow:
73
+ * 1. Verifies the signature to validate request authenticity
74
+ * 2. Checks the renewal status from WayForPay
75
+ * 3. Renews subscription if payment was successful
76
+ * 4. Marks subscription for deactivation if payment failed
77
+ *
78
+ * @param callbackData - The callback data from WayForPay
79
+ * @returns Processing result with response to send back to WayForPay
80
+ */
81
+ public async processSubscriptionCheckWebhook(
82
+ callbackData: WayForPayCallbackData,
83
+ ): Promise<SubscriptionCheckWebhookResult> {
84
+ const { orderReference, transactionStatus } = callbackData;
85
+
86
+ this.logger.info({
87
+ message: 'Processing subscription check webhook',
88
+ payload: {
89
+ orderReference,
90
+ transactionStatus,
91
+ regularOn: callbackData.regularOn,
92
+ },
93
+ });
94
+
95
+ try {
96
+ // Step 1: Verify signature to validate request authenticity
97
+ const signatureResult = this.wayForPayService.verifyCallbackSignature(callbackData);
98
+
99
+ if (!signatureResult.isValid) {
100
+ this.logger.warning({
101
+ message: 'Invalid subscription check webhook signature',
102
+ payload: {
103
+ orderReference,
104
+ error: signatureResult.error,
105
+ },
106
+ });
107
+
108
+ return {
109
+ success: false,
110
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'decline'),
111
+ error: signatureResult.error || 'Invalid signature',
112
+ };
113
+ }
114
+
115
+ // Step 2: Validate this is a subscription order reference
116
+ if (!isSubscriptionOrderReference(orderReference)) {
117
+ this.logger.warning({
118
+ message: 'Subscription check webhook received for non-subscription order',
119
+ payload: { orderReference },
120
+ });
121
+
122
+ return {
123
+ success: true,
124
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'accept'),
125
+ };
126
+ }
127
+
128
+ // Step 3: Check renewal status and process accordingly
129
+ if (transactionStatus === APPROVED_STATUS) {
130
+ await this.handleSuccessfulRenewal(callbackData);
131
+ } else if (transactionStatus === DECLINED_STATUS || transactionStatus === EXPIRED_STATUS) {
132
+ await this.handleFailedRenewal(callbackData);
133
+ } else {
134
+ this.logger.info({
135
+ message: 'Subscription check webhook received with non-final status',
136
+ payload: {
137
+ orderReference,
138
+ transactionStatus,
139
+ },
140
+ });
141
+ }
142
+
143
+ return {
144
+ success: true,
145
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'accept'),
146
+ };
147
+ } catch (error) {
148
+ this.logger.error({
149
+ message: 'Error processing subscription check webhook',
150
+ payload: {
151
+ orderReference,
152
+ error: JSON.stringify(error),
153
+ },
154
+ });
155
+
156
+ return {
157
+ success: false,
158
+ response: this.wayForPayService.buildWebhookResponse(orderReference, 'decline'),
159
+ error: error instanceof Error ? error.message : 'Unknown error',
160
+ };
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Handles a successful subscription renewal.
166
+ * Extends the subscription expiration date based on the plan.
167
+ */
168
+ private async handleSuccessfulRenewal(callbackData: WayForPayCallbackData): Promise<void> {
169
+ const { orderReference } = callbackData;
170
+
171
+ const parsedReference = parseOrderReference(orderReference);
172
+ if (!parsedReference) {
173
+ this.logger.warning({
174
+ message: 'Could not parse order reference for successful renewal',
175
+ payload: { orderReference },
176
+ });
177
+ return;
178
+ }
179
+
180
+ const userId = getOrderReferenceUserId(orderReference);
181
+ const planId = getOrderReferenceItemId(orderReference);
182
+ const { platform } = parsedReference;
183
+
184
+ if (!userId || !planId) {
185
+ this.logger.warning({
186
+ message: 'Could not extract userId or planId from order reference',
187
+ payload: { orderReference },
188
+ });
189
+ return;
190
+ }
191
+
192
+ // Find active subscription for user
193
+ const existingSubscription = await this.subscriptionService.getByUser({
194
+ userId,
195
+ platform,
196
+ status: 'active',
197
+ });
198
+
199
+ if (!existingSubscription) {
200
+ this.logger.warning({
201
+ message: 'No active subscription found for successful renewal',
202
+ payload: { userId, platform, orderReference },
203
+ });
204
+ return;
205
+ }
206
+
207
+ // Get subscription plan to calculate new expiration
208
+ const subscriptionPlan = await this.subscriptionPlanService.getById(planId);
209
+ if (!subscriptionPlan) {
210
+ this.logger.warning({
211
+ message: 'Subscription plan not found for renewal',
212
+ payload: { planId, orderReference },
213
+ });
214
+ return;
215
+ }
216
+
217
+ // Calculate new expiration date
218
+ const newExpiresAt = this.calculateExpirationDate(
219
+ subscriptionPlan.regularMode,
220
+ subscriptionPlan.count,
221
+ );
222
+
223
+ // Renew subscription
224
+ await this.renewSubscription(existingSubscription, newExpiresAt);
225
+
226
+ this.logger.info({
227
+ message: 'Subscription renewed successfully via check webhook',
228
+ payload: {
229
+ subscriptionId: existingSubscription.id,
230
+ userId,
231
+ platform,
232
+ planId,
233
+ newExpiresAt,
234
+ },
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Handles a failed subscription renewal.
240
+ * Marks the subscription as cancelled initially, then deactivates after grace period.
241
+ */
242
+ private async handleFailedRenewal(callbackData: WayForPayCallbackData): Promise<void> {
243
+ const { orderReference, reason, reasonCode, transactionStatus } = callbackData;
244
+
245
+ const parsedReference = parseOrderReference(orderReference);
246
+ if (!parsedReference) {
247
+ this.logger.warning({
248
+ message: 'Could not parse order reference for failed renewal',
249
+ payload: { orderReference },
250
+ });
251
+ return;
252
+ }
253
+
254
+ const userId = getOrderReferenceUserId(orderReference);
255
+ const { platform } = parsedReference;
256
+
257
+ if (!userId) {
258
+ this.logger.warning({
259
+ message: 'Could not extract userId from order reference for failed renewal',
260
+ payload: { orderReference },
261
+ });
262
+ return;
263
+ }
264
+
265
+ // Find active subscription for user
266
+ const existingSubscription = await this.subscriptionService.getByUser({
267
+ userId,
268
+ platform,
269
+ status: 'active',
270
+ });
271
+
272
+ if (!existingSubscription) {
273
+ this.logger.info({
274
+ message: 'No active subscription found for failed renewal check',
275
+ payload: { userId, platform, orderReference },
276
+ });
277
+ return;
278
+ }
279
+
280
+ // Check if subscription has expired past the grace period
281
+ const now = moment.utc();
282
+ const expiresAt = moment.utc(existingSubscription.expiresAt);
283
+ const gracePeriodMs = this.gracePeriodConfig.enabled ? this.gracePeriodConfig.durationMs : 0;
284
+ const gracePeriodEnd = expiresAt.clone().add(gracePeriodMs, 'milliseconds');
285
+
286
+ if (now.isAfter(gracePeriodEnd)) {
287
+ // Subscription has expired past grace period and renewal failed - deactivate it
288
+ await this.subscriptionService.deactivateSubscription({ id: existingSubscription.id });
289
+
290
+ this.logger.info({
291
+ message: 'Subscription deactivated due to failed renewal check (past grace period)',
292
+ payload: {
293
+ subscriptionId: existingSubscription.id,
294
+ userId,
295
+ platform,
296
+ orderReference,
297
+ transactionStatus,
298
+ reason,
299
+ reasonCode,
300
+ expiresAt: existingSubscription.expiresAt,
301
+ gracePeriodMs,
302
+ gracePeriodEnd: gracePeriodEnd.toISOString(),
303
+ },
304
+ });
305
+ } else if (expiresAt.isBefore(now)) {
306
+ // Subscription has expired but still within grace period - cancel (mark for future deactivation)
307
+ await this.subscriptionService.cancelSubscription({ id: existingSubscription.id });
308
+
309
+ this.logger.info({
310
+ message: 'Subscription cancelled due to failed renewal (within grace period)',
311
+ payload: {
312
+ subscriptionId: existingSubscription.id,
313
+ userId,
314
+ platform,
315
+ orderReference,
316
+ expiresAt: existingSubscription.expiresAt,
317
+ gracePeriodEnd: gracePeriodEnd.toISOString(),
318
+ transactionStatus,
319
+ reason,
320
+ reasonCode,
321
+ },
322
+ });
323
+ } else {
324
+ // Subscription not yet expired - log for monitoring but don't deactivate
325
+ this.logger.info({
326
+ message: 'Failed renewal check for subscription that has not yet expired',
327
+ payload: {
328
+ subscriptionId: existingSubscription.id,
329
+ userId,
330
+ platform,
331
+ orderReference,
332
+ expiresAt: existingSubscription.expiresAt,
333
+ transactionStatus,
334
+ reason,
335
+ reasonCode,
336
+ },
337
+ });
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Renews an existing subscription by extending its expiration date.
343
+ */
344
+ private async renewSubscription(
345
+ existingSubscription: SubscriptionEntity,
346
+ newExpiresAt: string,
347
+ ): Promise<void> {
348
+ // If the subscription hasn't expired yet, extend from current expiration
349
+ const currentExpiration = moment.utc(existingSubscription.expiresAt);
350
+ const now = moment.utc();
351
+
352
+ let extendedExpiresAt: string;
353
+ if (currentExpiration.isAfter(now)) {
354
+ // Extend from current expiration date
355
+ const durationToAdd = moment.utc(newExpiresAt).diff(moment.utc(), 'milliseconds');
356
+ extendedExpiresAt = currentExpiration.add(durationToAdd, 'milliseconds').toISOString();
357
+ } else {
358
+ // Subscription has expired, use the new expiration date
359
+ extendedExpiresAt = newExpiresAt;
360
+ }
361
+
362
+ await this.subscriptionService.renewSubscription({
363
+ userId: existingSubscription.userId,
364
+ platform: existingSubscription.platform,
365
+ expiresAt: extendedExpiresAt,
366
+ });
367
+
368
+ this.logger.info({
369
+ message: 'Subscription renewed via check webhook',
370
+ payload: {
371
+ subscriptionId: existingSubscription.id,
372
+ previousExpiresAt: existingSubscription.expiresAt,
373
+ newExpiresAt: extendedExpiresAt,
374
+ },
375
+ });
376
+ }
377
+
378
+ /**
379
+ * Calculates the subscription expiration date based on the regular mode.
380
+ */
381
+ private calculateExpirationDate(
382
+ regularMode: 'daily' | 'monthly' | 'yearly',
383
+ count: number,
384
+ ): string {
385
+ const now = moment.utc();
386
+
387
+ switch (regularMode) {
388
+ case 'daily':
389
+ return now.add(count, 'days').toISOString();
390
+ case 'monthly':
391
+ return now.add(count, 'months').toISOString();
392
+ case 'yearly':
393
+ return now.add(count, 'years').toISOString();
394
+ default:
395
+ return now.add(count, 'months').toISOString();
396
+ }
397
+ }
398
+ }
@@ -0,0 +1,29 @@
1
+ import { WayForPayCallbackData, WayForPayWebhookResponse } from './wayforpay.types';
2
+
3
+ /**
4
+ * Result of processing a subscription check webhook callback
5
+ */
6
+ type SubscriptionCheckWebhookResult = {
7
+ success: boolean;
8
+ response: WayForPayWebhookResponse;
9
+ error?: string;
10
+ };
11
+
12
+ /**
13
+ * Interface for subscription check webhook handler service operations.
14
+ * Handles subscription status check webhooks from WayForPay.
15
+ */
16
+ interface ISubscriptionCheckWebhookService {
17
+ /**
18
+ * Processes a subscription check webhook from WayForPay.
19
+ * Verifies signature, checks renewal status, and updates subscription accordingly.
20
+ *
21
+ * @param callbackData - The callback data from WayForPay
22
+ * @returns Processing result with response to send back to WayForPay
23
+ */
24
+ processSubscriptionCheckWebhook(
25
+ callbackData: WayForPayCallbackData,
26
+ ): Promise<SubscriptionCheckWebhookResult>;
27
+ }
28
+
29
+ export type { SubscriptionCheckWebhookResult, ISubscriptionCheckWebhookService };