@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,328 @@
1
+ import moment from 'moment';
2
+ import { Logger } from '../logger/types';
3
+ import { SubscriptionRepository, UpdateDBSubscriptionFields } from './repository';
4
+ import { SubscriptionEntity, ISubscriptionRepository, ISubscriptionService, SubscriptionPlan } from './types';
5
+ import { getSubscriptionTitle } from './utils';
6
+ import { PaymentService } from '../payments/service';
7
+ import { IPaymentService } from '../payments/types';
8
+ import { ConfigurationManager, GracePeriodConfig, DEFAULT_GRACE_PERIOD_CONFIG } from '../../config';
9
+
10
+ export class SubscriptionService implements ISubscriptionService {
11
+ private readonly logger: Logger;
12
+ private readonly repository: ISubscriptionRepository;
13
+ private readonly paymentService: IPaymentService;
14
+ private readonly gracePeriodConfig: GracePeriodConfig;
15
+
16
+ constructor({
17
+ logger,
18
+ repository,
19
+ paymentService,
20
+ gracePeriodConfig,
21
+ }: {
22
+ logger: Logger;
23
+ repository?: ISubscriptionRepository;
24
+ paymentService?: IPaymentService;
25
+ gracePeriodConfig?: GracePeriodConfig;
26
+ }) {
27
+ this.logger = logger;
28
+ this.repository = repository || new SubscriptionRepository();
29
+ this.paymentService = paymentService || new PaymentService({ logger });
30
+ this.gracePeriodConfig = gracePeriodConfig || this.loadGracePeriodConfig();
31
+ }
32
+
33
+ private loadGracePeriodConfig(): GracePeriodConfig {
34
+ try {
35
+ const configManager = ConfigurationManager.getInstance();
36
+ return configManager.getGracePeriodConfig();
37
+ } catch {
38
+ return DEFAULT_GRACE_PERIOD_CONFIG;
39
+ }
40
+ }
41
+
42
+ public async getByUser(params: {
43
+ userId: string;
44
+ platform: string;
45
+ status?: SubscriptionEntity['status'] | SubscriptionEntity['status'][];
46
+ }): Promise<SubscriptionEntity | null> {
47
+ try {
48
+ return await this.repository.getByUser(params);
49
+ } catch (error) {
50
+ this.logger.error({
51
+ message: 'Error in subscription service getByUser',
52
+ payload: {
53
+ userId: params.userId,
54
+ platform: params.platform,
55
+ error: JSON.stringify(error),
56
+ },
57
+ });
58
+ return null;
59
+ }
60
+ }
61
+
62
+ public async create(params: {
63
+ userId: string;
64
+ platform: string;
65
+ planId: string;
66
+ expiresAt: string;
67
+ startedAt: string;
68
+ }): Promise<SubscriptionEntity> {
69
+ try {
70
+ return await this.repository.create(params);
71
+ } catch (error) {
72
+ this.logger.error({
73
+ message: 'Error in subscription service create',
74
+ payload: {
75
+ userId: params.userId,
76
+ platform: params.platform,
77
+ planId: params.planId,
78
+ error: JSON.stringify(error),
79
+ },
80
+ });
81
+
82
+ throw error;
83
+ }
84
+ }
85
+ public async updateFieldsByUserId(params: {
86
+ userId: string;
87
+ platform: string;
88
+ fields: UpdateDBSubscriptionFields;
89
+ }): Promise<void> {
90
+ try {
91
+ await this.repository.updateFieldsByUserId(params);
92
+ } catch (error) {
93
+ this.logger.error({
94
+ message: 'Error in subscription service updateFieldsByUserId',
95
+ payload: {
96
+ userId: params.userId,
97
+ platform: params.platform,
98
+ fields: JSON.stringify(params.fields),
99
+ error: JSON.stringify(error),
100
+ },
101
+ });
102
+
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ public async getExpiredActiveSubscriptions(): Promise<SubscriptionEntity[]> {
108
+ try {
109
+ return await this.repository.getExpiredActiveSubscriptions();
110
+ } catch (error) {
111
+ this.logger.error({
112
+ message: 'Error in subscription service getExpiredActiveSubscriptions',
113
+ payload: {
114
+ error: JSON.stringify(error),
115
+ },
116
+ });
117
+ return [];
118
+ }
119
+ }
120
+
121
+ public async updateFieldsById(params: {
122
+ subscriptionId: string;
123
+ fields: UpdateDBSubscriptionFields;
124
+ }): Promise<void> {
125
+ try {
126
+ await this.repository.updateFieldsById(params);
127
+ } catch (error) {
128
+ this.logger.error({
129
+ message: 'Error in subscription service updateFieldsById',
130
+ payload: {
131
+ subscriptionId: params.subscriptionId,
132
+ fields: JSON.stringify(params.fields),
133
+ error: JSON.stringify(error),
134
+ },
135
+ });
136
+
137
+ throw error;
138
+ }
139
+ }
140
+
141
+ public async getOrCreateSubscriptionPaymentUrl(params: {
142
+ userId: string;
143
+ platform: string;
144
+ languageCode: string;
145
+ translate: (params: { code: string; path: string }) => string;
146
+ subscriptionPlan: SubscriptionPlan;
147
+ }): Promise<string> {
148
+ try {
149
+ // Check for existing pending payments for this plan
150
+ const existingPayments = await this.paymentService.getByUser({
151
+ userId: params.userId,
152
+ platform: params.platform,
153
+ status: 'pending',
154
+ });
155
+
156
+ const recentPayment = existingPayments
157
+ .filter((payment) => payment.planId === params.subscriptionPlan.id)
158
+ .find((payment) => moment.utc().diff(moment.utc(payment.createdAt), 'minutes') < 10);
159
+
160
+ if (recentPayment && recentPayment.paymentLink) {
161
+ return recentPayment.paymentLink;
162
+ }
163
+
164
+ const productName = getSubscriptionTitle(
165
+ params.translate({
166
+ code: params.languageCode,
167
+ path: params.subscriptionPlan.titleCode,
168
+ }),
169
+ );
170
+
171
+ const paymentEntity = await this.paymentService.createPaymentIntent({
172
+ userId: params.userId,
173
+ platform: params.platform,
174
+ planId: params.subscriptionPlan.id,
175
+ productName,
176
+ productPrice: params.subscriptionPlan.amount,
177
+ currency: params.subscriptionPlan.currency,
178
+ language: params.languageCode,
179
+ regularMode: params.subscriptionPlan.regularMode,
180
+ regularCount: params.subscriptionPlan.count,
181
+ });
182
+
183
+ return paymentEntity.paymentLink;
184
+ } catch (error) {
185
+ this.logger.error({
186
+ message: 'Error in subscription service getOrCreateSubscriptionPaymentUrl',
187
+ payload: {
188
+ userId: params.userId,
189
+ platform: params.platform,
190
+ planId: params.subscriptionPlan.id,
191
+ error: JSON.stringify(error),
192
+ },
193
+ });
194
+
195
+ throw error;
196
+ }
197
+ }
198
+
199
+ public async activateSubscription(params: Omit<SubscriptionEntity, 'id' | 'status'>): Promise<void> {
200
+ try {
201
+ await this.repository.activateSubscription(params);
202
+ } catch (error) {
203
+ this.logger.error({
204
+ message: 'Error in subscription service activateSubscription',
205
+ payload: {
206
+ userId: params.userId,
207
+ platform: params.platform,
208
+ planId: params.planId,
209
+ error: JSON.stringify(error),
210
+ },
211
+ });
212
+
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ public async renewSubscription(
218
+ params: Pick<SubscriptionEntity, 'userId' | 'platform' | 'expiresAt'>,
219
+ ): Promise<void> {
220
+ try {
221
+ await this.repository.renewSubscription(params);
222
+ } catch (error) {
223
+ this.logger.error({
224
+ message: 'Error in subscription service renewSubscription',
225
+ payload: {
226
+ userId: params.userId,
227
+ platform: params.platform,
228
+ expiresAt: params.expiresAt,
229
+ error: JSON.stringify(error),
230
+ },
231
+ });
232
+ throw error;
233
+ }
234
+ }
235
+
236
+ public async getSubscriptionsForDeactivationCheck(): Promise<SubscriptionEntity[]> {
237
+ try {
238
+ return (await this.repository.getByStatus({ status: ['active', 'cancelled'] })).filter(
239
+ (subscription) => subscription.id !== 'unlimited',
240
+ );
241
+ } catch (error) {
242
+ this.logger.error({
243
+ message: 'Error in subscription service getSubscriptionsForDeactivationCheck',
244
+ payload: { error: JSON.stringify(error) },
245
+ });
246
+
247
+ throw error;
248
+ }
249
+ }
250
+
251
+ public async deactivateSubscription({ id }: Pick<SubscriptionEntity, 'id'>): Promise<void> {
252
+ try {
253
+ await this.repository.deactivateSubscription({ id });
254
+ } catch (error) {
255
+ this.logger.error({
256
+ message: 'Error in subscription service deactivateSubscription',
257
+ payload: {
258
+ id,
259
+ error: JSON.stringify(error),
260
+ },
261
+ });
262
+ throw error;
263
+ }
264
+ }
265
+
266
+ public async cancelSubscription({ id }: Pick<SubscriptionEntity, 'id'>): Promise<void> {
267
+ try {
268
+ await this.repository.cancelSubscription({ id });
269
+ } catch (error) {
270
+ this.logger.error({
271
+ message: 'Error in subscription service cancelSubscription',
272
+ payload: { id, error: JSON.stringify(error) },
273
+ });
274
+ }
275
+ }
276
+
277
+ public async getExpiredSubscriptions(): Promise<SubscriptionEntity[]> {
278
+ try {
279
+ const expiredSubscriptions = await this.repository.getExpiredActiveSubscriptions();
280
+
281
+ if (!this.gracePeriodConfig.enabled || this.gracePeriodConfig.durationMs === 0) {
282
+ return expiredSubscriptions;
283
+ }
284
+
285
+ const now = moment.utc();
286
+ return expiredSubscriptions.filter((subscription) => {
287
+ const expiresAt = moment.utc(subscription.expiresAt);
288
+ const gracePeriodEnd = expiresAt.add(this.gracePeriodConfig.durationMs, 'milliseconds');
289
+ return now.isAfter(gracePeriodEnd);
290
+ });
291
+ } catch (error) {
292
+ this.logger.error({
293
+ message: 'Error in subscription service getExpiredSubscriptions',
294
+ payload: { error: JSON.stringify(error) },
295
+ });
296
+ return [];
297
+ }
298
+ }
299
+
300
+ public async getFailedRenewals(): Promise<SubscriptionEntity[]> {
301
+ try {
302
+ const cancelledSubscriptions = await this.repository.getByStatus({ status: 'cancelled' });
303
+
304
+ const now = moment.utc();
305
+ const gracePeriodMs = this.gracePeriodConfig.enabled ? this.gracePeriodConfig.durationMs : 0;
306
+
307
+ return cancelledSubscriptions.filter((subscription) => {
308
+ if (subscription.id === 'unlimited') {
309
+ return false;
310
+ }
311
+
312
+ const expiresAt = moment.utc(subscription.expiresAt);
313
+ const gracePeriodEnd = expiresAt.add(gracePeriodMs, 'milliseconds');
314
+ return now.isAfter(gracePeriodEnd);
315
+ });
316
+ } catch (error) {
317
+ this.logger.error({
318
+ message: 'Error in subscription service getFailedRenewals',
319
+ payload: { error: JSON.stringify(error) },
320
+ });
321
+ return [];
322
+ }
323
+ }
324
+
325
+ public async deactivate({ id }: Pick<SubscriptionEntity, 'id'>): Promise<void> {
326
+ return this.deactivateSubscription({ id });
327
+ }
328
+ }
@@ -0,0 +1,254 @@
1
+ import { Logger } from '../logger/types';
2
+ import { SubscriptionService } from './service';
3
+ import { TokenService } from '../token/service';
4
+ import { ISubscriptionService, SubscriptionEntity } from './types';
5
+ import { ITokenService, TokenBalance } from '../token/types';
6
+
7
+ /**
8
+ * Request object interface for status check handling.
9
+ * Compatible with Express.js and similar frameworks.
10
+ */
11
+ interface StatusCheckRequest {
12
+ body: unknown;
13
+ }
14
+
15
+ /**
16
+ * Response object interface for status check handling.
17
+ * Compatible with Express.js and similar frameworks.
18
+ */
19
+ interface StatusCheckResponse {
20
+ status(code: number): StatusCheckResponse;
21
+ json(data: unknown): void;
22
+ }
23
+
24
+ /**
25
+ * Configuration options for the status check handler.
26
+ */
27
+ interface StatusCheckHandlerConfig {
28
+ logger: Logger;
29
+ subscriptionService?: ISubscriptionService;
30
+ tokenService?: ITokenService;
31
+ }
32
+
33
+ /**
34
+ * Request body for subscription status check.
35
+ */
36
+ interface StatusCheckRequestBody {
37
+ userId: string;
38
+ platform: string;
39
+ }
40
+
41
+ /**
42
+ * Response payload for subscription status check.
43
+ */
44
+ interface SubscriptionStatusResponse {
45
+ /** Whether the user has an active subscription */
46
+ hasActiveSubscription: boolean;
47
+ /** Current subscription details, null if no subscription exists */
48
+ subscription: SubscriptionEntity | null;
49
+ /** User's token balance */
50
+ tokenBalance: TokenBalance;
51
+ }
52
+
53
+ /**
54
+ * Result of parsing the status check request body.
55
+ */
56
+ interface ParseResult {
57
+ success: boolean;
58
+ data?: StatusCheckRequestBody;
59
+ error?: string;
60
+ }
61
+
62
+ /**
63
+ * SubscriptionStatusCheckHandler provides HTTP request handling for checking
64
+ * a user's subscription status. It returns subscription details, token balance,
65
+ * and active status.
66
+ *
67
+ * Usage with Express:
68
+ * ```typescript
69
+ * const handler = new SubscriptionStatusCheckHandler({ logger });
70
+ * app.post('/api/subscription/status', (req, res) => handler.handleRequest(req, res));
71
+ * ```
72
+ *
73
+ * Usage with Cloud Functions:
74
+ * ```typescript
75
+ * const handler = new SubscriptionStatusCheckHandler({ logger });
76
+ * export const statusCheck = (req, res) => handler.handleRequest(req, res);
77
+ * ```
78
+ */
79
+ export class SubscriptionStatusCheckHandler {
80
+ private readonly logger: Logger;
81
+ private readonly subscriptionService: ISubscriptionService;
82
+ private readonly tokenService: ITokenService;
83
+
84
+ constructor(config: StatusCheckHandlerConfig) {
85
+ this.logger = config.logger;
86
+ this.subscriptionService = config.subscriptionService || new SubscriptionService({ logger: config.logger });
87
+ this.tokenService = config.tokenService || new TokenService({ logger: config.logger });
88
+ }
89
+
90
+ /**
91
+ * Handles an incoming HTTP request to check subscription status.
92
+ * Validates the request body, retrieves subscription and token data,
93
+ * and returns the appropriate response.
94
+ *
95
+ * @param req - The HTTP request object
96
+ * @param res - The HTTP response object
97
+ */
98
+ public async handleRequest(req: StatusCheckRequest, res: StatusCheckResponse): Promise<void> {
99
+ this.logger.info({
100
+ message: 'Received subscription status check request',
101
+ payload: {},
102
+ });
103
+
104
+ // Parse and validate the request body
105
+ const parseResult = this.parseRequestBody(req.body);
106
+
107
+ if (!parseResult.success || !parseResult.data) {
108
+ this.logger.warning({
109
+ message: 'Invalid status check request body',
110
+ payload: { error: parseResult.error },
111
+ });
112
+
113
+ res.status(400).json({
114
+ error: parseResult.error || 'Invalid request body',
115
+ });
116
+ return;
117
+ }
118
+
119
+ const { userId, platform } = parseResult.data;
120
+
121
+ try {
122
+ // Get subscription status
123
+ const statusResult = await this.checkStatus({ userId, platform });
124
+
125
+ this.logger.info({
126
+ message: 'Subscription status check completed',
127
+ payload: {
128
+ userId,
129
+ platform,
130
+ hasActiveSubscription: statusResult.hasActiveSubscription,
131
+ subscriptionStatus: statusResult.subscription?.status || null,
132
+ tokenBalance: statusResult.tokenBalance.available,
133
+ },
134
+ });
135
+
136
+ res.status(200).json(statusResult);
137
+ } catch (error) {
138
+ this.logger.error({
139
+ message: 'Error checking subscription status',
140
+ payload: {
141
+ userId,
142
+ platform,
143
+ error: JSON.stringify(error),
144
+ },
145
+ });
146
+
147
+ res.status(500).json({
148
+ error: 'Internal server error while checking subscription status',
149
+ });
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Checks subscription status directly without HTTP request parsing.
155
+ * Useful for internal calls or testing.
156
+ *
157
+ * @param params - User identification parameters
158
+ * @returns Subscription status response
159
+ */
160
+ public async checkStatus(params: { userId: string; platform: string }): Promise<SubscriptionStatusResponse> {
161
+ const { userId, platform } = params;
162
+
163
+ // Fetch subscription and token balance in parallel
164
+ const [subscription, tokenBalance] = await Promise.all([
165
+ this.subscriptionService.getByUser({ userId, platform }),
166
+ this.tokenService.getBalance({ userId, platform }),
167
+ ]);
168
+
169
+ // Determine if subscription is active
170
+ const hasActiveSubscription = subscription?.status === 'active';
171
+
172
+ return {
173
+ hasActiveSubscription,
174
+ subscription,
175
+ tokenBalance,
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Parses and validates the status check request body.
181
+ */
182
+ private parseRequestBody(body: unknown): ParseResult {
183
+ if (!body || typeof body !== 'object') {
184
+ return {
185
+ success: false,
186
+ error: 'Request body must be a JSON object',
187
+ };
188
+ }
189
+
190
+ const data = body as Record<string, unknown>;
191
+
192
+ // Validate required fields
193
+ if (!data.userId || typeof data.userId !== 'string') {
194
+ return {
195
+ success: false,
196
+ error: 'Missing or invalid required field: userId',
197
+ };
198
+ }
199
+
200
+ if (!data.platform || typeof data.platform !== 'string') {
201
+ return {
202
+ success: false,
203
+ error: 'Missing or invalid required field: platform',
204
+ };
205
+ }
206
+
207
+ return {
208
+ success: true,
209
+ data: {
210
+ userId: data.userId,
211
+ platform: data.platform,
212
+ },
213
+ };
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Creates a subscription status check handler function for use with serverless platforms.
219
+ * Returns a function that can be directly used as a Cloud Function or Lambda handler.
220
+ *
221
+ * @param config - Configuration options for the status check handler
222
+ * @returns An async function that handles status check requests
223
+ *
224
+ * @example
225
+ * ```typescript
226
+ * // Firebase Cloud Functions
227
+ * import * as functions from 'firebase-functions';
228
+ * import { createSubscriptionStatusCheckHandler } from 'miit-bot-payment';
229
+ *
230
+ * const logger = { info: console.log, warning: console.warn, error: console.error };
231
+ * export const subscriptionStatus = functions.https.onRequest(
232
+ * createSubscriptionStatusCheckHandler({ logger })
233
+ * );
234
+ * ```
235
+ */
236
+ export function createSubscriptionStatusCheckHandler(
237
+ config: StatusCheckHandlerConfig,
238
+ ): (req: StatusCheckRequest, res: StatusCheckResponse) => Promise<void> {
239
+ const handler = new SubscriptionStatusCheckHandler(config);
240
+ return (req: StatusCheckRequest, res: StatusCheckResponse) => handler.handleRequest(req, res);
241
+ }
242
+
243
+ /**
244
+ * Type export for the status check handler function signature.
245
+ */
246
+ export type StatusCheckHandlerFunction = (req: StatusCheckRequest, res: StatusCheckResponse) => Promise<void>;
247
+
248
+ export type {
249
+ StatusCheckRequest,
250
+ StatusCheckResponse,
251
+ StatusCheckHandlerConfig,
252
+ StatusCheckRequestBody,
253
+ SubscriptionStatusResponse,
254
+ };