@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.
- package/dist/config/ConfigurationManager.d.ts +64 -0
- package/dist/config/ConfigurationManager.d.ts.map +1 -0
- package/dist/config/ConfigurationManager.js +144 -0
- package/dist/config/ConfigurationManager.js.map +1 -0
- package/dist/config/defaults.d.ts +18 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +26 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/environment.d.ts +38 -0
- package/dist/config/environment.d.ts.map +1 -0
- package/dist/config/environment.js +91 -0
- package/dist/config/environment.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +18 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +53 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +3 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/cache/InMemoryCache.d.ts +17 -0
- package/dist/modules/cache/InMemoryCache.d.ts.map +1 -0
- package/dist/modules/cache/InMemoryCache.js +77 -0
- package/dist/modules/cache/InMemoryCache.js.map +1 -0
- package/dist/modules/cache/index.d.ts +3 -0
- package/dist/modules/cache/index.d.ts.map +1 -0
- package/dist/modules/cache/index.js +19 -0
- package/dist/modules/cache/index.js.map +1 -0
- package/dist/modules/cache/types.d.ts +52 -0
- package/dist/modules/cache/types.d.ts.map +1 -0
- package/dist/modules/cache/types.js +3 -0
- package/dist/modules/cache/types.js.map +1 -0
- package/dist/modules/errors/index.d.ts +2 -0
- package/dist/modules/errors/index.d.ts.map +1 -0
- package/dist/modules/errors/index.js +19 -0
- package/dist/modules/errors/index.js.map +1 -0
- package/dist/modules/errors/types.d.ts +112 -0
- package/dist/modules/errors/types.d.ts.map +1 -0
- package/dist/modules/errors/types.js +174 -0
- package/dist/modules/errors/types.js.map +1 -0
- package/dist/modules/payments/api.d.ts +63 -1
- package/dist/modules/payments/api.d.ts.map +1 -1
- package/dist/modules/payments/api.js +103 -1
- package/dist/modules/payments/api.js.map +1 -1
- package/dist/modules/payments/const.d.ts.map +1 -1
- package/dist/modules/payments/const.js +1 -0
- package/dist/modules/payments/const.js.map +1 -1
- package/dist/modules/payments/index.d.ts +8 -0
- package/dist/modules/payments/index.d.ts.map +1 -1
- package/dist/modules/payments/index.js +8 -0
- package/dist/modules/payments/index.js.map +1 -1
- package/dist/modules/payments/service.d.ts +42 -2
- package/dist/modules/payments/service.d.ts.map +1 -1
- package/dist/modules/payments/service.js +132 -3
- package/dist/modules/payments/service.js.map +1 -1
- package/dist/modules/payments/subscription-check-webhook.handler.d.ts +85 -0
- package/dist/modules/payments/subscription-check-webhook.handler.d.ts.map +1 -0
- package/dist/modules/payments/subscription-check-webhook.handler.js +155 -0
- package/dist/modules/payments/subscription-check-webhook.handler.js.map +1 -0
- package/dist/modules/payments/subscription-check-webhook.service.d.ts +59 -0
- package/dist/modules/payments/subscription-check-webhook.service.d.ts.map +1 -0
- package/dist/modules/payments/subscription-check-webhook.service.js +330 -0
- package/dist/modules/payments/subscription-check-webhook.service.js.map +1 -0
- package/dist/modules/payments/subscription-check-webhook.types.d.ts +25 -0
- package/dist/modules/payments/subscription-check-webhook.types.d.ts.map +1 -0
- package/dist/modules/payments/subscription-check-webhook.types.js +3 -0
- package/dist/modules/payments/subscription-check-webhook.types.js.map +1 -0
- package/dist/modules/payments/types.d.ts +69 -2
- package/dist/modules/payments/types.d.ts.map +1 -1
- package/dist/modules/payments/utils.d.ts +151 -5
- package/dist/modules/payments/utils.d.ts.map +1 -1
- package/dist/modules/payments/utils.js +253 -9
- package/dist/modules/payments/utils.js.map +1 -1
- package/dist/modules/payments/wayforpay.service.d.ts +39 -0
- package/dist/modules/payments/wayforpay.service.d.ts.map +1 -0
- package/dist/modules/payments/wayforpay.service.js +217 -0
- package/dist/modules/payments/wayforpay.service.js.map +1 -0
- package/dist/modules/payments/wayforpay.types.d.ts +115 -0
- package/dist/modules/payments/wayforpay.types.d.ts.map +1 -0
- package/dist/modules/payments/wayforpay.types.js +3 -0
- package/dist/modules/payments/wayforpay.types.js.map +1 -0
- package/dist/modules/payments/webhook.handler.d.ts +98 -0
- package/dist/modules/payments/webhook.handler.d.ts.map +1 -0
- package/dist/modules/payments/webhook.handler.js +153 -0
- package/dist/modules/payments/webhook.handler.js.map +1 -0
- package/dist/modules/payments/webhook.service.d.ts +99 -0
- package/dist/modules/payments/webhook.service.d.ts.map +1 -0
- package/dist/modules/payments/webhook.service.js +672 -0
- package/dist/modules/payments/webhook.service.js.map +1 -0
- package/dist/modules/payments/webhook.types.d.ts +35 -0
- package/dist/modules/payments/webhook.types.d.ts.map +1 -0
- package/dist/modules/payments/webhook.types.js +3 -0
- package/dist/modules/payments/webhook.types.js.map +1 -0
- package/dist/modules/subscription/change.service.d.ts +80 -0
- package/dist/modules/subscription/change.service.d.ts.map +1 -0
- package/dist/modules/subscription/change.service.js +226 -0
- package/dist/modules/subscription/change.service.js.map +1 -0
- package/dist/modules/subscription/index.d.ts +2 -0
- package/dist/modules/subscription/index.d.ts.map +1 -1
- package/dist/modules/subscription/index.js +2 -0
- package/dist/modules/subscription/index.js.map +1 -1
- package/dist/modules/subscription/service.d.ts +8 -1
- package/dist/modules/subscription/service.d.ts.map +1 -1
- package/dist/modules/subscription/service.js +58 -1
- package/dist/modules/subscription/service.js.map +1 -1
- package/dist/modules/subscription/status-check.handler.d.ts +117 -0
- package/dist/modules/subscription/status-check.handler.d.ts.map +1 -0
- package/dist/modules/subscription/status-check.handler.js +164 -0
- package/dist/modules/subscription/status-check.handler.js.map +1 -0
- package/dist/modules/subscription/types.d.ts +37 -1
- package/dist/modules/subscription/types.d.ts.map +1 -1
- package/dist/modules/subscriptionPlan/const.d.ts +5 -0
- package/dist/modules/subscriptionPlan/const.d.ts.map +1 -0
- package/dist/modules/subscriptionPlan/const.js +106 -0
- package/dist/modules/subscriptionPlan/const.js.map +1 -0
- package/dist/modules/subscriptionPlan/index.d.ts +5 -0
- package/dist/modules/subscriptionPlan/index.d.ts.map +1 -0
- package/dist/modules/subscriptionPlan/index.js +21 -0
- package/dist/modules/subscriptionPlan/index.js.map +1 -0
- package/dist/modules/subscriptionPlan/repository.d.ts +22 -0
- package/dist/modules/subscriptionPlan/repository.d.ts.map +1 -0
- package/dist/modules/subscriptionPlan/repository.js +95 -0
- package/dist/modules/subscriptionPlan/repository.js.map +1 -0
- package/dist/modules/subscriptionPlan/service.d.ts +21 -0
- package/dist/modules/subscriptionPlan/service.d.ts.map +1 -0
- package/dist/modules/subscriptionPlan/service.js +128 -0
- package/dist/modules/subscriptionPlan/service.js.map +1 -0
- package/dist/modules/subscriptionPlan/types.d.ts +40 -0
- package/dist/modules/subscriptionPlan/types.d.ts.map +1 -0
- package/dist/modules/subscriptionPlan/types.js +3 -0
- package/dist/modules/subscriptionPlan/types.js.map +1 -0
- package/dist/modules/token/const.d.ts +7 -0
- package/dist/modules/token/const.d.ts.map +1 -0
- package/dist/modules/token/const.js +66 -0
- package/dist/modules/token/const.js.map +1 -0
- package/dist/modules/token/index.d.ts +4 -0
- package/dist/modules/token/index.d.ts.map +1 -0
- package/dist/modules/token/index.js +20 -0
- package/dist/modules/token/index.js.map +1 -0
- package/dist/modules/token/service.d.ts +46 -0
- package/dist/modules/token/service.d.ts.map +1 -0
- package/dist/modules/token/service.js +249 -0
- package/dist/modules/token/service.js.map +1 -0
- package/dist/modules/token/types.d.ts +109 -0
- package/dist/modules/token/types.d.ts.map +1 -0
- package/dist/modules/token/types.js +3 -0
- package/dist/modules/token/types.js.map +1 -0
- package/dist/modules/tokenPack/const.d.ts +4 -0
- package/dist/modules/tokenPack/const.d.ts.map +1 -0
- package/dist/modules/tokenPack/const.js +10 -0
- package/dist/modules/tokenPack/const.js.map +1 -0
- package/dist/modules/tokenPack/index.d.ts +5 -0
- package/dist/modules/tokenPack/index.d.ts.map +1 -0
- package/dist/modules/tokenPack/index.js +21 -0
- package/dist/modules/tokenPack/index.js.map +1 -0
- package/dist/modules/tokenPack/repository.d.ts +32 -0
- package/dist/modules/tokenPack/repository.d.ts.map +1 -0
- package/dist/modules/tokenPack/repository.js +103 -0
- package/dist/modules/tokenPack/repository.js.map +1 -0
- package/dist/modules/tokenPack/service.d.ts +28 -0
- package/dist/modules/tokenPack/service.d.ts.map +1 -0
- package/dist/modules/tokenPack/service.js +106 -0
- package/dist/modules/tokenPack/service.js.map +1 -0
- package/dist/modules/tokenPack/types.d.ts +124 -0
- package/dist/modules/tokenPack/types.d.ts.map +1 -0
- package/dist/modules/tokenPack/types.js +3 -0
- package/dist/modules/tokenPack/types.js.map +1 -0
- package/package.json +9 -5
- package/src/config/ConfigurationManager.ts +159 -0
- package/src/config/defaults.ts +27 -0
- package/src/config/environment.ts +94 -0
- package/src/config/index.ts +22 -0
- package/src/config/types.ts +56 -0
- package/src/index.ts +29 -0
- package/src/modules/cache/InMemoryCache.ts +98 -0
- package/src/modules/cache/index.ts +2 -0
- package/src/modules/cache/types.ts +60 -0
- package/src/modules/cancellableAPI/utils.ts +60 -0
- package/src/modules/errors/index.ts +16 -0
- package/src/modules/errors/types.ts +201 -0
- package/src/modules/invoice/const.ts +7 -0
- package/src/modules/invoice/index.ts +4 -0
- package/src/modules/invoice/repository.ts +52 -0
- package/src/modules/invoice/service.ts +44 -0
- package/src/modules/invoice/types.ts +47 -0
- package/src/modules/logger/types.ts +8 -0
- package/src/modules/network/utils.ts +24 -0
- package/src/modules/payments/api.ts +289 -0
- package/src/modules/payments/const.ts +11 -0
- package/src/modules/payments/index.ts +14 -0
- package/src/modules/payments/repository.ts +125 -0
- package/src/modules/payments/service.test.ts +400 -0
- package/src/modules/payments/service.ts +365 -0
- package/src/modules/payments/subscription-check-webhook.handler.integration.test.ts +935 -0
- package/src/modules/payments/subscription-check-webhook.handler.ts +211 -0
- package/src/modules/payments/subscription-check-webhook.service.ts +398 -0
- package/src/modules/payments/subscription-check-webhook.types.ts +29 -0
- package/src/modules/payments/types.ts +193 -0
- package/src/modules/payments/utils.ts +428 -0
- package/src/modules/payments/wayforpay.service.test.ts +375 -0
- package/src/modules/payments/wayforpay.service.ts +284 -0
- package/src/modules/payments/wayforpay.types.ts +138 -0
- package/src/modules/payments/webhook.handler.integration.test.ts +975 -0
- package/src/modules/payments/webhook.handler.ts +219 -0
- package/src/modules/payments/webhook.service.ts +812 -0
- package/src/modules/payments/webhook.types.ts +38 -0
- package/src/modules/subscription/change.service.ts +317 -0
- package/src/modules/subscription/const.ts +9 -0
- package/src/modules/subscription/index.ts +5 -0
- package/src/modules/subscription/repository.ts +277 -0
- package/src/modules/subscription/service.test.ts +665 -0
- package/src/modules/subscription/service.ts +328 -0
- package/src/modules/subscription/status-check.handler.ts +254 -0
- package/src/modules/subscription/types.ts +267 -0
- package/src/modules/subscription/utils.ts +5 -0
- package/src/modules/subscriptionPlan/const.ts +106 -0
- package/src/modules/subscriptionPlan/index.ts +4 -0
- package/src/modules/subscriptionPlan/repository.ts +129 -0
- package/src/modules/subscriptionPlan/service.test.ts +401 -0
- package/src/modules/subscriptionPlan/service.ts +148 -0
- package/src/modules/subscriptionPlan/types.ts +67 -0
- package/src/modules/token/const.ts +64 -0
- package/src/modules/token/index.ts +3 -0
- package/src/modules/token/service.test.ts +499 -0
- package/src/modules/token/service.ts +297 -0
- package/src/modules/token/types.ts +124 -0
- package/src/modules/tokenPack/const.ts +9 -0
- package/src/modules/tokenPack/index.ts +4 -0
- package/src/modules/tokenPack/repository.ts +144 -0
- package/src/modules/tokenPack/service.ts +119 -0
- package/src/modules/tokenPack/types.ts +131 -0
- package/src/modules/user/index.ts +3 -0
- package/src/modules/user/types.ts +143 -0
- package/src/modules/user/userRepository.ts +64 -0
- package/src/modules/user/userService.ts +68 -0
- package/src/types/extend-express.d.ts +16 -0
- package/src/types/function.ts +5 -0
- package/src/types/utilities.ts +22 -0
- package/src/utils.ts +53 -0
- package/tsconfig.json +29 -0
- package/dist/modules/subscription/subscriptionPlan.d.ts +0 -4
- package/dist/modules/subscription/subscriptionPlan.d.ts.map +0 -1
- package/dist/modules/subscription/subscriptionPlan.js +0 -67
- 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 };
|