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.
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/docs/api.md +278 -0
- package/docs/architecture.md +281 -0
- package/docs/configuration.md +292 -0
- package/docs/getting-started.md +155 -0
- package/examples/advancedConfig.ts +123 -0
- package/examples/basicUsage.ts +98 -0
- package/examples/stripeIntegration.ts +106 -0
- package/package.json +181 -0
- package/src/ai/fraudDetection.ts +261 -0
- package/src/ai/patternRecognition.ts +218 -0
- package/src/analytics/dashboard.ts +195 -0
- package/src/analytics/metrics.ts +175 -0
- package/src/analytics/predictions.ts +135 -0
- package/src/analytics/reports.ts +221 -0
- package/src/api/controllers.ts +339 -0
- package/src/api/middleware.ts +172 -0
- package/src/api/routes.ts +141 -0
- package/src/config.ts +231 -0
- package/src/core/chargebackGuard.ts +616 -0
- package/src/core/eventEmitter.ts +118 -0
- package/src/core/lifecycle.ts +215 -0
- package/src/database/schema.ts +392 -0
- package/src/dispute/analyzer.ts +317 -0
- package/src/dispute/bankIntegration.ts +274 -0
- package/src/dispute/detector.ts +239 -0
- package/src/dispute/responseEngine.ts +440 -0
- package/src/evidence/collector.ts +426 -0
- package/src/evidence/encryption.ts +168 -0
- package/src/evidence/storage.ts +197 -0
- package/src/evidence/validator.ts +184 -0
- package/src/index.ts +43 -0
- package/src/integrations/paypal.ts +258 -0
- package/src/integrations/stripe.ts +280 -0
- package/src/integrations/webhook.ts +332 -0
- package/src/notifications/email.ts +161 -0
- package/src/notifications/inApp.ts +319 -0
- package/src/notifications/sms.ts +58 -0
- package/src/security/auth.ts +153 -0
- package/src/security/rateLimit.ts +77 -0
- package/src/security/validation.ts +166 -0
- package/src/server.ts +122 -0
- package/src/types/index.ts +790 -0
- package/src/utils/formatters.ts +72 -0
- package/src/utils/helpers.ts +193 -0
- package/src/utils/logger.ts +88 -0
- package/src/utils/validators.ts +39 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CHARGEBACK GUARD — Enhanced Event Emitter
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import { EventEmitter as NodeEventEmitter } from 'events';
|
|
6
|
+
import { NotificationEvent } from '../types';
|
|
7
|
+
import { createLogger } from '../utils/logger';
|
|
8
|
+
|
|
9
|
+
const log = createLogger('EventEmitter');
|
|
10
|
+
|
|
11
|
+
export interface EventPayload {
|
|
12
|
+
event: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type EventHandler = (payload: EventPayload) => void | Promise<void>;
|
|
18
|
+
|
|
19
|
+
// ────────────────────────────────────────────────────────────
|
|
20
|
+
// ENHANCED EVENT EMITTER
|
|
21
|
+
// ────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export class ChargebackEventEmitter extends NodeEventEmitter {
|
|
24
|
+
private readonly eventHistory: EventPayload[] = [];
|
|
25
|
+
private readonly maxHistorySize: number = 500;
|
|
26
|
+
private readonly asyncHandlers: Map<string, EventHandler[]> = new Map();
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
super();
|
|
30
|
+
this.setMaxListeners(50);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ──────────────────────────────────────────
|
|
34
|
+
// EMIT WITH STRUCTURED PAYLOAD
|
|
35
|
+
// ──────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
emitEvent(event: NotificationEvent | string, data: Record<string, unknown>): boolean {
|
|
38
|
+
const payload: EventPayload = {
|
|
39
|
+
event,
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
data,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Store in history
|
|
45
|
+
this.eventHistory.push(payload);
|
|
46
|
+
if (this.eventHistory.length > this.maxHistorySize) {
|
|
47
|
+
this.eventHistory.shift();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
log.debug(`Event emitted: ${event}`, { data });
|
|
51
|
+
|
|
52
|
+
// Run async handlers
|
|
53
|
+
const asyncList = this.asyncHandlers.get(event) ?? [];
|
|
54
|
+
asyncList.forEach(handler => {
|
|
55
|
+
handler(payload).catch(err => {
|
|
56
|
+
log.error(`Async handler error for event "${event}"`, { error: err.message });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return this.emit(event, payload);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ──────────────────────────────────────────
|
|
64
|
+
// REGISTER ASYNC HANDLER
|
|
65
|
+
// ──────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
onAsync(event: string, handler: EventHandler): this {
|
|
68
|
+
if (!this.asyncHandlers.has(event)) {
|
|
69
|
+
this.asyncHandlers.set(event, []);
|
|
70
|
+
}
|
|
71
|
+
this.asyncHandlers.get(event)!.push(handler);
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ──────────────────────────────────────────
|
|
76
|
+
// WAIT FOR EVENT (Promise-based)
|
|
77
|
+
// ──────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
waitFor(event: string, timeoutMs = 30000): Promise<EventPayload> {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
reject(new Error(`Timeout waiting for event: ${event}`));
|
|
83
|
+
}, timeoutMs);
|
|
84
|
+
|
|
85
|
+
this.once(event, (payload: EventPayload) => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
resolve(payload);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ──────────────────────────────────────────
|
|
93
|
+
// EVENT HISTORY
|
|
94
|
+
// ──────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
getHistory(event?: string): EventPayload[] {
|
|
97
|
+
if (event) {
|
|
98
|
+
return this.eventHistory.filter(e => e.event === event);
|
|
99
|
+
}
|
|
100
|
+
return [...this.eventHistory];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
clearHistory(): void {
|
|
104
|
+
this.eventHistory.length = 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ──────────────────────────────────────────
|
|
108
|
+
// STATS
|
|
109
|
+
// ──────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
getStats(): Record<string, number> {
|
|
112
|
+
const stats: Record<string, number> = {};
|
|
113
|
+
this.eventHistory.forEach(({ event }) => {
|
|
114
|
+
stats[event] = (stats[event] ?? 0) + 1;
|
|
115
|
+
});
|
|
116
|
+
return stats;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CHARGEBACK GUARD — Lifecycle Manager
|
|
3
|
+
// Handles initialization, health-checks, and graceful shutdown
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
import { createLogger } from '../utils/logger';
|
|
7
|
+
|
|
8
|
+
const log = createLogger('Lifecycle');
|
|
9
|
+
|
|
10
|
+
export type LifecycleStatus =
|
|
11
|
+
| 'uninitialized'
|
|
12
|
+
| 'initializing'
|
|
13
|
+
| 'ready'
|
|
14
|
+
| 'degraded'
|
|
15
|
+
| 'shutting_down'
|
|
16
|
+
| 'terminated';
|
|
17
|
+
|
|
18
|
+
export interface HealthCheck {
|
|
19
|
+
name: string;
|
|
20
|
+
status: 'healthy' | 'unhealthy' | 'degraded';
|
|
21
|
+
latencyMs?: number;
|
|
22
|
+
message?: string;
|
|
23
|
+
checkedAt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LifecycleHook {
|
|
27
|
+
name: string;
|
|
28
|
+
fn: () => Promise<void>;
|
|
29
|
+
timeout?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ────────────────────────────────────────────────────────────
|
|
33
|
+
// LIFECYCLE MANAGER CLASS
|
|
34
|
+
// ────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export class LifecycleManager {
|
|
37
|
+
private status: LifecycleStatus = 'uninitialized';
|
|
38
|
+
private startTime: Date = new Date();
|
|
39
|
+
private initHooks: LifecycleHook[] = [];
|
|
40
|
+
private shutdownHooks: LifecycleHook[] = [];
|
|
41
|
+
private healthCheckers: Map<string, () => Promise<HealthCheck>> = new Map();
|
|
42
|
+
private lastHealthCheck: HealthCheck[] = [];
|
|
43
|
+
|
|
44
|
+
// ──────────────────────────────────────────
|
|
45
|
+
// STATUS MANAGEMENT
|
|
46
|
+
// ──────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
getStatus(): LifecycleStatus {
|
|
49
|
+
return this.status;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isReady(): boolean {
|
|
53
|
+
return this.status === 'ready';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getUptime(): number {
|
|
57
|
+
return Date.now() - this.startTime.getTime();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ──────────────────────────────────────────
|
|
61
|
+
// HOOK REGISTRATION
|
|
62
|
+
// ──────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
registerInitHook(hook: LifecycleHook): void {
|
|
65
|
+
this.initHooks.push(hook);
|
|
66
|
+
log.debug(`Init hook registered: ${hook.name}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
registerShutdownHook(hook: LifecycleHook): void {
|
|
70
|
+
this.shutdownHooks.push(hook);
|
|
71
|
+
log.debug(`Shutdown hook registered: ${hook.name}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
registerHealthChecker(name: string, checker: () => Promise<HealthCheck>): void {
|
|
75
|
+
this.healthCheckers.set(name, checker);
|
|
76
|
+
log.debug(`Health checker registered: ${name}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ──────────────────────────────────────────
|
|
80
|
+
// INITIALIZATION
|
|
81
|
+
// ──────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
async initialize(): Promise<void> {
|
|
84
|
+
if (this.status !== 'uninitialized') {
|
|
85
|
+
throw new Error(`Cannot initialize: current status is "${this.status}"`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.status = 'initializing';
|
|
89
|
+
this.startTime = new Date();
|
|
90
|
+
log.info('ChargebackGuard initializing...');
|
|
91
|
+
|
|
92
|
+
for (const hook of this.initHooks) {
|
|
93
|
+
try {
|
|
94
|
+
log.debug(`Running init hook: ${hook.name}`);
|
|
95
|
+
await this.runWithTimeout(hook.fn, hook.timeout ?? 30000, hook.name);
|
|
96
|
+
log.debug(`Init hook completed: ${hook.name}`);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
+
log.error(`Init hook failed: ${hook.name} — ${msg}`);
|
|
100
|
+
this.status = 'degraded';
|
|
101
|
+
throw new Error(`Initialization failed at hook "${hook.name}": ${msg}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.status = 'ready';
|
|
106
|
+
log.info('ChargebackGuard ready ✅', { uptimeMs: this.getUptime() });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ──────────────────────────────────────────
|
|
110
|
+
// GRACEFUL SHUTDOWN
|
|
111
|
+
// ──────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
async shutdown(reason = 'manual'): Promise<void> {
|
|
114
|
+
if (this.status === 'shutting_down' || this.status === 'terminated') {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.status = 'shutting_down';
|
|
119
|
+
log.info(`ChargebackGuard shutting down. Reason: ${reason}`);
|
|
120
|
+
|
|
121
|
+
// Run shutdown hooks in reverse order
|
|
122
|
+
for (const hook of [...this.shutdownHooks].reverse()) {
|
|
123
|
+
try {
|
|
124
|
+
log.debug(`Running shutdown hook: ${hook.name}`);
|
|
125
|
+
await this.runWithTimeout(hook.fn, hook.timeout ?? 10000, hook.name);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
128
|
+
log.warn(`Shutdown hook failed (non-fatal): ${hook.name} — ${msg}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.status = 'terminated';
|
|
133
|
+
log.info('ChargebackGuard terminated.');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ──────────────────────────────────────────
|
|
137
|
+
// HEALTH CHECKS
|
|
138
|
+
// ──────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
async runHealthChecks(): Promise<HealthCheck[]> {
|
|
141
|
+
const results: HealthCheck[] = [];
|
|
142
|
+
|
|
143
|
+
for (const [name, checker] of this.healthCheckers.entries()) {
|
|
144
|
+
try {
|
|
145
|
+
const start = Date.now();
|
|
146
|
+
const result = await this.runWithTimeout(
|
|
147
|
+
() => checker(),
|
|
148
|
+
5000,
|
|
149
|
+
`health:${name}`
|
|
150
|
+
);
|
|
151
|
+
results.push({ ...result, latencyMs: Date.now() - start });
|
|
152
|
+
} catch (err) {
|
|
153
|
+
results.push({
|
|
154
|
+
name,
|
|
155
|
+
status: 'unhealthy',
|
|
156
|
+
message: err instanceof Error ? err.message : 'Unknown error',
|
|
157
|
+
checkedAt: new Date().toISOString(),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.lastHealthCheck = results;
|
|
163
|
+
return results;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getLastHealthCheck(): HealthCheck[] {
|
|
167
|
+
return this.lastHealthCheck;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async getSystemHealth(): Promise<{
|
|
171
|
+
status: 'healthy' | 'degraded' | 'unhealthy';
|
|
172
|
+
checks: HealthCheck[];
|
|
173
|
+
uptime: number;
|
|
174
|
+
lifecycleStatus: LifecycleStatus;
|
|
175
|
+
}> {
|
|
176
|
+
const checks = await this.runHealthChecks();
|
|
177
|
+
const hasUnhealthy = checks.some(c => c.status === 'unhealthy');
|
|
178
|
+
const hasDegraded = checks.some(c => c.status === 'degraded');
|
|
179
|
+
|
|
180
|
+
const overallStatus = hasUnhealthy ? 'unhealthy' : hasDegraded ? 'degraded' : 'healthy';
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
status: overallStatus,
|
|
184
|
+
checks,
|
|
185
|
+
uptime: this.getUptime(),
|
|
186
|
+
lifecycleStatus: this.status,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ──────────────────────────────────────────
|
|
191
|
+
// PRIVATE HELPERS
|
|
192
|
+
// ──────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
private async runWithTimeout<T>(
|
|
195
|
+
fn: () => Promise<T>,
|
|
196
|
+
timeoutMs: number,
|
|
197
|
+
label: string
|
|
198
|
+
): Promise<T> {
|
|
199
|
+
return new Promise<T>((resolve, reject) => {
|
|
200
|
+
const timer = setTimeout(() => {
|
|
201
|
+
reject(new Error(`Timeout after ${timeoutMs}ms: ${label}`));
|
|
202
|
+
}, timeoutMs);
|
|
203
|
+
|
|
204
|
+
fn()
|
|
205
|
+
.then(result => {
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
resolve(result);
|
|
208
|
+
})
|
|
209
|
+
.catch(err => {
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
reject(err);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|