chargeback-guard 2.0.0

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/docs/api.md +278 -0
  4. package/docs/architecture.md +281 -0
  5. package/docs/configuration.md +292 -0
  6. package/docs/getting-started.md +155 -0
  7. package/examples/advancedConfig.ts +123 -0
  8. package/examples/basicUsage.ts +98 -0
  9. package/examples/stripeIntegration.ts +106 -0
  10. package/package.json +181 -0
  11. package/src/ai/fraudDetection.ts +261 -0
  12. package/src/ai/patternRecognition.ts +218 -0
  13. package/src/analytics/dashboard.ts +195 -0
  14. package/src/analytics/metrics.ts +175 -0
  15. package/src/analytics/predictions.ts +135 -0
  16. package/src/analytics/reports.ts +221 -0
  17. package/src/api/controllers.ts +339 -0
  18. package/src/api/middleware.ts +172 -0
  19. package/src/api/routes.ts +141 -0
  20. package/src/config.ts +231 -0
  21. package/src/core/chargebackGuard.ts +616 -0
  22. package/src/core/eventEmitter.ts +118 -0
  23. package/src/core/lifecycle.ts +215 -0
  24. package/src/database/schema.ts +392 -0
  25. package/src/dispute/analyzer.ts +317 -0
  26. package/src/dispute/bankIntegration.ts +274 -0
  27. package/src/dispute/detector.ts +239 -0
  28. package/src/dispute/responseEngine.ts +440 -0
  29. package/src/evidence/collector.ts +426 -0
  30. package/src/evidence/encryption.ts +168 -0
  31. package/src/evidence/storage.ts +197 -0
  32. package/src/evidence/validator.ts +184 -0
  33. package/src/index.ts +43 -0
  34. package/src/integrations/paypal.ts +258 -0
  35. package/src/integrations/stripe.ts +280 -0
  36. package/src/integrations/webhook.ts +332 -0
  37. package/src/notifications/email.ts +161 -0
  38. package/src/notifications/inApp.ts +319 -0
  39. package/src/notifications/sms.ts +58 -0
  40. package/src/security/auth.ts +153 -0
  41. package/src/security/rateLimit.ts +77 -0
  42. package/src/security/validation.ts +166 -0
  43. package/src/server.ts +122 -0
  44. package/src/types/index.ts +790 -0
  45. package/src/utils/formatters.ts +72 -0
  46. package/src/utils/helpers.ts +193 -0
  47. package/src/utils/logger.ts +88 -0
  48. package/src/utils/validators.ts +39 -0
