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,72 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — Data Formatters
3
+ // ============================================================
4
+
5
+ export function formatAmount(cents: number, currency = 'USD'): string {
6
+ return new Intl.NumberFormat('en-US', {
7
+ style: 'currency',
8
+ currency,
9
+ minimumFractionDigits: 2,
10
+ }).format(cents / 100);
11
+ }
12
+
13
+ export function formatDate(date: string | Date, locale = 'en-US'): string {
14
+ return new Intl.DateTimeFormat(locale, {
15
+ year: 'numeric',
16
+ month: 'short',
17
+ day: 'numeric',
18
+ }).format(new Date(date));
19
+ }
20
+
21
+ export function formatDateTime(date: string | Date, locale = 'en-US'): string {
22
+ return new Intl.DateTimeFormat(locale, {
23
+ year: 'numeric',
24
+ month: 'short',
25
+ day: 'numeric',
26
+ hour: '2-digit',
27
+ minute: '2-digit',
28
+ }).format(new Date(date));
29
+ }
30
+
31
+ export function formatTrustScore(score: number): string {
32
+ if (score >= 90) { return `${score} (Excellent)`; }
33
+ if (score >= 75) { return `${score} (Good)`; }
34
+ if (score >= 55) { return `${score} (Fair)`; }
35
+ if (score >= 35) { return `${score} (Poor)`; }
36
+ return `${score} (Critical)`;
37
+ }
38
+
39
+ export function formatRiskLevel(level: string): string {
40
+ const labels: Record<string, string> = {
41
+ very_low: '🟢 Very Low',
42
+ low: '🟢 Low',
43
+ medium: '🟡 Medium',
44
+ high: '🟠 High',
45
+ critical: '🔴 Critical',
46
+ };
47
+ return labels[level] ?? level;
48
+ }
49
+
50
+ export function formatDisputeReason(reason: string): string {
51
+ const labels: Record<string, string> = {
52
+ product_not_received: 'Product Not Received',
53
+ product_unacceptable: 'Product Unacceptable',
54
+ unauthorized_transaction: 'Unauthorized Transaction',
55
+ duplicate_transaction: 'Duplicate Transaction',
56
+ credit_not_processed: 'Credit Not Processed',
57
+ subscription_cancelled: 'Subscription Cancelled',
58
+ fraudulent: 'Fraudulent',
59
+ general: 'General',
60
+ };
61
+ return labels[reason] ?? reason.replace(/_/g, ' ');
62
+ }
63
+
64
+ export function formatConfidenceLevel(level: string): string {
65
+ const labels: Record<string, string> = {
66
+ very_high: '✅ Very High (>85%)',
67
+ high: '👍 High (>65%)',
68
+ medium: '⚠️ Medium (>45%)',
69
+ low: '❌ Low (<45%)',
70
+ };
71
+ return labels[level] ?? level;
72
+ }
@@ -0,0 +1,193 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — General Helper Utilities
3
+ // ============================================================
4
+
5
+ import crypto from 'crypto';
6
+
7
+ // ────────────────────────────────────────────────────────────
8
+ // STRING HELPERS
9
+ // ────────────────────────────────────────────────────────────
10
+
11
+ export function truncate(str: string, maxLength: number): string {
12
+ if (str.length <= maxLength) { return str; }
13
+ return str.slice(0, maxLength - 3) + '...';
14
+ }
15
+
16
+ export function capitalize(str: string): string {
17
+ return str.charAt(0).toUpperCase() + str.slice(1);
18
+ }
19
+
20
+ export function slugify(str: string): string {
21
+ return str.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
22
+ }
23
+
24
+ export function maskEmail(email: string): string {
25
+ const [local, domain] = email.split('@');
26
+ if (!local || !domain) { return email; }
27
+ const masked = local.slice(0, 2) + '*'.repeat(Math.max(0, local.length - 2));
28
+ return `${masked}@${domain}`;
29
+ }
30
+
31
+ // ────────────────────────────────────────────────────────────
32
+ // NUMBER HELPERS
33
+ // ────────────────────────────────────────────────────────────
34
+
35
+ export function centsToDollars(cents: number): string {
36
+ return `$${(cents / 100).toFixed(2)}`;
37
+ }
38
+
39
+ export function dollarsToCents(dollars: number): number {
40
+ return Math.round(dollars * 100);
41
+ }
42
+
43
+ export function formatPercentage(value: number, decimals = 1): string {
44
+ return `${value.toFixed(decimals)}%`;
45
+ }
46
+
47
+ // ────────────────────────────────────────────────────────────
48
+ // DATE HELPERS
49
+ // ────────────────────────────────────────────────────────────
50
+
51
+ export function daysFromNow(days: number): Date {
52
+ const d = new Date();
53
+ d.setDate(d.getDate() + days);
54
+ return d;
55
+ }
56
+
57
+ export function daysBetween(from: Date, to: Date): number {
58
+ return Math.floor((to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24));
59
+ }
60
+
61
+ export function isExpired(date: Date | string): boolean {
62
+ return new Date(date).getTime() < Date.now();
63
+ }
64
+
65
+ export function isDeadlineClose(date: Date | string, hoursThreshold = 48): boolean {
66
+ const remaining = new Date(date).getTime() - Date.now();
67
+ return remaining > 0 && remaining < hoursThreshold * 3600000;
68
+ }
69
+
70
+ export function formatDuration(ms: number): string {
71
+ if (ms < 1000) { return `${ms}ms`; }
72
+ if (ms < 60000) { return `${(ms / 1000).toFixed(1)}s`; }
73
+ if (ms < 3600000) { return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`; }
74
+ return `${(ms / 3600000).toFixed(1)}h`;
75
+ }
76
+
77
+ // ────────────────────────────────────────────────────────────
78
+ // OBJECT HELPERS
79
+ // ────────────────────────────────────────────────────────────
80
+
81
+ export function omit<T extends object, K extends keyof T>(
82
+ obj: T,
83
+ keys: K[]
84
+ ): Omit<T, K> {
85
+ const result = { ...obj };
86
+ keys.forEach(key => delete result[key]);
87
+ return result;
88
+ }
89
+
90
+ export function pick<T extends object, K extends keyof T>(
91
+ obj: T,
92
+ keys: K[]
93
+ ): Pick<T, K> {
94
+ const result = {} as Pick<T, K>;
95
+ keys.forEach(key => { result[key] = obj[key]; });
96
+ return result;
97
+ }
98
+
99
+ export function deepClone<T>(obj: T): T {
100
+ return JSON.parse(JSON.stringify(obj));
101
+ }
102
+
103
+ export function isEmpty(value: unknown): boolean {
104
+ if (value === null || value === undefined) { return true; }
105
+ if (typeof value === 'string') { return value.trim().length === 0; }
106
+ if (Array.isArray(value)) { return value.length === 0; }
107
+ if (typeof value === 'object') { return Object.keys(value).length === 0; }
108
+ return false;
109
+ }
110
+
111
+ // ────────────────────────────────────────────────────────────
112
+ // ARRAY HELPERS
113
+ // ────────────────────────────────────────────────────────────
114
+
115
+ export function chunkArray<T>(arr: T[], size: number): T[][] {
116
+ const chunks: T[][] = [];
117
+ for (let i = 0; i < arr.length; i += size) {
118
+ chunks.push(arr.slice(i, i + size));
119
+ }
120
+ return chunks;
121
+ }
122
+
123
+ export function uniqueBy<T>(arr: T[], key: keyof T): T[] {
124
+ const seen = new Set();
125
+ return arr.filter(item => {
126
+ const val = item[key];
127
+ if (seen.has(val)) { return false; }
128
+ seen.add(val);
129
+ return true;
130
+ });
131
+ }
132
+
133
+ export function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
134
+ return arr.reduce((groups, item) => {
135
+ const k = String(item[key]);
136
+ if (!groups[k]) { groups[k] = []; }
137
+ groups[k].push(item);
138
+ return groups;
139
+ }, {} as Record<string, T[]>);
140
+ }
141
+
142
+ // ────────────────────────────────────────────────────────────
143
+ // ASYNC HELPERS
144
+ // ────────────────────────────────────────────────────────────
145
+
146
+ export async function sleep(ms: number): Promise<void> {
147
+ return new Promise(r => setTimeout(r, ms));
148
+ }
149
+
150
+ export async function retry<T>(
151
+ fn: () => Promise<T>,
152
+ retries = 3,
153
+ delayMs = 1000
154
+ ): Promise<T> {
155
+ let lastError: Error | undefined;
156
+ for (let i = 0; i <= retries; i++) {
157
+ try {
158
+ return await fn();
159
+ } catch (err) {
160
+ lastError = err instanceof Error ? err : new Error(String(err));
161
+ if (i < retries) { await sleep(delayMs * Math.pow(2, i)); } // exponential backoff
162
+ }
163
+ }
164
+ throw lastError;
165
+ }
166
+
167
+ export async function timeout<T>(
168
+ promise: Promise<T>,
169
+ ms: number,
170
+ message = `Operation timed out after ${ms}ms`
171
+ ): Promise<T> {
172
+ return Promise.race([
173
+ promise,
174
+ new Promise<never>((_, reject) =>
175
+ setTimeout(() => reject(new Error(message)), ms)
176
+ ),
177
+ ]);
178
+ }
179
+
180
+ // ────────────────────────────────────────────────────────────
181
+ // ID GENERATION
182
+ // ────────────────────────────────────────────────────────────
183
+
184
+ export function generateId(prefix = ''): string {
185
+ const random = crypto.randomBytes(12).toString('base64url');
186
+ return prefix ? `${prefix}_${random}` : random;
187
+ }
188
+
189
+ export function generateOrderId(): string {
190
+ const ts = Date.now().toString(36).toUpperCase();
191
+ const rand = crypto.randomBytes(4).toString('hex').toUpperCase();
192
+ return `ORD-${ts}-${rand}`;
193
+ }
@@ -0,0 +1,88 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — Logger Utility
3
+ // ============================================================
4
+
5
+ import winston from 'winston';
6
+ import DailyRotateFile from 'winston-daily-rotate-file';
7
+ import path from 'path';
8
+ import { appConfig } from '../config';
9
+
10
+ const { combine, timestamp, printf, colorize, errors, json } = winston.format;
11
+
12
+ // ────────────────────────────────────────────────────────────
13
+ // CUSTOM FORMATS
14
+ // ────────────────────────────────────────────────────────────
15
+
16
+ const devFormat = combine(
17
+ colorize({ all: true }),
18
+ timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
19
+ errors({ stack: true }),
20
+ printf(({ level, message, timestamp: ts, ...meta }) => {
21
+ const metaStr = Object.keys(meta).length ? `\n${JSON.stringify(meta, null, 2)}` : '';
22
+ return `[${ts}] ${level}: ${message}${metaStr}`;
23
+ })
24
+ );
25
+
26
+ const prodFormat = combine(
27
+ timestamp(),
28
+ errors({ stack: true }),
29
+ json()
30
+ );
31
+
32
+ // ────────────────────────────────────────────────────────────
33
+ // TRANSPORTS
34
+ // ────────────────────────────────────────────────────────────
35
+
36
+ const transports: winston.transport[] = [
37
+ new winston.transports.Console({
38
+ format: appConfig.isDev ? devFormat : prodFormat,
39
+ silent: process.env.NODE_ENV === 'test',
40
+ }),
41
+ ];
42
+
43
+ // Add file transports in production / staging
44
+ if (!appConfig.isDev && process.env.NODE_ENV !== 'test') {
45
+ transports.push(
46
+ new DailyRotateFile({
47
+ filename: path.join('logs', 'application-%DATE%.log'),
48
+ datePattern: 'YYYY-MM-DD',
49
+ zippedArchive: true,
50
+ maxSize: '20m',
51
+ maxFiles: '30d',
52
+ format: prodFormat,
53
+ }),
54
+ new DailyRotateFile({
55
+ filename: path.join('logs', 'error-%DATE%.log'),
56
+ datePattern: 'YYYY-MM-DD',
57
+ zippedArchive: true,
58
+ maxSize: '20m',
59
+ maxFiles: '30d',
60
+ level: 'error',
61
+ format: prodFormat,
62
+ })
63
+ );
64
+ }
65
+
66
+ // ────────────────────────────────────────────────────────────
67
+ // LOGGER INSTANCE
68
+ // ────────────────────────────────────────────────────────────
69
+
70
+ export const logger = winston.createLogger({
71
+ level: appConfig.logLevel,
72
+ defaultMeta: {
73
+ service: 'chargeback-guard',
74
+ version: appConfig.version,
75
+ },
76
+ transports,
77
+ exitOnError: false,
78
+ });
79
+
80
+ // ────────────────────────────────────────────────────────────
81
+ // CHILD LOGGER FACTORY
82
+ // ────────────────────────────────────────────────────────────
83
+
84
+ export const createLogger = (module: string): winston.Logger => {
85
+ return logger.child({ module });
86
+ };
87
+
88
+ export default logger;
@@ -0,0 +1,39 @@
1
+ // ============================================================
2
+ // CHARGEBACK GUARD — Validation Utility Functions
3
+ // ============================================================
4
+
5
+ export function isValidEmail(email: string): boolean {
6
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
7
+ }
8
+
9
+ export function isValidIP(ip: string): boolean {
10
+ const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
11
+ const ipv6 = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
12
+ return ipv4.test(ip) || ipv6.test(ip);
13
+ }
14
+
15
+ export function isValidCurrency(currency: string): boolean {
16
+ return /^[A-Z]{3}$/.test(currency);
17
+ }
18
+
19
+ export function isValidUrl(url: string): boolean {
20
+ try { new URL(url); return true; }
21
+ catch { return false; }
22
+ }
23
+
24
+ export function isValidUUID(id: string): boolean {
25
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
26
+ }
27
+
28
+ export function isPositiveAmount(amount: number): boolean {
29
+ return typeof amount === 'number' && amount > 0 && isFinite(amount);
30
+ }
31
+
32
+ export function sanitizeString(str: string, maxLength = 255): string {
33
+ return str.trim().slice(0, maxLength).replace(/[<>]/g, '');
34
+ }
35
+
36
+ export function isValidDisputeId(id: string): boolean {
37
+ // Stripe: dp_xxx | PayPal: PP-D-xxx
38
+ return /^(dp_|PP-D-|ch_)[A-Za-z0-9_-]{10,}$/.test(id) || id.length >= 5;
39
+ }