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,77 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — Rate Limiting
3
+ // ============================================================
4
+
5
+ import rateLimit from 'express-rate-limit';
6
+ import { Request, Response } from 'express';
7
+ import { securityConfig } from '../config';
8
+
9
+ // ────────────────────────────────────────────────────────────
10
+ // STANDARD API RATE LIMITER
11
+ // ────────────────────────────────────────────────────────────
12
+
13
+ export const apiRateLimit = rateLimit({
14
+ windowMs: securityConfig.rateLimit.windowMs,
15
+ max: securityConfig.rateLimit.maxRequests,
16
+ standardHeaders: true,
17
+ legacyHeaders: false,
18
+ skipFailedRequests: securityConfig.rateLimit.skipFailedRequests,
19
+ message: {
20
+ success: false,
21
+ error: {
22
+ code: 'RATE_LIMIT_EXCEEDED',
23
+ message: `Too many requests. Limit: ${securityConfig.rateLimit.maxRequests} per ${securityConfig.rateLimit.windowMs / 60000} minutes`,
24
+ },
25
+ },
26
+ });
27
+
28
+ // ────────────────────────────────────────────────────────────
29
+ // STRICT LIMITER (for auth endpoints)
30
+ // ────────────────────────────────────────────────────────────
31
+
32
+ export const authRateLimit = rateLimit({
33
+ windowMs: 15 * 60 * 1000, // 15 minutes
34
+ max: 10,
35
+ standardHeaders: true,
36
+ legacyHeaders: false,
37
+ message: {
38
+ success: false,
39
+ error: {
40
+ code: 'AUTH_RATE_LIMIT_EXCEEDED',
41
+ message: 'Too many authentication attempts. Try again in 15 minutes.',
42
+ },
43
+ },
44
+ skipSuccessfulRequests: true,
45
+ });
46
+
47
+ // ────────────────────────────────────────────────────────────
48
+ // WEBHOOK LIMITER
49
+ // ────────────────────────────────────────────────────────────
50
+
51
+ export const webhookRateLimit = rateLimit({
52
+ windowMs: 60 * 1000, // 1 minute
53
+ max: 500, // webhooks can be high-volume
54
+ standardHeaders: true,
55
+ legacyHeaders: false,
56
+ message: {
57
+ success: false,
58
+ error: { code: 'WEBHOOK_RATE_LIMIT_EXCEEDED', message: 'Webhook rate limit exceeded' },
59
+ },
60
+ });
61
+
62
+ // ────────────────────────────────────────────────────────────
63
+ // EVIDENCE SUBMISSION LIMITER
64
+ // ────────────────────────────────────────────────────────────
65
+
66
+ export const evidenceRateLimit = rateLimit({
67
+ windowMs: 60 * 60 * 1000, // 1 hour
68
+ max: 200,
69
+ standardHeaders: true,
70
+ legacyHeaders: false,
71
+ keyGenerator: (req: Request) =>
72
+ (req as Request & { merchantId?: string }).merchantId ?? req.ip ?? 'unknown',
73
+ message: {
74
+ success: false,
75
+ error: { code: 'EVIDENCE_RATE_LIMIT_EXCEEDED', message: 'Evidence submission rate limit exceeded' },
76
+ },
77
+ });
@@ -0,0 +1,166 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — Request Validation Schemas (Zod)
3
+ // ============================================================
4
+
5
+ import { z } from 'zod';
6
+ import { Request, Response, NextFunction } from 'express';
7
+ import { createLogger } from '../utils/logger';
8
+
9
+ const log = createLogger('Validation');
10
+
11
+ // ────────────────────────────────────────────────────────────
12
+ // SCHEMAS
13
+ // ────────────────────────────────────────────────────────────
14
+
15
+ export const RegisterPaymentSchema = z.object({
16
+ orderId: z.string().min(1).max(255),
17
+ amount: z.number().positive(),
18
+ currency: z.string().length(3).toUpperCase(),
19
+ customerEmail: z.string().email(),
20
+ customerName: z.string().max(255).optional(),
21
+ customerIp: z.string().ip().optional(),
22
+ userAgent: z.string().max(512).optional(),
23
+ transactionId: z.string().max(255).optional(),
24
+ paymentMethod: z.string().max(50).optional(),
25
+ cardLastFour: z.string().max(4).optional(),
26
+ cardBrand: z.string().max(50).optional(),
27
+ provider: z.enum(['stripe', 'paypal', 'square', 'braintree']).optional(),
28
+ shippingAddress: z.object({
29
+ line1: z.string().max(255),
30
+ line2: z.string().max(255).optional(),
31
+ city: z.string().max(100),
32
+ state: z.string().max(100).optional(),
33
+ postalCode: z.string().max(20),
34
+ country: z.string().length(2).toUpperCase(),
35
+ }).optional(),
36
+ sessionData: z.object({
37
+ sessionId: z.string().optional(),
38
+ duration: z.number().min(0).optional(),
39
+ pageViews: z.array(z.any()).optional(),
40
+ clickTracking: z.array(z.any()).optional(),
41
+ timeOnCheckout: z.number().min(0).optional(),
42
+ referrer: z.string().url().optional(),
43
+ }).optional(),
44
+ deviceFingerprint: z.object({
45
+ screenResolution: z.string().optional(),
46
+ timezone: z.string().optional(),
47
+ language: z.string().optional(),
48
+ platform: z.string().optional(),
49
+ }).optional(),
50
+ metadata: z.record(z.unknown()).optional(),
51
+ });
52
+
53
+ export const DisputeHandleSchema = z.object({
54
+ id: z.string().min(1),
55
+ orderId: z.string().optional(),
56
+ amount: z.number().min(0),
57
+ currency: z.string().optional(),
58
+ reason: z.string().min(1),
59
+ status: z.string().min(1),
60
+ provider: z.enum(['stripe', 'paypal', 'square', 'braintree']),
61
+ evidenceDueBy: z.string().optional(),
62
+ metadata: z.record(z.unknown()).optional(),
63
+ });
64
+
65
+ export const PaginationSchema = z.object({
66
+ page: z.coerce.number().int().positive().default(1),
67
+ limit: z.coerce.number().int().min(1).max(100).default(20),
68
+ sortBy: z.string().optional(),
69
+ sortOrder: z.enum(['asc', 'desc']).default('desc'),
70
+ from: z.string().optional(),
71
+ to: z.string().optional(),
72
+ status: z.string().optional(),
73
+ reason: z.string().optional(),
74
+ provider: z.string().optional(),
75
+ search: z.string().max(255).optional(),
76
+ });
77
+
78
+ export const MerchantRegisterSchema = z.object({
79
+ name: z.string().min(2).max(255),
80
+ email: z.string().email(),
81
+ password: z.string().min(8).max(128),
82
+ webhookUrl: z.string().url().optional(),
83
+ plan: z.enum(['free', 'starter', 'pro', 'enterprise']).default('free'),
84
+ });
85
+
86
+ export const MerchantLoginSchema = z.object({
87
+ email: z.string().email(),
88
+ password: z.string().min(1),
89
+ });
90
+
91
+ export const UpdateShippingSchema = z.object({
92
+ orderId: z.string().min(1),
93
+ trackingNumber: z.string().min(1),
94
+ carrier: z.string().optional(),
95
+ shippedAt: z.string().optional(),
96
+ estimatedDelivery: z.string().optional(),
97
+ });
98
+
99
+ // ────────────────────────────────────────────────────────────
100
+ // VALIDATION MIDDLEWARE FACTORY
101
+ // ────────────────────────────────────────────────────────────
102
+
103
+ type SchemaSource = 'body' | 'query' | 'params';
104
+
105
+ export function validate<T extends z.ZodTypeAny>(
106
+ schema: T,
107
+ source: SchemaSource = 'body'
108
+ ): (req: Request, res: Response, next: NextFunction) => void {
109
+ return (req: Request, res: Response, next: NextFunction): void => {
110
+ const result = schema.safeParse(req[source]);
111
+
112
+ if (!result.success) {
113
+ const errors = result.error.errors.map(e => ({
114
+ field: e.path.join('.'),
115
+ message: e.message,
116
+ code: e.code,
117
+ }));
118
+
119
+ log.debug('Validation failed', { source, errors });
120
+
121
+ res.status(422).json({
122
+ success: false,
123
+ error: {
124
+ code: 'VALIDATION_ERROR',
125
+ message: 'Request validation failed',
126
+ details: { errors },
127
+ },
128
+ });
129
+ return;
130
+ }
131
+
132
+ // Attach parsed & coerced data
133
+ if (source === 'body') { req.body = result.data; }
134
+ else if (source === 'query') { req.query = result.data as typeof req.query; }
135
+
136
+ next();
137
+ };
138
+ }
139
+
140
+ // ────────────────────────────────────────────────────────────
141
+ // SANITIZE MIDDLEWARE (removes XSS risk from strings)
142
+ // ────────────────────────────────────────────────────────────
143
+
144
+ export function sanitizeBody(req: Request, _res: Response, next: NextFunction): void {
145
+ if (req.body && typeof req.body === 'object') {
146
+ req.body = sanitizeObject(req.body as Record<string, unknown>);
147
+ }
148
+ next();
149
+ }
150
+
151
+ function sanitizeObject(obj: Record<string, unknown>): Record<string, unknown> {
152
+ const result: Record<string, unknown> = {};
153
+ for (const [key, value] of Object.entries(obj)) {
154
+ if (typeof value === 'string') {
155
+ result[key] = value
156
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
157
+ .replace(/on\w+="[^"]*"/gi, '')
158
+ .trim();
159
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
160
+ result[key] = sanitizeObject(value as Record<string, unknown>);
161
+ } else {
162
+ result[key] = value;
163
+ }
164
+ }
165
+ return result;
166
+ }
package/src/server.ts ADDED
@@ -0,0 +1,122 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — HTTP Server Entry Point
3
+ // ============================================================
4
+
5
+ import dotenv from 'dotenv';
6
+ dotenv.config();
7
+
8
+ import http from 'http';
9
+ import { createChargebackGuard } from './index';
10
+ import { WebhookHandler } from './integrations/webhook';
11
+ import { createApp } from './api/routes';
12
+ import { createLogger } from './utils/logger';
13
+ import { appConfig } from './config';
14
+ import { DisputeStatus, NotificationEvent } from './types';
15
+
16
+ const log = createLogger('Server');
17
+
18
+ async function bootstrap(): Promise<void> {
19
+ // ── 1. Initialize ChargebackGuard ────────────────────────
20
+ log.info('Bootstrapping ChargebackGuard...');
21
+
22
+ const guard = await createChargebackGuard({
23
+ autoReply: true,
24
+ evidenceCollection: true,
25
+ });
26
+
27
+ // ── 2. Setup Webhook Handler ─────────────────────────────
28
+ const webhookHandler = new WebhookHandler();
29
+
30
+ webhookHandler.onDisputeDetected(async (dispute) => {
31
+ log.info(`Processing dispute: ${dispute.id}`);
32
+ try {
33
+ await guard.handleDispute(dispute);
34
+ } catch (err) {
35
+ log.error('Dispute handling error', {
36
+ error: err instanceof Error ? err.message : String(err),
37
+ disputeId: dispute.id,
38
+ });
39
+ }
40
+ });
41
+
42
+ webhookHandler.onDisputeResolved(async (disputeId, status, provider) => {
43
+ const event = status === DisputeStatus.WON
44
+ ? NotificationEvent.DISPUTE_WON
45
+ : NotificationEvent.DISPUTE_LOST;
46
+
47
+ guard.events.emitEvent(event, { disputeId, status, provider });
48
+ });
49
+
50
+ // ── 3. Create Express app ────────────────────────────────
51
+ const app = createApp(webhookHandler);
52
+ app.locals['chargebackGuard'] = guard;
53
+
54
+ // ── 4. Start HTTP server ─────────────────────────────────
55
+ const server = http.createServer(app);
56
+
57
+ server.listen(appConfig.port, () => {
58
+ log.info(`🛡️ ChargebackGuard API running`, {
59
+ url: `http://localhost:${appConfig.port}`,
60
+ environment: appConfig.env,
61
+ version: appConfig.version,
62
+ });
63
+
64
+ console.log(`
65
+ ╔══════════════════════════════════════════════════╗
66
+ ║ 🛡️ CHARGEBACK GUARD v${appConfig.version} ║
67
+ ╠══════════════════════════════════════════════════╣
68
+ ║ Server: http://localhost:${appConfig.port} ║
69
+ ║ Env: ${appConfig.env.padEnd(38)} ║
70
+ ╠══════════════════════════════════════════════════╣
71
+ ║ API ENDPOINTS ║
72
+ ║ POST /api/v1/auth/register — Register merchant ║
73
+ ║ POST /api/v1/auth/login — Login ║
74
+ ║ POST /api/v1/payments — Register payment ║
75
+ ║ GET /api/v1/disputes — List disputes ║
76
+ ║ POST /api/v1/webhooks/stripe — Stripe webhook ║
77
+ ║ POST /api/v1/webhooks/paypal — PayPal webhook ║
78
+ ║ GET /api/v1/analytics/stats — Stats ║
79
+ ║ GET /api/v1/health — Health check ║
80
+ ╚══════════════════════════════════════════════════╝
81
+ `);
82
+ });
83
+
84
+ // ── 5. Graceful shutdown ─────────────────────────────────
85
+ const shutdown = async (signal: string): Promise<void> => {
86
+ log.info(`Received ${signal} — shutting down gracefully`);
87
+
88
+ server.close(async () => {
89
+ try {
90
+ await guard.shutdown();
91
+ log.info('Shutdown complete');
92
+ process.exit(0);
93
+ } catch (err) {
94
+ log.error('Shutdown error', { error: err instanceof Error ? err.message : String(err) });
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ // Force shutdown after 30s
100
+ setTimeout(() => {
101
+ log.error('Forced shutdown after timeout');
102
+ process.exit(1);
103
+ }, 30000);
104
+ };
105
+
106
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
107
+ process.on('SIGINT', () => shutdown('SIGINT'));
108
+
109
+ process.on('uncaughtException', (err) => {
110
+ log.error('Uncaught exception', { error: err.message, stack: err.stack });
111
+ shutdown('uncaughtException').catch(console.error);
112
+ });
113
+
114
+ process.on('unhandledRejection', (reason) => {
115
+ log.error('Unhandled promise rejection', { reason: String(reason) });
116
+ });
117
+ }
118
+
119
+ bootstrap().catch(err => {
120
+ console.error('Bootstrap failed:', err);
121
+ process.exit(1);
122
+ });