@@ -0,0 +1,339 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — API Controllers
3
+ // ============================================================
4
+
5
+ import { Request, Response } from 'express';
6
+ import { createLogger } from '../utils/logger';
7
+ import { appConfig } from '../config';
8
+ import {
9
+ ApiResponse,
10
+ ApiMeta,
11
+ PaginationMeta,
12
+ FilterQuery,
13
+ } from '../types';
14
+ import { AuthenticatedRequest } from '../security/auth';
15
+ import { v4 as uuidv4 } from 'uuid';
16
+
17
+ const log = createLogger('Controllers');
18
+
19
+ // ────────────────────────────────────────────────────────────
20
+ // RESPONSE HELPERS
21
+ // ────────────────────────────────────────────────────────────
22
+
23
+ function makeMeta(req: Request, pagination?: PaginationMeta): ApiMeta {
24
+ return {
25
+ timestamp: new Date().toISOString(),
26
+ requestId: (req as Request & { requestId?: string }).requestId ?? uuidv4(),
27
+ version: appConfig.version,
28
+ pagination,
29
+ };
30
+ }
31
+
32
+ function ok<T>(res: Response, req: Request, data: T, pagination?: PaginationMeta): void {
33
+ const body: ApiResponse<T> = {
34
+ success: true,
35
+ data,
36
+ meta: makeMeta(req, pagination),
37
+ };
38
+ res.status(200).json(body);
39
+ }
40
+
41
+ function created<T>(res: Response, req: Request, data: T): void {
42
+ const body: ApiResponse<T> = {
43
+ success: true,
44
+ data,
45
+ meta: makeMeta(req),
46
+ };
47
+ res.status(201).json(body);
48
+ }
49
+
50
+ function notFound(res: Response, req: Request, message: string): void {
51
+ res.status(404).json({
52
+ success: false,
53
+ error: { code: 'NOT_FOUND', message },
54
+ meta: makeMeta(req),
55
+ });
56
+ }
57
+
58
+ function badRequest(res: Response, req: Request, message: string, details?: unknown): void {
59
+ res.status(400).json({
60
+ success: false,
61
+ error: { code: 'BAD_REQUEST', message, details },
62
+ meta: makeMeta(req),
63
+ });
64
+ }
65
+
66
+ // ────────────────────────────────────────────────────────────
67
+ // PAYMENTS CONTROLLER
68
+ // ────────────────────────────────────────────────────────────
69
+
70
+ export const PaymentsController = {
71
+
72
+ async register(req: AuthenticatedRequest, res: Response): Promise<void> {
73
+ try {
74
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
75
+ const { ChargebackGuard } = require('../core/chargebackGuard');
76
+ const guard = req.app.locals['chargebackGuard'] as InstanceType<typeof ChargebackGuard>;
77
+
78
+ const result = await guard.registerPayment({
79
+ ...req.body,
80
+ customerIp: req.body.customerIp ?? req.ip,
81
+ userAgent: req.body.userAgent ?? req.headers['user-agent'],
82
+ });
83
+
84
+ created(res, req, result);
85
+ } catch (err) {
86
+ const message = err instanceof Error ? err.message : 'Registration failed';
87
+ log.error('Payment registration error', { error: message });
88
+ badRequest(res, req, message);
89
+ }
90
+ },
91
+
92
+ async list(req: AuthenticatedRequest, res: Response): Promise<void> {
93
+ try {
94
+ const query = req.query as FilterQuery;
95
+ const page = Number(query.page ?? 1);
96
+ const limit = Number(query.limit ?? 20);
97
+
98
+ // Placeholder — DB query goes here
99
+ const items: unknown[] = [];
100
+ const total = 0;
101
+
102
+ ok(res, req, items, {
103
+ page, limit, total,
104
+ totalPages: Math.ceil(total / limit),
105
+ hasNext: page * limit < total,
106
+ hasPrev: page > 1,
107
+ });
108
+ } catch (err) {
109
+ log.error('List payments error', { error: err instanceof Error ? err.message : err });
110
+ res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Failed to list payments' } });
111
+ }
112
+ },
113
+
114
+ async updateShipping(req: AuthenticatedRequest, res: Response): Promise<void> {
115
+ try {
116
+ const { orderId } = req.params as { orderId: string };
117
+ const { trackingNumber, carrier, shippedAt, estimatedDelivery } = req.body as {
118
+ trackingNumber: string;
119
+ carrier?: string;
120
+ shippedAt?: string;
121
+ estimatedDelivery?: string;
122
+ };
123
+
124
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
125
+ const { EvidenceStorage } = require('../evidence/storage');
126
+ const storage = new EvidenceStorage();
127
+
128
+ const updated = await storage.update(orderId, {
129
+ data: {
130
+ shippingData: { trackingNumber, carrier, shippingDate: shippedAt, estimatedDelivery },
131
+ },
132
+ } as Parameters<typeof storage.update>[1]);
133
+
134
+ if (!updated) {
135
+ notFound(res, req, `Order not found: ${orderId}`);
136
+ return;
137
+ }
138
+
139
+ ok(res, req, { orderId, trackingNumber, updated: true });
140
+ } catch (err) {
141
+ log.error('Update shipping error', { error: err instanceof Error ? err.message : err });
142
+ badRequest(res, req, err instanceof Error ? err.message : 'Update failed');
143
+ }
144
+ },
145
+ };
146
+
147
+ // ────────────────────────────────────────────────────────────
148
+ // DISPUTES CONTROLLER
149
+ // ────────────────────────────────────────────────────────────
150
+
151
+ export const DisputesController = {
152
+
153
+ async list(req: AuthenticatedRequest, res: Response): Promise<void> {
154
+ try {
155
+ const { StripeIntegration } = require('../integrations/stripe');
156
+ const stripe = new StripeIntegration();
157
+ const disputes = await stripe.listActionableDisputes();
158
+ ok(res, req, disputes);
159
+ } catch (err) {
160
+ log.error('List disputes error', { error: err instanceof Error ? err.message : err });
161
+ res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Failed to list disputes' } });
162
+ }
163
+ },
164
+
165
+ async getOne(req: AuthenticatedRequest, res: Response): Promise<void> {
166
+ try {
167
+ const { id } = req.params as { id: string };
168
+ const { StripeIntegration } = require('../integrations/stripe');
169
+ const stripe = new StripeIntegration();
170
+ const dispute = await stripe.getDispute(id);
171
+ ok(res, req, dispute);
172
+ } catch (err) {
173
+ notFound(res, req, `Dispute not found: ${req.params['id']}`);
174
+ }
175
+ },
176
+
177
+ async handle(req: AuthenticatedRequest, res: Response): Promise<void> {
178
+ try {
179
+ const { ChargebackGuard } = require('../core/chargebackGuard');
180
+ const guard = req.app.locals['chargebackGuard'] as InstanceType<typeof ChargebackGuard>;
181
+ const result = await guard.handleDispute(req.body);
182
+ ok(res, req, result);
183
+ } catch (err) {
184
+ const message = err instanceof Error ? err.message : 'Dispute handling failed';
185
+ log.error('Handle dispute error', { error: message });
186
+ res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message } });
187
+ }
188
+ },
189
+
190
+ async submitReply(req: AuthenticatedRequest, res: Response): Promise<void> {
191
+ try {
192
+ const { id } = req.params as { id: string };
193
+ const reply = req.body;
194
+
195
+ const { StripeIntegration } = require('../integrations/stripe');
196
+ const stripe = new StripeIntegration();
197
+ await stripe.submitDisputeEvidence(id, reply);
198
+
199
+ ok(res, req, { disputeId: id, submitted: true, submittedAt: new Date().toISOString() });
200
+ } catch (err) {
201
+ const message = err instanceof Error ? err.message : 'Submission failed';
202
+ badRequest(res, req, message);
203
+ }
204
+ },
205
+ };
206
+
207
+ // ────────────────────────────────────────────────────────────
208
+ // ANALYTICS CONTROLLER
209
+ // ────────────────────────────────────────────────────────────
210
+
211
+ export const AnalyticsController = {
212
+
213
+ async getStats(req: AuthenticatedRequest, res: Response): Promise<void> {
214
+ try {
215
+ const { ChargebackGuard } = require('../core/chargebackGuard');
216
+ const guard = req.app.locals['chargebackGuard'] as InstanceType<typeof ChargebackGuard>;
217
+ const q = req.query as { from?: string; to?: string };
218
+ const stats = await guard.getProtectionStats(
219
+ q.from ? new Date(q.from) : undefined,
220
+ q.to ? new Date(q.to) : undefined
221
+ );
222
+ ok(res, req, stats);
223
+ } catch (err) {
224
+ log.error('Stats error', { error: err instanceof Error ? err.message : err });
225
+ res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Stats unavailable' } });
226
+ }
227
+ },
228
+
229
+ async getDashboard(req: AuthenticatedRequest, res: Response): Promise<void> {
230
+ try {
231
+ const { DashboardService } = require('../analytics/dashboard');
232
+ const svc = new DashboardService();
233
+ const dashboard = await svc.getMetrics(req.merchantId);
234
+ ok(res, req, dashboard);
235
+ } catch (err) {
236
+ log.error('Dashboard error', { error: err instanceof Error ? err.message : err });
237
+ res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Dashboard unavailable' } });
238
+ }
239
+ },
240
+ };
241
+
242
+ // ────────────────────────────────────────────────────────────
243
+ // HEALTH CONTROLLER
244
+ // ────────────────────────────────────────────────────────────
245
+
246
+ export const HealthController = {
247
+
248
+ async check(req: Request, res: Response): Promise<void> {
249
+ try {
250
+ const guard = req.app.locals['chargebackGuard'];
251
+ const health = guard
252
+ ? await guard.getHealth()
253
+ : { success: true, data: { status: 'healthy', uptime: process.uptime() * 1000 } };
254
+
255
+ const statusCode = health.success ? 200 : 503;
256
+ res.status(statusCode).json({
257
+ ...health,
258
+ meta: makeMeta(req),
259
+ });
260
+ } catch {
261
+ res.status(503).json({ success: false, error: { code: 'HEALTH_CHECK_FAILED', message: 'Health check failed' } });
262
+ }
263
+ },
264
+
265
+ async ping(_req: Request, res: Response): Promise<void> {
266
+ res.status(200).json({ pong: true, timestamp: new Date().toISOString() });
267
+ },
268
+ };
269
+
270
+ // ────────────────────────────────────────────────────────────
271
+ // AUTH CONTROLLER
272
+ // ────────────────────────────────────────────────────────────
273
+
274
+ export const AuthController = {
275
+
276
+ async register(req: Request, res: Response): Promise<void> {
277
+ try {
278
+ const { hashPassword, signToken, generateMerchantApiKey } = require('./../../src/security/auth');
279
+ const { name, email, password, webhookUrl, plan } = req.body as {
280
+ name: string;
281
+ email: string;
282
+ password: string;
283
+ webhookUrl?: string;
284
+ plan: string;
285
+ };
286
+
287
+ const passwordHash = await hashPassword(password);
288
+ const merchantId = uuidv4();
289
+ const apiKey = generateMerchantApiKey();
290
+
291
+ // Store merchant (placeholder — DB layer goes here)
292
+ const merchant = {
293
+ merchantId,
294
+ name,
295
+ email,
296
+ passwordHash,
297
+ apiKey,
298
+ webhookUrl,
299
+ plan,
300
+ createdAt: new Date().toISOString(),
301
+ };
302
+
303
+ const token = signToken({ merchantId, email, plan });
304
+
305
+ created(res, req, {
306
+ merchantId,
307
+ email,
308
+ name,
309
+ plan,
310
+ apiKey,
311
+ token,
312
+ });
313
+ } catch (err) {
314
+ const message = err instanceof Error ? err.message : 'Registration failed';
315
+ badRequest(res, req, message);
316
+ }
317
+ },
318
+
319
+ async login(req: Request, res: Response): Promise<void> {
320
+ try {
321
+ const { signToken } = require('./../../src/security/auth');
322
+ const { email, password } = req.body as { email: string; password: string };
323
+
324
+ // Placeholder — real implementation queries DB
325
+ log.info(`Login attempt: ${email}`);
326
+
327
+ // For demo: return a token
328
+ const token = signToken({
329
+ merchantId: uuidv4(),
330
+ email,
331
+ plan: 'pro',
332
+ });
333
+
334
+ ok(res, req, { token, email });
335
+ } catch (err) {
336
+ res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: 'Invalid credentials' } });
337
+ }
338
+ },
339
+ };
@@ -0,0 +1,172 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — Express Middleware Stack
3
+ // ============================================================
4
+
5
+ import express, { Application, Request, Response, NextFunction } from 'express';
6
+ import cors from 'cors';
7
+ import helmet from 'helmet';
8
+ import compression from 'compression';
9
+ import morgan from 'morgan';
10
+ import { v4 as uuidv4 } from 'uuid';
11
+ import { createLogger } from '../utils/logger';
12
+ import { corsConfig, appConfig } from '../config';
13
+ import { apiRateLimit } from '../security/rateLimit';
14
+ import { sanitizeBody } from '../security/validation';
15
+
16
+ const log = createLogger('Middleware');
17
+
18
+ // ────────────────────────────────────────────────────────────
19
+ // REQUEST ID MIDDLEWARE
20
+ // ────────────────────────────────────────────────────────────
21
+
22
+ export function requestId(req: Request, res: Response, next: NextFunction): void {
23
+ const id = (req.headers['x-request-id'] as string) ?? uuidv4();
24
+ (req as Request & { requestId: string }).requestId = id;
25
+ res.setHeader('X-Request-Id', id);
26
+ next();
27
+ }
28
+
29
+ // ────────────────────────────────────────────────────────────
30
+ // RESPONSE TIME MIDDLEWARE
31
+ // ────────────────────────────────────────────────────────────
32
+
33
+ export function responseTime(req: Request, res: Response, next: NextFunction): void {
34
+ const start = Date.now();
35
+ res.on('finish', () => {
36
+ const duration = Date.now() - start;
37
+ res.setHeader('X-Response-Time', `${duration}ms`);
38
+ });
39
+ next();
40
+ }
41
+
42
+ // ────────────────────────────────────────────────────────────
43
+ // RAW BODY CAPTURE (needed for Stripe webhook verification)
44
+ // ────────────────────────────────────────────────────────────
45
+
46
+ export function captureRawBody(req: Request, res: Response, next: NextFunction): void {
47
+ if (req.path.includes('/webhook')) {
48
+ let raw = '';
49
+ req.on('data', (chunk: Buffer) => { raw += chunk.toString(); });
50
+ req.on('end', () => {
51
+ (req as Request & { rawBody: string }).rawBody = raw;
52
+ next();
53
+ });
54
+ } else {
55
+ next();
56
+ }
57
+ }
58
+
59
+ // ────────────────────────────────────────────────────────────
60
+ // NOT FOUND HANDLER
61
+ // ────────────────────────────────────────────────────────────
62
+
63
+ export function notFoundHandler(req: Request, res: Response): void {
64
+ res.status(404).json({
65
+ success: false,
66
+ error: {
67
+ code: 'NOT_FOUND',
68
+ message: `Route not found: ${req.method} ${req.path}`,
69
+ },
70
+ meta: {
71
+ timestamp: new Date().toISOString(),
72
+ requestId: (req as Request & { requestId?: string }).requestId,
73
+ version: appConfig.version,
74
+ },
75
+ });
76
+ }
77
+
78
+ // ────────────────────────────────────────────────────────────
79
+ // GLOBAL ERROR HANDLER
80
+ // ────────────────────────────────────────────────────────────
81
+
82
+ export function errorHandler(
83
+ err: Error & { status?: number; code?: string },
84
+ req: Request,
85
+ res: Response,
86
+ _next: NextFunction
87
+ ): void {
88
+ const status = err.status ?? 500;
89
+ const code = err.code ?? 'INTERNAL_ERROR';
90
+
91
+ log.error('Unhandled error', {
92
+ status,
93
+ code,
94
+ message: err.message,
95
+ path: req.path,
96
+ method: req.method,
97
+ requestId: (req as Request & { requestId?: string }).requestId,
98
+ stack: appConfig.isDev ? err.stack : undefined,
99
+ });
100
+
101
+ res.status(status).json({
102
+ success: false,
103
+ error: {
104
+ code,
105
+ message: status === 500 ? 'Internal server error' : err.message,
106
+ ...(appConfig.isDev && { stack: err.stack }),
107
+ },
108
+ meta: {
109
+ timestamp: new Date().toISOString(),
110
+ requestId: (req as Request & { requestId?: string }).requestId,
111
+ version: appConfig.version,
112
+ },
113
+ });
114
+ }
115
+
116
+ // ────────────────────────────────────────────────────────────
117
+ // APPLY ALL MIDDLEWARE
118
+ // ────────────────────────────────────────────────────────────
119
+
120
+ export function applyMiddleware(app: Application): void {
121
+ // Security headers
122
+ app.use(helmet({
123
+ crossOriginResourcePolicy: { policy: 'same-site' },
124
+ contentSecurityPolicy: {
125
+ directives: {
126
+ defaultSrc: ["'self'"],
127
+ scriptSrc: ["'self'"],
128
+ styleSrc: ["'self'", "'unsafe-inline'"],
129
+ imgSrc: ["'self'", 'data:'],
130
+ },
131
+ },
132
+ }));
133
+
134
+ // CORS
135
+ app.use(cors({
136
+ origin: corsConfig.origins,
137
+ methods: corsConfig.methods,
138
+ credentials: true,
139
+ optionsSuccessStatus: 200,
140
+ }));
141
+
142
+ // Compression
143
+ app.use(compression());
144
+
145
+ // Request ID
146
+ app.use(requestId);
147
+ app.use(responseTime);
148
+
149
+ // Raw body capture (before JSON parsing for webhooks)
150
+ app.use(captureRawBody);
151
+
152
+ // Body parsers
153
+ app.use(express.json({ limit: '5mb' }));
154
+ app.use(express.urlencoded({ extended: true, limit: '5mb' }));
155
+
156
+ // Sanitize inputs
157
+ app.use(sanitizeBody);
158
+
159
+ // Request logging
160
+ if (appConfig.isDev) {
161
+ app.use(morgan('dev'));
162
+ } else {
163
+ app.use(morgan('combined', {
164
+ stream: { write: (msg: string) => log.info(msg.trim()) },
165
+ }));
166
+ }
167
+
168
+ // Global rate limiting
169
+ app.use('/api/', apiRateLimit);
170
+
171
+ log.debug('Middleware stack applied');
172
+ }
@@ -0,0 +1,141 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — API Routes (Express Router)
3
+ // ============================================================
4
+
5
+ import { Router } from 'express';
6
+ import {
7
+ PaymentsController,
8
+ DisputesController,
9
+ AnalyticsController,
10
+ HealthController,
11
+ AuthController,
12
+ } from './controllers';
13
+ import { requireAuth } from '../security/auth';
14
+ import {
15
+ authRateLimit,
16
+ webhookRateLimit,
17
+ evidenceRateLimit,
18
+ } from '../security/rateLimit';
19
+ import {
20
+ validate,
21
+ RegisterPaymentSchema,
22
+ DisputeHandleSchema,
23
+ MerchantRegisterSchema,
24
+ MerchantLoginSchema,
25
+ PaginationSchema,
26
+ UpdateShippingSchema,
27
+ } from '../security/validation';
28
+ import { WebhookHandler } from '../integrations/webhook';
29
+ import { createLogger } from '../utils/logger';
30
+
31
+ const log = createLogger('Routes');
32
+
33
+ // ────────────────────────────────────────────────────────────
34
+ // ROOT ROUTER
35
+ // ────────────────────────────────────────────────────────────
36
+
37
+ export function createRouter(webhookHandler: WebhookHandler): Router {
38
+ const router = Router();
39
+
40
+ // ── Health ──────────────────────────────
41
+ router.get('/ping', HealthController.ping);
42
+ router.get('/health', HealthController.check);
43
+
44
+ // ── Auth ────────────────────────────────
45
+ router.post('/auth/register',
46
+ authRateLimit,
47
+ validate(MerchantRegisterSchema),
48
+ AuthController.register
49
+ );
50
+ router.post('/auth/login',
51
+ authRateLimit,
52
+ validate(MerchantLoginSchema),
53
+ AuthController.login
54
+ );
55
+
56
+ // ── Payments ────────────────────────────
57
+ router.post('/payments',
58
+ requireAuth,
59
+ evidenceRateLimit,
60
+ validate(RegisterPaymentSchema),
61
+ PaymentsController.register
62
+ );
63
+ router.get('/payments',
64
+ requireAuth,
65
+ validate(PaginationSchema, 'query'),
66
+ PaymentsController.list
67
+ );
68
+ router.patch('/payments/:orderId/shipping',
69
+ requireAuth,
70
+ validate(UpdateShippingSchema),
71
+ PaymentsController.updateShipping
72
+ );
73
+
74
+ // ── Disputes ────────────────────────────
75
+ router.get('/disputes',
76
+ requireAuth,
77
+ validate(PaginationSchema, 'query'),
78
+ DisputesController.list
79
+ );
80
+ router.get('/disputes/:id',
81
+ requireAuth,
82
+ DisputesController.getOne
83
+ );
84
+ router.post('/disputes/handle',
85
+ requireAuth,
86
+ validate(DisputeHandleSchema),
87
+ DisputesController.handle
88
+ );
89
+ router.post('/disputes/:id/reply',
90
+ requireAuth,
91
+ DisputesController.submitReply
92
+ );
93
+
94
+ // ── Analytics ───────────────────────────
95
+ router.get('/analytics/stats',
96
+ requireAuth,
97
+ AnalyticsController.getStats
98
+ );
99
+ router.get('/analytics/dashboard',
100
+ requireAuth,
101
+ AnalyticsController.getDashboard
102
+ );
103
+
104
+ // ── Webhooks ────────────────────────────
105
+ router.post('/webhooks/stripe',
106
+ webhookRateLimit,
107
+ webhookHandler.handleStripe()
108
+ );
109
+ router.post('/webhooks/paypal',
110
+ webhookRateLimit,
111
+ webhookHandler.handlePayPal()
112
+ );
113
+
114
+ log.debug('Routes registered');
115
+ return router;
116
+ }
117
+
118
+ // ────────────────────────────────────────────────────────────
119
+ // APP FACTORY
120
+ // ────────────────────────────────────────────────────────────
121
+
122
+ import express, { Application } from 'express';
123
+ import { applyMiddleware, notFoundHandler, errorHandler } from './middleware';
124
+
125
+ export function createApp(webhookHandler: WebhookHandler): Application {
126
+ const app = express();
127
+
128
+ // Apply middleware stack
129
+ applyMiddleware(app);
130
+
131
+ // Mount API routes
132
+ app.use('/api/v1', createRouter(webhookHandler));
133
+
134
+ // 404 handler
135
+ app.use(notFoundHandler);
136
+
137
+ // Error handler (must be last)
138
+ app.use(errorHandler);
139
+
140
+ return app;
141
+ }