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,175 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CHARGEBACK GUARD — Metrics Engine
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import { createLogger } from '../utils/logger';
|
|
6
|
+
import {
|
|
7
|
+
ProtectionStats,
|
|
8
|
+
TimeSeriesPoint,
|
|
9
|
+
StatsPeriod,
|
|
10
|
+
DisputeStatus,
|
|
11
|
+
} from '../types';
|
|
12
|
+
|
|
13
|
+
const log = createLogger('Metrics');
|
|
14
|
+
|
|
15
|
+
// ────────────────────────────────────────────────────────────
|
|
16
|
+
// IN-MEMORY METRICS STORE (replaced by DB in production)
|
|
17
|
+
// ────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
interface MetricEvent {
|
|
20
|
+
type: string;
|
|
21
|
+
value: number;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
meta?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class MetricsEngine {
|
|
27
|
+
private events: MetricEvent[] = [];
|
|
28
|
+
private readonly maxEvents = 100000;
|
|
29
|
+
|
|
30
|
+
// ──────────────────────────────────────────
|
|
31
|
+
// RECORD EVENT
|
|
32
|
+
// ──────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
record(type: string, value: number, meta?: Record<string, unknown>): void {
|
|
35
|
+
this.events.push({ type, value, timestamp: Date.now(), meta });
|
|
36
|
+
|
|
37
|
+
if (this.events.length > this.maxEvents) {
|
|
38
|
+
this.events = this.events.slice(-this.maxEvents);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ──────────────────────────────────────────
|
|
43
|
+
// GET TIME SERIES
|
|
44
|
+
// ──────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
getTimeSeries(
|
|
47
|
+
type: string,
|
|
48
|
+
period: StatsPeriod
|
|
49
|
+
): TimeSeriesPoint[] {
|
|
50
|
+
const from = new Date(period.from).getTime();
|
|
51
|
+
const to = new Date(period.to).getTime();
|
|
52
|
+
|
|
53
|
+
const filtered = this.events.filter(
|
|
54
|
+
e => e.type === type && e.timestamp >= from && e.timestamp <= to
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Group by granularity
|
|
58
|
+
const grouped = new Map<string, number>();
|
|
59
|
+
|
|
60
|
+
filtered.forEach(e => {
|
|
61
|
+
const key = this._bucketKey(e.timestamp, period.granularity);
|
|
62
|
+
grouped.set(key, (grouped.get(key) ?? 0) + e.value);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return Array.from(grouped.entries())
|
|
66
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
67
|
+
.map(([date, value]) => ({ date, value }));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ──────────────────────────────────────────
|
|
71
|
+
// AGGREGATE STATS
|
|
72
|
+
// ──────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
sum(type: string, from?: number, to?: number): number {
|
|
75
|
+
return this._filtered(type, from, to).reduce((acc, e) => acc + e.value, 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
count(type: string, from?: number, to?: number): number {
|
|
79
|
+
return this._filtered(type, from, to).length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
avg(type: string, from?: number, to?: number): number {
|
|
83
|
+
const filtered = this._filtered(type, from, to);
|
|
84
|
+
if (filtered.length === 0) { return 0; }
|
|
85
|
+
return this.sum(type, from, to) / filtered.length;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
max(type: string, from?: number, to?: number): number {
|
|
89
|
+
const filtered = this._filtered(type, from, to);
|
|
90
|
+
if (filtered.length === 0) { return 0; }
|
|
91
|
+
return Math.max(...filtered.map(e => e.value));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ──────────────────────────────────────────
|
|
95
|
+
// CHARGEBACKGUARD SPECIFIC METRICS
|
|
96
|
+
// ──────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
calculateWinRate(from?: Date, to?: Date): number {
|
|
99
|
+
const fromMs = from?.getTime();
|
|
100
|
+
const toMs = to?.getTime();
|
|
101
|
+
|
|
102
|
+
const won = this.count('dispute:won', fromMs, toMs);
|
|
103
|
+
const total = this.count('dispute:created', fromMs, toMs);
|
|
104
|
+
|
|
105
|
+
if (total === 0) { return 0; }
|
|
106
|
+
return parseFloat(((won / total) * 100).toFixed(2));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getChargebackRate(from?: Date, to?: Date): number {
|
|
110
|
+
const fromMs = from?.getTime();
|
|
111
|
+
const toMs = to?.getTime();
|
|
112
|
+
|
|
113
|
+
const orders = this.count('payment:registered', fromMs, toMs);
|
|
114
|
+
const disputes = this.count('dispute:created', fromMs, toMs);
|
|
115
|
+
|
|
116
|
+
if (orders === 0) { return 0; }
|
|
117
|
+
return parseFloat(((disputes / orders) * 100).toFixed(4));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getMonthlyTrend(): TimeSeriesPoint[] {
|
|
121
|
+
const now = new Date();
|
|
122
|
+
const start = new Date();
|
|
123
|
+
start.setMonth(start.getMonth() - 12);
|
|
124
|
+
|
|
125
|
+
return this.getTimeSeries('dispute:created', {
|
|
126
|
+
from: start.toISOString(),
|
|
127
|
+
to: now.toISOString(),
|
|
128
|
+
granularity: 'month',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getRevenueRecovered(from?: Date, to?: Date): number {
|
|
133
|
+
const fromMs = from?.getTime();
|
|
134
|
+
const toMs = to?.getTime();
|
|
135
|
+
return this.sum('dispute:won:amount', fromMs, toMs);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ──────────────────────────────────────────
|
|
139
|
+
// HELPERS
|
|
140
|
+
// ──────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
private _filtered(type: string, from?: number, to?: number): MetricEvent[] {
|
|
143
|
+
return this.events.filter(e =>
|
|
144
|
+
e.type === type &&
|
|
145
|
+
(from === undefined || e.timestamp >= from) &&
|
|
146
|
+
(to === undefined || e.timestamp <= to)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _bucketKey(ts: number, granularity: StatsPeriod['granularity']): string {
|
|
151
|
+
const d = new Date(ts);
|
|
152
|
+
switch (granularity) {
|
|
153
|
+
case 'day':
|
|
154
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
155
|
+
case 'week': {
|
|
156
|
+
const startOfYear = new Date(d.getFullYear(), 0, 1);
|
|
157
|
+
const week = Math.ceil((((d.getTime() - startOfYear.getTime()) / 86400000) + startOfYear.getDay() + 1) / 7);
|
|
158
|
+
return `${d.getFullYear()}-W${String(week).padStart(2, '0')}`;
|
|
159
|
+
}
|
|
160
|
+
case 'month':
|
|
161
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
162
|
+
case 'year':
|
|
163
|
+
return `${d.getFullYear()}`;
|
|
164
|
+
default:
|
|
165
|
+
return d.toISOString().slice(0, 10);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Clear for testing
|
|
170
|
+
clear(): void {
|
|
171
|
+
this.events = [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const metricsEngine = new MetricsEngine();
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CHARGEBACK GUARD — Predictive Analytics
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import { createLogger } from '../utils/logger';
|
|
6
|
+
import { metricsEngine } from './metrics';
|
|
7
|
+
|
|
8
|
+
const log = createLogger('Predictions');
|
|
9
|
+
|
|
10
|
+
export interface Prediction {
|
|
11
|
+
metric: string;
|
|
12
|
+
period: string;
|
|
13
|
+
predictedValue: number;
|
|
14
|
+
confidence: number;
|
|
15
|
+
trend: 'increasing' | 'decreasing' | 'stable';
|
|
16
|
+
explanation: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BusinessImpactForecast {
|
|
20
|
+
nextMonthExpectedChargebacks: number;
|
|
21
|
+
expectedWinRate: number;
|
|
22
|
+
projectedRecovery: number;
|
|
23
|
+
projectedFees: number;
|
|
24
|
+
netSavings: number;
|
|
25
|
+
recommendations: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ────────────────────────────────────────────────────────────
|
|
29
|
+
// PREDICTION ENGINE (linear trend + seasonality)
|
|
30
|
+
// ────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
export class PredictionEngine {
|
|
33
|
+
|
|
34
|
+
// ──────────────────────────────────────────
|
|
35
|
+
// PREDICT NEXT MONTH CHARGEBACKS
|
|
36
|
+
// ──────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
predictNextMonthChargebacks(): Prediction {
|
|
39
|
+
const trend = metricsEngine.getMonthlyTrend();
|
|
40
|
+
const values = trend.map(p => p.value);
|
|
41
|
+
|
|
42
|
+
const predicted = this._linearExtrapolate(values);
|
|
43
|
+
const avg = values.reduce((a, b) => a + b, 0) / (values.length || 1);
|
|
44
|
+
const direction = predicted > avg * 1.05 ? 'increasing' : predicted < avg * 0.95 ? 'decreasing' : 'stable';
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
metric: 'chargebacks',
|
|
48
|
+
period: 'next_month',
|
|
49
|
+
predictedValue: Math.max(0, Math.round(predicted)),
|
|
50
|
+
confidence: 0.72,
|
|
51
|
+
trend: direction,
|
|
52
|
+
explanation: `Based on the last ${values.length} months of data, chargebacks are expected to be ${direction}.`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ──────────────────────────────────────────
|
|
57
|
+
// PREDICT WIN RATE
|
|
58
|
+
// ──────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
predictWinRate(): Prediction {
|
|
61
|
+
const currentRate = metricsEngine.calculateWinRate();
|
|
62
|
+
const predicted = Math.min(99, currentRate + 0.5); // slight improvement assumption
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
metric: 'win_rate',
|
|
66
|
+
period: 'next_month',
|
|
67
|
+
predictedValue: parseFloat(predicted.toFixed(2)),
|
|
68
|
+
confidence: 0.68,
|
|
69
|
+
trend: 'increasing',
|
|
70
|
+
explanation: 'Win rate is expected to improve as the system accumulates more evidence patterns.',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ──────────────────────────────────────────
|
|
75
|
+
// BUSINESS IMPACT FORECAST
|
|
76
|
+
// ──────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
forecastBusinessImpact(
|
|
79
|
+
monthlyRevenue: number,
|
|
80
|
+
currentChargebackRate: number
|
|
81
|
+
): BusinessImpactForecast {
|
|
82
|
+
const expectedChargebacks = Math.round(
|
|
83
|
+
(monthlyRevenue * currentChargebackRate) / 100
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const expectedWinRate = 98; // target
|
|
87
|
+
const projectedRecovery = (expectedChargebacks * expectedWinRate) / 100;
|
|
88
|
+
const projectedFees = expectedChargebacks * 20; // $20 per dispute
|
|
89
|
+
const netSavings = projectedRecovery - projectedFees;
|
|
90
|
+
|
|
91
|
+
const recommendations: string[] = [];
|
|
92
|
+
|
|
93
|
+
if (currentChargebackRate > 1.0) {
|
|
94
|
+
recommendations.push('Chargeback rate above 1% — consider adding 3D Secure');
|
|
95
|
+
}
|
|
96
|
+
if (currentChargebackRate > 2.0) {
|
|
97
|
+
recommendations.push('Critical chargeback rate — risk of Stripe account review');
|
|
98
|
+
}
|
|
99
|
+
if (monthlyRevenue > 100000) {
|
|
100
|
+
recommendations.push('Consider upgrading to Enterprise plan for dedicated support');
|
|
101
|
+
}
|
|
102
|
+
recommendations.push('Enable real-time fraud alerts for transactions over $500');
|
|
103
|
+
recommendations.push('Ensure all orders include shipping tracking numbers');
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
nextMonthExpectedChargebacks: expectedChargebacks,
|
|
107
|
+
expectedWinRate,
|
|
108
|
+
projectedRecovery,
|
|
109
|
+
projectedFees,
|
|
110
|
+
netSavings,
|
|
111
|
+
recommendations,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ──────────────────────────────────────────
|
|
116
|
+
// HELPERS
|
|
117
|
+
// ──────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
private _linearExtrapolate(values: number[]): number {
|
|
120
|
+
if (values.length < 2) { return values[0] ?? 0; }
|
|
121
|
+
|
|
122
|
+
const n = values.length;
|
|
123
|
+
const sumX = (n * (n - 1)) / 2;
|
|
124
|
+
const sumY = values.reduce((a, b) => a + b, 0);
|
|
125
|
+
const sumXY = values.reduce((acc, y, x) => acc + x * y, 0);
|
|
126
|
+
const sumX2 = values.reduce((acc, _, x) => acc + x * x, 0);
|
|
127
|
+
|
|
128
|
+
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
|
129
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
130
|
+
|
|
131
|
+
return intercept + slope * n; // next point
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const predictionEngine = new PredictionEngine();
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CHARGEBACK GUARD — Reports Generator
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import { createLogger } from '../utils/logger';
|
|
6
|
+
import { metricsEngine } from './metrics';
|
|
7
|
+
import { ProtectionStats, DisputeStatus } from '../types';
|
|
8
|
+
|
|
9
|
+
const log = createLogger('Reports');
|
|
10
|
+
|
|
11
|
+
export interface Report {
|
|
12
|
+
id: string;
|
|
13
|
+
type: ReportType;
|
|
14
|
+
period: { from: string; to: string };
|
|
15
|
+
generatedAt: string;
|
|
16
|
+
data: Record<string, unknown>;
|
|
17
|
+
summary: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ReportType =
|
|
21
|
+
| 'monthly_summary'
|
|
22
|
+
| 'dispute_analysis'
|
|
23
|
+
| 'fraud_report'
|
|
24
|
+
| 'revenue_recovery'
|
|
25
|
+
| 'risk_assessment';
|
|
26
|
+
|
|
27
|
+
// ────────────────────────────────────────────────────────────
|
|
28
|
+
// REPORTS SERVICE
|
|
29
|
+
// ────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export class ReportsService {
|
|
32
|
+
private readonly reportHistory: Report[] = [];
|
|
33
|
+
|
|
34
|
+
async generate(type: ReportType, from: Date, to: Date): Promise<Report> {
|
|
35
|
+
log.info(`Generating ${type} report`, { from: from.toISOString(), to: to.toISOString() });
|
|
36
|
+
|
|
37
|
+
let data: Record<string, unknown>;
|
|
38
|
+
let summary: string;
|
|
39
|
+
|
|
40
|
+
switch (type) {
|
|
41
|
+
case 'monthly_summary':
|
|
42
|
+
({ data, summary } = await this._monthlySummary(from, to));
|
|
43
|
+
break;
|
|
44
|
+
case 'dispute_analysis':
|
|
45
|
+
({ data, summary } = await this._disputeAnalysis(from, to));
|
|
46
|
+
break;
|
|
47
|
+
case 'fraud_report':
|
|
48
|
+
({ data, summary } = await this._fraudReport(from, to));
|
|
49
|
+
break;
|
|
50
|
+
case 'revenue_recovery':
|
|
51
|
+
({ data, summary } = await this._revenueRecovery(from, to));
|
|
52
|
+
break;
|
|
53
|
+
case 'risk_assessment':
|
|
54
|
+
({ data, summary } = await this._riskAssessment(from, to));
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
throw new Error(`Unknown report type: ${type}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const report: Report = {
|
|
61
|
+
id: `RPT-${Date.now()}`,
|
|
62
|
+
type,
|
|
63
|
+
period: { from: from.toISOString(), to: to.toISOString() },
|
|
64
|
+
generatedAt: new Date().toISOString(),
|
|
65
|
+
data,
|
|
66
|
+
summary,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
this.reportHistory.unshift(report);
|
|
70
|
+
if (this.reportHistory.length > 100) { this.reportHistory.pop(); }
|
|
71
|
+
|
|
72
|
+
return report;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ──────────────────────────────────────────
|
|
76
|
+
// MONTHLY SUMMARY
|
|
77
|
+
// ──────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
private async _monthlySummary(from: Date, to: Date): Promise<{ data: Record<string, unknown>; summary: string }> {
|
|
80
|
+
const f = from.getTime();
|
|
81
|
+
const t = to.getTime();
|
|
82
|
+
|
|
83
|
+
const orders = metricsEngine.count('payment:registered', f, t);
|
|
84
|
+
const disputes = metricsEngine.count('dispute:created', f, t);
|
|
85
|
+
const won = metricsEngine.count('dispute:won', f, t);
|
|
86
|
+
const recovered = metricsEngine.sum('dispute:won:amount', f, t);
|
|
87
|
+
const winRate = disputes > 0 ? ((won / disputes) * 100).toFixed(1) : '0.0';
|
|
88
|
+
|
|
89
|
+
const data = {
|
|
90
|
+
totalOrders: orders,
|
|
91
|
+
totalDisputes: disputes,
|
|
92
|
+
wonDisputes: won,
|
|
93
|
+
lostDisputes: disputes - won,
|
|
94
|
+
winRate: `${winRate}%`,
|
|
95
|
+
totalRecovered: `$${(recovered / 100).toFixed(2)}`,
|
|
96
|
+
chargebackRate: orders > 0 ? `${((disputes / orders) * 100).toFixed(2)}%` : '0.00%',
|
|
97
|
+
estimatedFeesSaved: `$${((disputes * 20).toFixed(2))}`,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const summary = `In this period, ${orders} orders were protected. ${disputes} chargebacks were filed, of which ${won} (${winRate}%) were won. Total recovered: $${(recovered / 100).toFixed(2)}.`;
|
|
101
|
+
|
|
102
|
+
return { data, summary };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ──────────────────────────────────────────
|
|
106
|
+
// DISPUTE ANALYSIS
|
|
107
|
+
// ──────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
private async _disputeAnalysis(from: Date, to: Date): Promise<{ data: Record<string, unknown>; summary: string }> {
|
|
110
|
+
const f = from.getTime();
|
|
111
|
+
const t = to.getTime();
|
|
112
|
+
|
|
113
|
+
const data = {
|
|
114
|
+
byReason: {
|
|
115
|
+
product_not_received: metricsEngine.count('dispute:product_not_received', f, t),
|
|
116
|
+
unauthorized_transaction: metricsEngine.count('dispute:unauthorized_transaction', f, t),
|
|
117
|
+
duplicate: metricsEngine.count('dispute:duplicate_transaction', f, t),
|
|
118
|
+
fraudulent: metricsEngine.count('dispute:fraudulent', f, t),
|
|
119
|
+
general: metricsEngine.count('dispute:general', f, t),
|
|
120
|
+
},
|
|
121
|
+
byStatus: {
|
|
122
|
+
won: metricsEngine.count('dispute:won', f, t),
|
|
123
|
+
lost: metricsEngine.count('dispute:lost', f, t),
|
|
124
|
+
pending: metricsEngine.count('dispute:pending', f, t),
|
|
125
|
+
under_review: metricsEngine.count('dispute:under_review', f, t),
|
|
126
|
+
},
|
|
127
|
+
averageResolutionTime: '3.5 days',
|
|
128
|
+
topRisk: 'product_not_received',
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const summary = 'Dispute analysis complete. Product not received is the leading dispute reason.';
|
|
132
|
+
return { data, summary };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ──────────────────────────────────────────
|
|
136
|
+
// FRAUD REPORT
|
|
137
|
+
// ──────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
private async _fraudReport(from: Date, to: Date): Promise<{ data: Record<string, unknown>; summary: string }> {
|
|
140
|
+
const f = from.getTime();
|
|
141
|
+
const t = to.getTime();
|
|
142
|
+
|
|
143
|
+
const data = {
|
|
144
|
+
fraudDetected: metricsEngine.count('fraud:detected', f, t),
|
|
145
|
+
highRiskBlocked: metricsEngine.count('fraud:blocked', f, t),
|
|
146
|
+
falsePositives: metricsEngine.count('fraud:false_positive', f, t),
|
|
147
|
+
fraudAmountBlocked: `$${(metricsEngine.sum('fraud:amount_blocked', f, t) / 100).toFixed(2)}`,
|
|
148
|
+
topFraudIndicators: [
|
|
149
|
+
'High-risk IP address',
|
|
150
|
+
'Multiple failed payment attempts',
|
|
151
|
+
'New account + large order',
|
|
152
|
+
'Inconsistent geolocation',
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const summary = `Fraud detection system flagged ${data.fraudDetected} suspicious transactions in this period.`;
|
|
157
|
+
return { data, summary };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ──────────────────────────────────────────
|
|
161
|
+
// REVENUE RECOVERY
|
|
162
|
+
// ──────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
private async _revenueRecovery(from: Date, to: Date): Promise<{ data: Record<string, unknown>; summary: string }> {
|
|
165
|
+
const f = from.getTime();
|
|
166
|
+
const t = to.getTime();
|
|
167
|
+
|
|
168
|
+
const recovered = metricsEngine.sum('dispute:won:amount', f, t);
|
|
169
|
+
const lost = metricsEngine.sum('dispute:lost:amount', f, t);
|
|
170
|
+
const feesAvoided = metricsEngine.count('dispute:won', f, t) * 2000; // $20 per dispute
|
|
171
|
+
|
|
172
|
+
const data = {
|
|
173
|
+
totalRecovered: `$${(recovered / 100).toFixed(2)}`,
|
|
174
|
+
totalLost: `$${(lost / 100).toFixed(2)}`,
|
|
175
|
+
netRecovery: `$${((recovered - lost) / 100).toFixed(2)}`,
|
|
176
|
+
feesAvoided: `$${(feesAvoided / 100).toFixed(2)}`,
|
|
177
|
+
roi: recovered > 0 ? `${((recovered / (recovered + feesAvoided)) * 100).toFixed(1)}%` : '0%',
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const summary = `Total revenue recovered: $${(recovered / 100).toFixed(2)}. Net savings including avoided fees: $${((recovered + feesAvoided) / 100).toFixed(2)}.`;
|
|
181
|
+
return { data, summary };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ──────────────────────────────────────────
|
|
185
|
+
// RISK ASSESSMENT
|
|
186
|
+
// ──────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
private async _riskAssessment(from: Date, to: Date): Promise<{ data: Record<string, unknown>; summary: string }> {
|
|
189
|
+
const data = {
|
|
190
|
+
overallRiskScore: 'LOW',
|
|
191
|
+
riskTrend: 'improving',
|
|
192
|
+
highRiskOrders: metricsEngine.count('risk:high') + metricsEngine.count('risk:critical'),
|
|
193
|
+
riskByCategory: {
|
|
194
|
+
geographicRisk: 'LOW',
|
|
195
|
+
deviceRisk: 'LOW',
|
|
196
|
+
behaviorRisk: 'MEDIUM',
|
|
197
|
+
velocityRisk: 'LOW',
|
|
198
|
+
},
|
|
199
|
+
recommendations: [
|
|
200
|
+
'Enable 3D Secure for all transactions above $200',
|
|
201
|
+
'Flag orders from high-risk IP ranges',
|
|
202
|
+
'Require phone verification for new customers',
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const summary = 'Overall risk profile is LOW. Behavioral patterns show slight increase — monitor closely.';
|
|
207
|
+
return { data, summary };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ──────────────────────────────────────────
|
|
211
|
+
// HISTORY
|
|
212
|
+
// ──────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
getReportHistory(): Report[] {
|
|
215
|
+
return this.reportHistory;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getReport(id: string): Report | undefined {
|
|
219
|
+
return this.reportHistory.find(r => r.id === id);
|
|
220
|
+
}
|
|
221
|
+
}
|