@nehorai/payments-drizzle 0.1.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/dist/index.cjs +1310 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +1289 -0
- package/dist/index.js.map +1 -0
- package/dist/provider-health.drizzle-repository-DfRo4qJ8.d.cts +185 -0
- package/dist/provider-health.drizzle-repository-DfRo4qJ8.d.ts +185 -0
- package/dist/repositories/index.cjs +1190 -0
- package/dist/repositories/index.cjs.map +1 -0
- package/dist/repositories/index.d.cts +16 -0
- package/dist/repositories/index.d.ts +16 -0
- package/dist/repositories/index.js +1177 -0
- package/dist/repositories/index.js.map +1 -0
- package/dist/schema/index.cjs +276 -0
- package/dist/schema/index.cjs.map +1 -0
- package/dist/schema/index.d.cts +1729 -0
- package/dist/schema/index.d.ts +1729 -0
- package/dist/schema/index.js +269 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/storage/index.cjs +124 -0
- package/dist/storage/index.cjs.map +1 -0
- package/dist/storage/index.d.cts +52 -0
- package/dist/storage/index.d.ts +52 -0
- package/dist/storage/index.js +96 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
// src/repositories/base-drizzle.repository.ts
|
|
2
|
+
function toCamelCase(row) {
|
|
3
|
+
const result = {};
|
|
4
|
+
for (const [key, value] of Object.entries(row)) {
|
|
5
|
+
const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
6
|
+
result[camelKey] = value;
|
|
7
|
+
}
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
function toSnakeCase(obj) {
|
|
11
|
+
const result = {};
|
|
12
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
13
|
+
if (value === void 0) continue;
|
|
14
|
+
const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
15
|
+
result[snakeKey] = value;
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
function applyPagination(items, total, limit, offset) {
|
|
20
|
+
return {
|
|
21
|
+
data: items,
|
|
22
|
+
total,
|
|
23
|
+
limit,
|
|
24
|
+
offset,
|
|
25
|
+
hasMore: offset + items.length < total
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function parseNumeric(value) {
|
|
29
|
+
if (value === null || value === void 0) return 0;
|
|
30
|
+
if (typeof value === "number") return value;
|
|
31
|
+
return parseFloat(value) || 0;
|
|
32
|
+
}
|
|
33
|
+
function normalizeArrayFilter(value) {
|
|
34
|
+
if (value === void 0) return void 0;
|
|
35
|
+
return Array.isArray(value) ? value : [value];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/repositories/transaction.drizzle-repository.ts
|
|
39
|
+
import { eq, and, gte, lte, inArray, sql, count } from "drizzle-orm";
|
|
40
|
+
|
|
41
|
+
// src/schema/payment-transactions.ts
|
|
42
|
+
import { pgTable, uuid, text, numeric, timestamp, jsonb, index, unique } from "drizzle-orm/pg-core";
|
|
43
|
+
var paymentTransactions = pgTable(
|
|
44
|
+
"payment_transactions",
|
|
45
|
+
{
|
|
46
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
47
|
+
// Idempotency - prevents duplicate charges
|
|
48
|
+
internal_payment_id: text("internal_payment_id").notNull(),
|
|
49
|
+
idempotency_key: text("idempotency_key"),
|
|
50
|
+
// User association (FK configured at application level)
|
|
51
|
+
user_id: uuid("user_id").notNull(),
|
|
52
|
+
// Transaction classification
|
|
53
|
+
transaction_type: text("transaction_type").$type().notNull(),
|
|
54
|
+
status: text("status").$type().notNull().default("created"),
|
|
55
|
+
// Amounts in smallest currency unit (cents/agorot)
|
|
56
|
+
amount_minor: numeric("amount_minor").notNull(),
|
|
57
|
+
currency: text("currency").notNull().default("USD"),
|
|
58
|
+
// Original amount if currency converted
|
|
59
|
+
original_amount_minor: numeric("original_amount_minor"),
|
|
60
|
+
original_currency: text("original_currency"),
|
|
61
|
+
currency_conversion_rate: numeric("currency_conversion_rate"),
|
|
62
|
+
// Provider information
|
|
63
|
+
provider: text("provider").notNull(),
|
|
64
|
+
// stripe, hyp, cardcom
|
|
65
|
+
provider_transaction_id: text("provider_transaction_id"),
|
|
66
|
+
provider_authorization_code: text("provider_authorization_code"),
|
|
67
|
+
provider_metadata: jsonb("provider_metadata").$type(),
|
|
68
|
+
// Two-phase commit tracking (J5)
|
|
69
|
+
authorized_at: timestamp("authorized_at", { withTimezone: true }),
|
|
70
|
+
captured_at: timestamp("captured_at", { withTimezone: true }),
|
|
71
|
+
voided_at: timestamp("voided_at", { withTimezone: true }),
|
|
72
|
+
capture_deadline: timestamp("capture_deadline", { withTimezone: true }),
|
|
73
|
+
// Refund tracking
|
|
74
|
+
refunded_amount_minor: numeric("refunded_amount_minor").default("0"),
|
|
75
|
+
last_refund_at: timestamp("last_refund_at", { withTimezone: true }),
|
|
76
|
+
// Tax invoice (Israeli requirement)
|
|
77
|
+
tax_invoice_status: text("tax_invoice_status").$type().default("pending"),
|
|
78
|
+
tax_invoice_number: text("tax_invoice_number"),
|
|
79
|
+
tax_invoice_url: text("tax_invoice_url"),
|
|
80
|
+
// Error tracking
|
|
81
|
+
failure_code: text("failure_code"),
|
|
82
|
+
failure_message: text("failure_message"),
|
|
83
|
+
failure_details: jsonb("failure_details"),
|
|
84
|
+
// Application metadata
|
|
85
|
+
description: text("description"),
|
|
86
|
+
metadata: jsonb("metadata").$type(),
|
|
87
|
+
// Timestamps
|
|
88
|
+
created_at: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
89
|
+
updated_at: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
90
|
+
},
|
|
91
|
+
(table) => ({
|
|
92
|
+
// Performance indexes
|
|
93
|
+
userIdx: index("payment_transactions_user_idx").on(table.user_id),
|
|
94
|
+
statusIdx: index("payment_transactions_status_idx").on(table.status),
|
|
95
|
+
providerIdx: index("payment_transactions_provider_idx").on(table.provider),
|
|
96
|
+
providerTxIdx: index("payment_transactions_provider_tx_idx").on(table.provider_transaction_id),
|
|
97
|
+
createdAtIdx: index("payment_transactions_created_at_idx").on(table.created_at),
|
|
98
|
+
// Unique constraints for idempotency
|
|
99
|
+
internalIdUnique: unique("payment_transactions_internal_id_unique").on(
|
|
100
|
+
table.internal_payment_id
|
|
101
|
+
),
|
|
102
|
+
idempotencyUnique: unique("payment_transactions_idempotency_unique").on(table.idempotency_key)
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// src/schema/payment-methods.ts
|
|
107
|
+
import { pgTable as pgTable2, uuid as uuid2, text as text2, boolean, timestamp as timestamp2, jsonb as jsonb2, index as index2 } from "drizzle-orm/pg-core";
|
|
108
|
+
var paymentMethods = pgTable2(
|
|
109
|
+
"payment_methods",
|
|
110
|
+
{
|
|
111
|
+
id: uuid2("id").defaultRandom().primaryKey(),
|
|
112
|
+
// User association (FK configured at application level)
|
|
113
|
+
user_id: uuid2("user_id").notNull(),
|
|
114
|
+
// Method type
|
|
115
|
+
type: text2("type").$type().notNull(),
|
|
116
|
+
// Provider information
|
|
117
|
+
provider: text2("provider").notNull(),
|
|
118
|
+
// stripe, hyp, cardcom
|
|
119
|
+
provider_payment_method_id: text2("provider_payment_method_id").notNull(),
|
|
120
|
+
// Card details (tokenized, never full numbers)
|
|
121
|
+
card_brand: text2("card_brand").$type(),
|
|
122
|
+
card_last4: text2("card_last4"),
|
|
123
|
+
card_exp_month: text2("card_exp_month"),
|
|
124
|
+
card_exp_year: text2("card_exp_year"),
|
|
125
|
+
card_bin: text2("card_bin"),
|
|
126
|
+
// First 6-8 digits for routing
|
|
127
|
+
// State
|
|
128
|
+
is_default: boolean("is_default").default(false),
|
|
129
|
+
is_active: boolean("is_active").default(true),
|
|
130
|
+
// Provider-specific data
|
|
131
|
+
provider_metadata: jsonb2("provider_metadata").$type(),
|
|
132
|
+
// Timestamps
|
|
133
|
+
created_at: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
134
|
+
updated_at: timestamp2("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
135
|
+
last_used_at: timestamp2("last_used_at", { withTimezone: true })
|
|
136
|
+
},
|
|
137
|
+
(table) => ({
|
|
138
|
+
// Performance indexes
|
|
139
|
+
userIdx: index2("payment_methods_user_idx").on(table.user_id),
|
|
140
|
+
userDefaultIdx: index2("payment_methods_user_default_idx").on(table.user_id, table.is_default),
|
|
141
|
+
providerIdx: index2("payment_methods_provider_idx").on(table.provider),
|
|
142
|
+
providerMethodIdx: index2("payment_methods_provider_method_idx").on(
|
|
143
|
+
table.provider_payment_method_id
|
|
144
|
+
),
|
|
145
|
+
cardBinIdx: index2("payment_methods_card_bin_idx").on(table.card_bin)
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// src/schema/webhook-events.ts
|
|
150
|
+
import {
|
|
151
|
+
pgTable as pgTable3,
|
|
152
|
+
uuid as uuid3,
|
|
153
|
+
text as text3,
|
|
154
|
+
timestamp as timestamp3,
|
|
155
|
+
jsonb as jsonb3,
|
|
156
|
+
index as index3,
|
|
157
|
+
unique as unique2
|
|
158
|
+
} from "drizzle-orm/pg-core";
|
|
159
|
+
var paymentWebhookEvents = pgTable3(
|
|
160
|
+
"payment_webhook_events",
|
|
161
|
+
{
|
|
162
|
+
id: uuid3("id").defaultRandom().primaryKey(),
|
|
163
|
+
// Event identification
|
|
164
|
+
provider: text3("provider").notNull(),
|
|
165
|
+
// stripe, hyp, cardcom
|
|
166
|
+
provider_event_id: text3("provider_event_id").notNull(),
|
|
167
|
+
event_type: text3("event_type").notNull(),
|
|
168
|
+
// Processing state
|
|
169
|
+
status: text3("status").$type().notNull().default("pending"),
|
|
170
|
+
attempts: text3("attempts").default("0"),
|
|
171
|
+
last_attempt_at: timestamp3("last_attempt_at", { withTimezone: true }),
|
|
172
|
+
// Linked transaction (if applicable)
|
|
173
|
+
transaction_id: uuid3("transaction_id"),
|
|
174
|
+
// Event payload (store for debugging and retry)
|
|
175
|
+
payload: jsonb3("payload").$type().notNull(),
|
|
176
|
+
signature: text3("signature"),
|
|
177
|
+
// Error tracking
|
|
178
|
+
error_message: text3("error_message"),
|
|
179
|
+
error_details: jsonb3("error_details"),
|
|
180
|
+
// Timestamps
|
|
181
|
+
received_at: timestamp3("received_at", { withTimezone: true }).notNull().defaultNow(),
|
|
182
|
+
processed_at: timestamp3("processed_at", { withTimezone: true }),
|
|
183
|
+
created_at: timestamp3("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
184
|
+
},
|
|
185
|
+
(table) => ({
|
|
186
|
+
// Unique constraint for idempotency
|
|
187
|
+
providerEventUnique: unique2("webhook_events_provider_event_unique").on(
|
|
188
|
+
table.provider,
|
|
189
|
+
table.provider_event_id
|
|
190
|
+
),
|
|
191
|
+
// Performance indexes
|
|
192
|
+
statusIdx: index3("webhook_events_status_idx").on(table.status),
|
|
193
|
+
transactionIdx: index3("webhook_events_transaction_idx").on(
|
|
194
|
+
table.transaction_id
|
|
195
|
+
),
|
|
196
|
+
receivedAtIdx: index3("webhook_events_received_at_idx").on(table.received_at),
|
|
197
|
+
providerIdx: index3("webhook_events_provider_idx").on(table.provider),
|
|
198
|
+
eventTypeIdx: index3("webhook_events_event_type_idx").on(table.event_type)
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// src/schema/payment-audit-log.ts
|
|
203
|
+
import {
|
|
204
|
+
pgTable as pgTable4,
|
|
205
|
+
uuid as uuid4,
|
|
206
|
+
text as text4,
|
|
207
|
+
timestamp as timestamp4,
|
|
208
|
+
jsonb as jsonb4,
|
|
209
|
+
index as index4
|
|
210
|
+
} from "drizzle-orm/pg-core";
|
|
211
|
+
var paymentAuditLog = pgTable4(
|
|
212
|
+
"payment_audit_log",
|
|
213
|
+
{
|
|
214
|
+
id: uuid4("id").defaultRandom().primaryKey(),
|
|
215
|
+
// What transaction was affected
|
|
216
|
+
transaction_id: uuid4("transaction_id").notNull(),
|
|
217
|
+
// What action occurred
|
|
218
|
+
action: text4("action").$type().notNull(),
|
|
219
|
+
// State before and after
|
|
220
|
+
previous_state: jsonb4("previous_state"),
|
|
221
|
+
new_state: jsonb4("new_state").notNull(),
|
|
222
|
+
// Who/what triggered this change
|
|
223
|
+
triggered_by: text4("triggered_by").$type().notNull(),
|
|
224
|
+
triggered_by_id: uuid4("triggered_by_id"),
|
|
225
|
+
// User ID if applicable
|
|
226
|
+
// Request context
|
|
227
|
+
ip_address: text4("ip_address"),
|
|
228
|
+
user_agent: text4("user_agent"),
|
|
229
|
+
// Correlation for distributed tracing
|
|
230
|
+
correlation_id: text4("correlation_id"),
|
|
231
|
+
// Additional context
|
|
232
|
+
metadata: jsonb4("metadata").$type(),
|
|
233
|
+
// Immutable timestamp (never updated)
|
|
234
|
+
created_at: timestamp4("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
235
|
+
},
|
|
236
|
+
(table) => ({
|
|
237
|
+
// Performance indexes
|
|
238
|
+
transactionIdx: index4("payment_audit_log_transaction_idx").on(
|
|
239
|
+
table.transaction_id
|
|
240
|
+
),
|
|
241
|
+
actionIdx: index4("payment_audit_log_action_idx").on(table.action),
|
|
242
|
+
correlationIdx: index4("payment_audit_log_correlation_idx").on(
|
|
243
|
+
table.correlation_id
|
|
244
|
+
),
|
|
245
|
+
createdAtIdx: index4("payment_audit_log_created_at_idx").on(table.created_at),
|
|
246
|
+
triggeredByIdx: index4("payment_audit_log_triggered_by_idx").on(
|
|
247
|
+
table.triggered_by
|
|
248
|
+
)
|
|
249
|
+
})
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// src/schema/provider-health.ts
|
|
253
|
+
import {
|
|
254
|
+
pgTable as pgTable5,
|
|
255
|
+
uuid as uuid5,
|
|
256
|
+
text as text5,
|
|
257
|
+
numeric as numeric2,
|
|
258
|
+
timestamp as timestamp5,
|
|
259
|
+
jsonb as jsonb5,
|
|
260
|
+
index as index5,
|
|
261
|
+
unique as unique3
|
|
262
|
+
} from "drizzle-orm/pg-core";
|
|
263
|
+
var providerHealth = pgTable5(
|
|
264
|
+
"payment_provider_health",
|
|
265
|
+
{
|
|
266
|
+
id: uuid5("id").defaultRandom().primaryKey(),
|
|
267
|
+
// Provider identification
|
|
268
|
+
provider: text5("provider").notNull(),
|
|
269
|
+
// stripe, hyp, cardcom
|
|
270
|
+
// Circuit breaker state
|
|
271
|
+
circuit_state: text5("circuit_state").$type().notNull().default("closed"),
|
|
272
|
+
// Failure tracking
|
|
273
|
+
failure_count: text5("failure_count").default("0"),
|
|
274
|
+
success_count: text5("success_count").default("0"),
|
|
275
|
+
last_failure_at: timestamp5("last_failure_at", { withTimezone: true }),
|
|
276
|
+
last_success_at: timestamp5("last_success_at", { withTimezone: true }),
|
|
277
|
+
// Circuit breaker timing
|
|
278
|
+
circuit_opened_at: timestamp5("circuit_opened_at", { withTimezone: true }),
|
|
279
|
+
next_retry_at: timestamp5("next_retry_at", { withTimezone: true }),
|
|
280
|
+
// Performance metrics
|
|
281
|
+
avg_latency_ms: numeric2("avg_latency_ms"),
|
|
282
|
+
error_rate: numeric2("error_rate"),
|
|
283
|
+
// 0-1
|
|
284
|
+
request_count_window: text5("request_count_window").default("0"),
|
|
285
|
+
// Health check results
|
|
286
|
+
last_health_check_at: timestamp5("last_health_check_at", { withTimezone: true }),
|
|
287
|
+
health_check_result: jsonb5("health_check_result").$type(),
|
|
288
|
+
// Timestamps
|
|
289
|
+
created_at: timestamp5("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
290
|
+
updated_at: timestamp5("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
291
|
+
},
|
|
292
|
+
(table) => ({
|
|
293
|
+
// One record per provider
|
|
294
|
+
providerUnique: unique3("provider_health_provider_unique").on(table.provider),
|
|
295
|
+
// Performance indexes
|
|
296
|
+
circuitStateIdx: index5("provider_health_circuit_state_idx").on(
|
|
297
|
+
table.circuit_state
|
|
298
|
+
),
|
|
299
|
+
nextRetryIdx: index5("provider_health_next_retry_idx").on(table.next_retry_at)
|
|
300
|
+
})
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// src/repositories/transaction.drizzle-repository.ts
|
|
304
|
+
function mapToTransaction(row) {
|
|
305
|
+
return {
|
|
306
|
+
id: row.id,
|
|
307
|
+
internalPaymentId: row.internal_payment_id,
|
|
308
|
+
idempotencyKey: row.idempotency_key,
|
|
309
|
+
userId: row.user_id,
|
|
310
|
+
transactionType: row.transaction_type,
|
|
311
|
+
status: row.status,
|
|
312
|
+
amountMinor: parseNumeric(row.amount_minor),
|
|
313
|
+
currency: row.currency,
|
|
314
|
+
originalAmountMinor: row.original_amount_minor ? parseNumeric(row.original_amount_minor) : null,
|
|
315
|
+
originalCurrency: row.original_currency,
|
|
316
|
+
currencyConversionRate: row.currency_conversion_rate ? parseNumeric(row.currency_conversion_rate) : null,
|
|
317
|
+
provider: row.provider,
|
|
318
|
+
providerTransactionId: row.provider_transaction_id,
|
|
319
|
+
providerAuthorizationCode: row.provider_authorization_code,
|
|
320
|
+
providerMetadata: row.provider_metadata,
|
|
321
|
+
authorizedAt: row.authorized_at,
|
|
322
|
+
capturedAt: row.captured_at,
|
|
323
|
+
voidedAt: row.voided_at,
|
|
324
|
+
captureDeadline: row.capture_deadline,
|
|
325
|
+
refundedAmountMinor: parseNumeric(row.refunded_amount_minor),
|
|
326
|
+
lastRefundAt: row.last_refund_at,
|
|
327
|
+
taxInvoiceStatus: row.tax_invoice_status ?? "pending",
|
|
328
|
+
taxInvoiceNumber: row.tax_invoice_number,
|
|
329
|
+
taxInvoiceUrl: row.tax_invoice_url,
|
|
330
|
+
failureCode: row.failure_code,
|
|
331
|
+
failureMessage: row.failure_message,
|
|
332
|
+
failureDetails: row.failure_details,
|
|
333
|
+
description: row.description,
|
|
334
|
+
metadata: row.metadata,
|
|
335
|
+
createdAt: row.created_at,
|
|
336
|
+
updatedAt: row.updated_at
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
var DrizzleTransactionRepository = class {
|
|
340
|
+
constructor(db) {
|
|
341
|
+
this.db = db;
|
|
342
|
+
}
|
|
343
|
+
async findById(id) {
|
|
344
|
+
const result = await this.db.select().from(paymentTransactions).where(eq(paymentTransactions.id, id)).limit(1);
|
|
345
|
+
return result[0] ? mapToTransaction(result[0]) : null;
|
|
346
|
+
}
|
|
347
|
+
async create(data) {
|
|
348
|
+
const result = await this.db.insert(paymentTransactions).values({
|
|
349
|
+
internal_payment_id: data.internalPaymentId,
|
|
350
|
+
idempotency_key: data.idempotencyKey,
|
|
351
|
+
user_id: data.userId,
|
|
352
|
+
transaction_type: data.transactionType,
|
|
353
|
+
status: data.status ?? "created",
|
|
354
|
+
amount_minor: String(data.amountMinor),
|
|
355
|
+
currency: data.currency,
|
|
356
|
+
provider: data.provider,
|
|
357
|
+
description: data.description,
|
|
358
|
+
metadata: data.metadata
|
|
359
|
+
}).returning();
|
|
360
|
+
return mapToTransaction(result[0]);
|
|
361
|
+
}
|
|
362
|
+
async update(id, data) {
|
|
363
|
+
const updateData = {
|
|
364
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
365
|
+
};
|
|
366
|
+
if (data.status !== void 0) updateData.status = data.status;
|
|
367
|
+
if (data.providerTransactionId !== void 0)
|
|
368
|
+
updateData.provider_transaction_id = data.providerTransactionId;
|
|
369
|
+
if (data.providerAuthorizationCode !== void 0)
|
|
370
|
+
updateData.provider_authorization_code = data.providerAuthorizationCode;
|
|
371
|
+
if (data.providerMetadata !== void 0) updateData.provider_metadata = data.providerMetadata;
|
|
372
|
+
if (data.authorizedAt !== void 0) updateData.authorized_at = data.authorizedAt;
|
|
373
|
+
if (data.capturedAt !== void 0) updateData.captured_at = data.capturedAt;
|
|
374
|
+
if (data.voidedAt !== void 0) updateData.voided_at = data.voidedAt;
|
|
375
|
+
if (data.captureDeadline !== void 0) updateData.capture_deadline = data.captureDeadline;
|
|
376
|
+
if (data.refundedAmountMinor !== void 0)
|
|
377
|
+
updateData.refunded_amount_minor = String(data.refundedAmountMinor);
|
|
378
|
+
if (data.lastRefundAt !== void 0) updateData.last_refund_at = data.lastRefundAt;
|
|
379
|
+
if (data.taxInvoiceStatus !== void 0) updateData.tax_invoice_status = data.taxInvoiceStatus;
|
|
380
|
+
if (data.taxInvoiceNumber !== void 0) updateData.tax_invoice_number = data.taxInvoiceNumber;
|
|
381
|
+
if (data.taxInvoiceUrl !== void 0) updateData.tax_invoice_url = data.taxInvoiceUrl;
|
|
382
|
+
if (data.failureCode !== void 0) updateData.failure_code = data.failureCode;
|
|
383
|
+
if (data.failureMessage !== void 0) updateData.failure_message = data.failureMessage;
|
|
384
|
+
if (data.failureDetails !== void 0) updateData.failure_details = data.failureDetails;
|
|
385
|
+
const result = await this.db.update(paymentTransactions).set(updateData).where(eq(paymentTransactions.id, id)).returning();
|
|
386
|
+
return result[0] ? mapToTransaction(result[0]) : null;
|
|
387
|
+
}
|
|
388
|
+
async delete(id) {
|
|
389
|
+
const result = await this.db.delete(paymentTransactions).where(eq(paymentTransactions.id, id)).returning({ id: paymentTransactions.id });
|
|
390
|
+
return result.length > 0;
|
|
391
|
+
}
|
|
392
|
+
async findByInternalPaymentId(internalPaymentId) {
|
|
393
|
+
const result = await this.db.select().from(paymentTransactions).where(eq(paymentTransactions.internal_payment_id, internalPaymentId)).limit(1);
|
|
394
|
+
return result[0] ? mapToTransaction(result[0]) : null;
|
|
395
|
+
}
|
|
396
|
+
async findByIdempotencyKey(idempotencyKey) {
|
|
397
|
+
const result = await this.db.select().from(paymentTransactions).where(eq(paymentTransactions.idempotency_key, idempotencyKey)).limit(1);
|
|
398
|
+
return result[0] ? mapToTransaction(result[0]) : null;
|
|
399
|
+
}
|
|
400
|
+
async findByProviderTransactionId(provider, providerTransactionId) {
|
|
401
|
+
const result = await this.db.select().from(paymentTransactions).where(
|
|
402
|
+
and(
|
|
403
|
+
eq(paymentTransactions.provider, provider),
|
|
404
|
+
eq(paymentTransactions.provider_transaction_id, providerTransactionId)
|
|
405
|
+
)
|
|
406
|
+
).limit(1);
|
|
407
|
+
return result[0] ? mapToTransaction(result[0]) : null;
|
|
408
|
+
}
|
|
409
|
+
async findMany(filter, pagination = {}) {
|
|
410
|
+
const { limit = 20, offset = 0 } = pagination;
|
|
411
|
+
const conditions = this.buildFilterConditions(filter);
|
|
412
|
+
const [rows, [countResult]] = await Promise.all([
|
|
413
|
+
this.db.select().from(paymentTransactions).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(sql`${paymentTransactions.created_at} DESC`).limit(limit).offset(offset),
|
|
414
|
+
this.db.select({ total: count() }).from(paymentTransactions).where(conditions.length > 0 ? and(...conditions) : void 0)
|
|
415
|
+
]);
|
|
416
|
+
const transactions = rows.map(mapToTransaction);
|
|
417
|
+
return applyPagination(transactions, countResult?.total ?? 0, limit, offset);
|
|
418
|
+
}
|
|
419
|
+
async findByUserId(userId, pagination = {}) {
|
|
420
|
+
return this.findMany({ userId }, pagination);
|
|
421
|
+
}
|
|
422
|
+
async updateStatus(id, status, additionalData = {}) {
|
|
423
|
+
return this.update(id, { ...additionalData, status });
|
|
424
|
+
}
|
|
425
|
+
async incrementRefundedAmount(id, amountMinor) {
|
|
426
|
+
const result = await this.db.update(paymentTransactions).set({
|
|
427
|
+
refunded_amount_minor: sql`COALESCE(${paymentTransactions.refunded_amount_minor}, '0')::numeric + ${amountMinor}`,
|
|
428
|
+
last_refund_at: /* @__PURE__ */ new Date(),
|
|
429
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
430
|
+
}).where(eq(paymentTransactions.id, id)).returning();
|
|
431
|
+
return result[0] ? mapToTransaction(result[0]) : null;
|
|
432
|
+
}
|
|
433
|
+
async findExpiredAuthorizations(beforeDate) {
|
|
434
|
+
const result = await this.db.select().from(paymentTransactions).where(
|
|
435
|
+
and(
|
|
436
|
+
eq(paymentTransactions.status, "authorized"),
|
|
437
|
+
lte(paymentTransactions.capture_deadline, beforeDate)
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
return result.map(mapToTransaction);
|
|
441
|
+
}
|
|
442
|
+
async countByStatus(filter = {}) {
|
|
443
|
+
const conditions = this.buildFilterConditions(filter);
|
|
444
|
+
const result = await this.db.select({
|
|
445
|
+
status: paymentTransactions.status,
|
|
446
|
+
count: count()
|
|
447
|
+
}).from(paymentTransactions).where(conditions.length > 0 ? and(...conditions) : void 0).groupBy(paymentTransactions.status);
|
|
448
|
+
const counts = {
|
|
449
|
+
created: 0,
|
|
450
|
+
pending_authorization: 0,
|
|
451
|
+
authorized: 0,
|
|
452
|
+
capturing: 0,
|
|
453
|
+
captured: 0,
|
|
454
|
+
voided: 0,
|
|
455
|
+
failed: 0,
|
|
456
|
+
expired: 0,
|
|
457
|
+
partially_refunded: 0,
|
|
458
|
+
fully_refunded: 0
|
|
459
|
+
};
|
|
460
|
+
for (const row of result) {
|
|
461
|
+
counts[row.status] = row.count;
|
|
462
|
+
}
|
|
463
|
+
return counts;
|
|
464
|
+
}
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// Private Helpers
|
|
467
|
+
// ============================================================================
|
|
468
|
+
buildFilterConditions(filter) {
|
|
469
|
+
const conditions = [];
|
|
470
|
+
if (filter.userId) {
|
|
471
|
+
conditions.push(eq(paymentTransactions.user_id, filter.userId));
|
|
472
|
+
}
|
|
473
|
+
const statuses = normalizeArrayFilter(filter.status);
|
|
474
|
+
if (statuses && statuses.length > 0) {
|
|
475
|
+
if (statuses.length === 1) {
|
|
476
|
+
conditions.push(eq(paymentTransactions.status, statuses[0]));
|
|
477
|
+
} else {
|
|
478
|
+
conditions.push(inArray(paymentTransactions.status, statuses));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const providers = normalizeArrayFilter(filter.provider);
|
|
482
|
+
if (providers && providers.length > 0) {
|
|
483
|
+
if (providers.length === 1) {
|
|
484
|
+
conditions.push(eq(paymentTransactions.provider, providers[0]));
|
|
485
|
+
} else {
|
|
486
|
+
conditions.push(inArray(paymentTransactions.provider, providers));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const transactionTypes = normalizeArrayFilter(filter.transactionType);
|
|
490
|
+
if (transactionTypes && transactionTypes.length > 0) {
|
|
491
|
+
if (transactionTypes.length === 1) {
|
|
492
|
+
conditions.push(eq(paymentTransactions.transaction_type, transactionTypes[0]));
|
|
493
|
+
} else {
|
|
494
|
+
conditions.push(inArray(paymentTransactions.transaction_type, transactionTypes));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (filter.dateRange?.from) {
|
|
498
|
+
conditions.push(gte(paymentTransactions.created_at, filter.dateRange.from));
|
|
499
|
+
}
|
|
500
|
+
if (filter.dateRange?.to) {
|
|
501
|
+
conditions.push(lte(paymentTransactions.created_at, filter.dateRange.to));
|
|
502
|
+
}
|
|
503
|
+
return conditions;
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// src/repositories/payment-method.drizzle-repository.ts
|
|
508
|
+
import { eq as eq2, and as and2, inArray as inArray2, sql as sql2, count as count2 } from "drizzle-orm";
|
|
509
|
+
function mapToPaymentMethod(row) {
|
|
510
|
+
return {
|
|
511
|
+
id: row.id,
|
|
512
|
+
userId: row.user_id,
|
|
513
|
+
type: row.type,
|
|
514
|
+
provider: row.provider,
|
|
515
|
+
providerPaymentMethodId: row.provider_payment_method_id,
|
|
516
|
+
cardBrand: row.card_brand,
|
|
517
|
+
cardLast4: row.card_last4,
|
|
518
|
+
cardExpMonth: row.card_exp_month,
|
|
519
|
+
cardExpYear: row.card_exp_year,
|
|
520
|
+
cardBin: row.card_bin,
|
|
521
|
+
isDefault: row.is_default ?? false,
|
|
522
|
+
isActive: row.is_active ?? true,
|
|
523
|
+
providerMetadata: row.provider_metadata,
|
|
524
|
+
createdAt: row.created_at,
|
|
525
|
+
updatedAt: row.updated_at,
|
|
526
|
+
lastUsedAt: row.last_used_at
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
var DrizzlePaymentMethodRepository = class {
|
|
530
|
+
constructor(db) {
|
|
531
|
+
this.db = db;
|
|
532
|
+
}
|
|
533
|
+
async findById(id) {
|
|
534
|
+
const result = await this.db.select().from(paymentMethods).where(eq2(paymentMethods.id, id)).limit(1);
|
|
535
|
+
return result[0] ? mapToPaymentMethod(result[0]) : null;
|
|
536
|
+
}
|
|
537
|
+
async create(data) {
|
|
538
|
+
const result = await this.db.insert(paymentMethods).values({
|
|
539
|
+
user_id: data.userId,
|
|
540
|
+
type: data.type,
|
|
541
|
+
provider: data.provider,
|
|
542
|
+
provider_payment_method_id: data.providerPaymentMethodId,
|
|
543
|
+
card_brand: data.cardBrand,
|
|
544
|
+
card_last4: data.cardLast4,
|
|
545
|
+
card_exp_month: data.cardExpMonth,
|
|
546
|
+
card_exp_year: data.cardExpYear,
|
|
547
|
+
card_bin: data.cardBin,
|
|
548
|
+
is_default: data.isDefault ?? false,
|
|
549
|
+
provider_metadata: data.providerMetadata
|
|
550
|
+
}).returning();
|
|
551
|
+
return mapToPaymentMethod(result[0]);
|
|
552
|
+
}
|
|
553
|
+
async update(id, data) {
|
|
554
|
+
const updateData = {
|
|
555
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
556
|
+
};
|
|
557
|
+
if (data.isDefault !== void 0) updateData.is_default = data.isDefault;
|
|
558
|
+
if (data.isActive !== void 0) updateData.is_active = data.isActive;
|
|
559
|
+
if (data.cardExpMonth !== void 0) updateData.card_exp_month = data.cardExpMonth;
|
|
560
|
+
if (data.cardExpYear !== void 0) updateData.card_exp_year = data.cardExpYear;
|
|
561
|
+
if (data.lastUsedAt !== void 0) updateData.last_used_at = data.lastUsedAt;
|
|
562
|
+
if (data.providerMetadata !== void 0) updateData.provider_metadata = data.providerMetadata;
|
|
563
|
+
const result = await this.db.update(paymentMethods).set(updateData).where(eq2(paymentMethods.id, id)).returning();
|
|
564
|
+
return result[0] ? mapToPaymentMethod(result[0]) : null;
|
|
565
|
+
}
|
|
566
|
+
async delete(id) {
|
|
567
|
+
const result = await this.db.delete(paymentMethods).where(eq2(paymentMethods.id, id)).returning({ id: paymentMethods.id });
|
|
568
|
+
return result.length > 0;
|
|
569
|
+
}
|
|
570
|
+
async findByProviderPaymentMethodId(provider, providerPaymentMethodId) {
|
|
571
|
+
const result = await this.db.select().from(paymentMethods).where(
|
|
572
|
+
and2(
|
|
573
|
+
eq2(paymentMethods.provider, provider),
|
|
574
|
+
eq2(paymentMethods.provider_payment_method_id, providerPaymentMethodId)
|
|
575
|
+
)
|
|
576
|
+
).limit(1);
|
|
577
|
+
return result[0] ? mapToPaymentMethod(result[0]) : null;
|
|
578
|
+
}
|
|
579
|
+
async findByUserId(userId, filter = {}) {
|
|
580
|
+
const conditions = [eq2(paymentMethods.user_id, userId)];
|
|
581
|
+
if (filter.isActive !== void 0) {
|
|
582
|
+
conditions.push(eq2(paymentMethods.is_active, filter.isActive));
|
|
583
|
+
}
|
|
584
|
+
if (filter.isDefault !== void 0) {
|
|
585
|
+
conditions.push(eq2(paymentMethods.is_default, filter.isDefault));
|
|
586
|
+
}
|
|
587
|
+
const providers = normalizeArrayFilter(filter.provider);
|
|
588
|
+
if (providers && providers.length > 0) {
|
|
589
|
+
conditions.push(inArray2(paymentMethods.provider, providers));
|
|
590
|
+
}
|
|
591
|
+
const result = await this.db.select().from(paymentMethods).where(and2(...conditions)).orderBy(sql2`${paymentMethods.created_at} DESC`);
|
|
592
|
+
return result.map(mapToPaymentMethod);
|
|
593
|
+
}
|
|
594
|
+
async findDefaultForUser(userId) {
|
|
595
|
+
const result = await this.db.select().from(paymentMethods).where(
|
|
596
|
+
and2(
|
|
597
|
+
eq2(paymentMethods.user_id, userId),
|
|
598
|
+
eq2(paymentMethods.is_default, true),
|
|
599
|
+
eq2(paymentMethods.is_active, true)
|
|
600
|
+
)
|
|
601
|
+
).limit(1);
|
|
602
|
+
return result[0] ? mapToPaymentMethod(result[0]) : null;
|
|
603
|
+
}
|
|
604
|
+
async findMany(filter, pagination = {}) {
|
|
605
|
+
const { limit = 20, offset = 0 } = pagination;
|
|
606
|
+
const conditions = this.buildFilterConditions(filter);
|
|
607
|
+
const [rows, [countResult]] = await Promise.all([
|
|
608
|
+
this.db.select().from(paymentMethods).where(conditions.length > 0 ? and2(...conditions) : void 0).orderBy(sql2`${paymentMethods.created_at} DESC`).limit(limit).offset(offset),
|
|
609
|
+
this.db.select({ total: count2() }).from(paymentMethods).where(conditions.length > 0 ? and2(...conditions) : void 0)
|
|
610
|
+
]);
|
|
611
|
+
const methods = rows.map(mapToPaymentMethod);
|
|
612
|
+
return applyPagination(methods, countResult?.total ?? 0, limit, offset);
|
|
613
|
+
}
|
|
614
|
+
async setAsDefault(id, userId) {
|
|
615
|
+
await this.db.update(paymentMethods).set({ is_default: false, updated_at: /* @__PURE__ */ new Date() }).where(and2(eq2(paymentMethods.user_id, userId), eq2(paymentMethods.is_default, true)));
|
|
616
|
+
return this.update(id, { isDefault: true });
|
|
617
|
+
}
|
|
618
|
+
async deactivate(id) {
|
|
619
|
+
const result = await this.update(id, { isActive: false });
|
|
620
|
+
return result !== null;
|
|
621
|
+
}
|
|
622
|
+
async markAsUsed(id) {
|
|
623
|
+
return this.update(id, { lastUsedAt: /* @__PURE__ */ new Date() });
|
|
624
|
+
}
|
|
625
|
+
async findByCardBin(userId, cardBin) {
|
|
626
|
+
const result = await this.db.select().from(paymentMethods).where(
|
|
627
|
+
and2(
|
|
628
|
+
eq2(paymentMethods.user_id, userId),
|
|
629
|
+
eq2(paymentMethods.card_bin, cardBin),
|
|
630
|
+
eq2(paymentMethods.is_active, true)
|
|
631
|
+
)
|
|
632
|
+
);
|
|
633
|
+
return result.map(mapToPaymentMethod);
|
|
634
|
+
}
|
|
635
|
+
async countActiveForUser(userId) {
|
|
636
|
+
const result = await this.db.select({ count: count2() }).from(paymentMethods).where(and2(eq2(paymentMethods.user_id, userId), eq2(paymentMethods.is_active, true)));
|
|
637
|
+
return result[0]?.count ?? 0;
|
|
638
|
+
}
|
|
639
|
+
// ============================================================================
|
|
640
|
+
// Private Helpers
|
|
641
|
+
// ============================================================================
|
|
642
|
+
buildFilterConditions(filter) {
|
|
643
|
+
const conditions = [];
|
|
644
|
+
if (filter.userId) {
|
|
645
|
+
conditions.push(eq2(paymentMethods.user_id, filter.userId));
|
|
646
|
+
}
|
|
647
|
+
if (filter.isDefault !== void 0) {
|
|
648
|
+
conditions.push(eq2(paymentMethods.is_default, filter.isDefault));
|
|
649
|
+
}
|
|
650
|
+
if (filter.isActive !== void 0) {
|
|
651
|
+
conditions.push(eq2(paymentMethods.is_active, filter.isActive));
|
|
652
|
+
}
|
|
653
|
+
if (filter.cardBin) {
|
|
654
|
+
conditions.push(eq2(paymentMethods.card_bin, filter.cardBin));
|
|
655
|
+
}
|
|
656
|
+
const providers = normalizeArrayFilter(filter.provider);
|
|
657
|
+
if (providers && providers.length > 0) {
|
|
658
|
+
conditions.push(inArray2(paymentMethods.provider, providers));
|
|
659
|
+
}
|
|
660
|
+
const types = normalizeArrayFilter(filter.type);
|
|
661
|
+
if (types && types.length > 0) {
|
|
662
|
+
conditions.push(inArray2(paymentMethods.type, types));
|
|
663
|
+
}
|
|
664
|
+
return conditions;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// src/repositories/webhook-event.drizzle-repository.ts
|
|
669
|
+
import { eq as eq3, and as and3, gte as gte2, lte as lte2, inArray as inArray3, sql as sql3, count as count3, lt } from "drizzle-orm";
|
|
670
|
+
function mapToWebhookEvent(row) {
|
|
671
|
+
return {
|
|
672
|
+
id: row.id,
|
|
673
|
+
provider: row.provider,
|
|
674
|
+
providerEventId: row.provider_event_id,
|
|
675
|
+
eventType: row.event_type,
|
|
676
|
+
status: row.status,
|
|
677
|
+
attempts: parseInt(row.attempts ?? "0", 10),
|
|
678
|
+
lastAttemptAt: row.last_attempt_at,
|
|
679
|
+
transactionId: row.transaction_id,
|
|
680
|
+
payload: row.payload,
|
|
681
|
+
signature: row.signature,
|
|
682
|
+
errorMessage: row.error_message,
|
|
683
|
+
errorDetails: row.error_details,
|
|
684
|
+
receivedAt: row.received_at,
|
|
685
|
+
processedAt: row.processed_at,
|
|
686
|
+
createdAt: row.created_at
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
var DrizzleWebhookEventRepository = class {
|
|
690
|
+
constructor(db) {
|
|
691
|
+
this.db = db;
|
|
692
|
+
}
|
|
693
|
+
async findById(id) {
|
|
694
|
+
const result = await this.db.select().from(paymentWebhookEvents).where(eq3(paymentWebhookEvents.id, id)).limit(1);
|
|
695
|
+
return result[0] ? mapToWebhookEvent(result[0]) : null;
|
|
696
|
+
}
|
|
697
|
+
async create(data) {
|
|
698
|
+
const result = await this.db.insert(paymentWebhookEvents).values({
|
|
699
|
+
provider: data.provider,
|
|
700
|
+
provider_event_id: data.providerEventId,
|
|
701
|
+
event_type: data.eventType,
|
|
702
|
+
payload: data.payload,
|
|
703
|
+
signature: data.signature,
|
|
704
|
+
status: "pending"
|
|
705
|
+
}).returning();
|
|
706
|
+
return mapToWebhookEvent(result[0]);
|
|
707
|
+
}
|
|
708
|
+
async update(id, data) {
|
|
709
|
+
const updateData = {};
|
|
710
|
+
if (data.status !== void 0) updateData.status = data.status;
|
|
711
|
+
if (data.attempts !== void 0) updateData.attempts = String(data.attempts);
|
|
712
|
+
if (data.lastAttemptAt !== void 0) updateData.last_attempt_at = data.lastAttemptAt;
|
|
713
|
+
if (data.transactionId !== void 0) updateData.transaction_id = data.transactionId;
|
|
714
|
+
if (data.processedAt !== void 0) updateData.processed_at = data.processedAt;
|
|
715
|
+
if (data.errorMessage !== void 0) updateData.error_message = data.errorMessage;
|
|
716
|
+
if (data.errorDetails !== void 0) updateData.error_details = data.errorDetails;
|
|
717
|
+
const result = await this.db.update(paymentWebhookEvents).set(updateData).where(eq3(paymentWebhookEvents.id, id)).returning();
|
|
718
|
+
return result[0] ? mapToWebhookEvent(result[0]) : null;
|
|
719
|
+
}
|
|
720
|
+
async delete(id) {
|
|
721
|
+
const result = await this.db.delete(paymentWebhookEvents).where(eq3(paymentWebhookEvents.id, id)).returning({ id: paymentWebhookEvents.id });
|
|
722
|
+
return result.length > 0;
|
|
723
|
+
}
|
|
724
|
+
async findByProviderEventId(provider, providerEventId) {
|
|
725
|
+
const result = await this.db.select().from(paymentWebhookEvents).where(
|
|
726
|
+
and3(
|
|
727
|
+
eq3(paymentWebhookEvents.provider, provider),
|
|
728
|
+
eq3(paymentWebhookEvents.provider_event_id, providerEventId)
|
|
729
|
+
)
|
|
730
|
+
).limit(1);
|
|
731
|
+
return result[0] ? mapToWebhookEvent(result[0]) : null;
|
|
732
|
+
}
|
|
733
|
+
async findByTransactionId(transactionId) {
|
|
734
|
+
const result = await this.db.select().from(paymentWebhookEvents).where(eq3(paymentWebhookEvents.transaction_id, transactionId)).orderBy(sql3`${paymentWebhookEvents.received_at} DESC`);
|
|
735
|
+
return result.map(mapToWebhookEvent);
|
|
736
|
+
}
|
|
737
|
+
async findMany(filter, pagination = {}) {
|
|
738
|
+
const { limit = 20, offset = 0 } = pagination;
|
|
739
|
+
const conditions = this.buildFilterConditions(filter);
|
|
740
|
+
const [rows, [countResult]] = await Promise.all([
|
|
741
|
+
this.db.select().from(paymentWebhookEvents).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(sql3`${paymentWebhookEvents.received_at} DESC`).limit(limit).offset(offset),
|
|
742
|
+
this.db.select({ total: count3() }).from(paymentWebhookEvents).where(conditions.length > 0 ? and3(...conditions) : void 0)
|
|
743
|
+
]);
|
|
744
|
+
const events = rows.map(mapToWebhookEvent);
|
|
745
|
+
return applyPagination(events, countResult?.total ?? 0, limit, offset);
|
|
746
|
+
}
|
|
747
|
+
async findFailedForRetry(maxAttempts, olderThan) {
|
|
748
|
+
const conditions = [
|
|
749
|
+
eq3(paymentWebhookEvents.status, "failed"),
|
|
750
|
+
lt(sql3`CAST(${paymentWebhookEvents.attempts} AS INTEGER)`, maxAttempts)
|
|
751
|
+
];
|
|
752
|
+
if (olderThan) {
|
|
753
|
+
conditions.push(lt(paymentWebhookEvents.last_attempt_at, olderThan));
|
|
754
|
+
}
|
|
755
|
+
const result = await this.db.select().from(paymentWebhookEvents).where(and3(...conditions)).orderBy(sql3`${paymentWebhookEvents.last_attempt_at} ASC`).limit(100);
|
|
756
|
+
return result.map(mapToWebhookEvent);
|
|
757
|
+
}
|
|
758
|
+
async findPending(limit = 100) {
|
|
759
|
+
const result = await this.db.select().from(paymentWebhookEvents).where(eq3(paymentWebhookEvents.status, "pending")).orderBy(sql3`${paymentWebhookEvents.received_at} ASC`).limit(limit);
|
|
760
|
+
return result.map(mapToWebhookEvent);
|
|
761
|
+
}
|
|
762
|
+
async markAsProcessing(id) {
|
|
763
|
+
const result = await this.db.update(paymentWebhookEvents).set({
|
|
764
|
+
status: "processing",
|
|
765
|
+
last_attempt_at: /* @__PURE__ */ new Date(),
|
|
766
|
+
attempts: sql3`CAST(${paymentWebhookEvents.attempts} AS INTEGER) + 1`
|
|
767
|
+
}).where(and3(eq3(paymentWebhookEvents.id, id), eq3(paymentWebhookEvents.status, "pending"))).returning({ id: paymentWebhookEvents.id });
|
|
768
|
+
return result.length > 0;
|
|
769
|
+
}
|
|
770
|
+
async markAsProcessed(id, transactionId) {
|
|
771
|
+
return this.update(id, {
|
|
772
|
+
status: "processed",
|
|
773
|
+
processedAt: /* @__PURE__ */ new Date(),
|
|
774
|
+
transactionId
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
async markAsFailed(id, errorMessage, errorDetails) {
|
|
778
|
+
return this.update(id, {
|
|
779
|
+
status: "failed",
|
|
780
|
+
errorMessage,
|
|
781
|
+
errorDetails
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
async incrementAttempts(id) {
|
|
785
|
+
const result = await this.db.update(paymentWebhookEvents).set({
|
|
786
|
+
attempts: sql3`CAST(${paymentWebhookEvents.attempts} AS INTEGER) + 1`,
|
|
787
|
+
last_attempt_at: /* @__PURE__ */ new Date()
|
|
788
|
+
}).where(eq3(paymentWebhookEvents.id, id)).returning();
|
|
789
|
+
return result[0] ? mapToWebhookEvent(result[0]) : null;
|
|
790
|
+
}
|
|
791
|
+
async isAlreadyProcessed(provider, providerEventId) {
|
|
792
|
+
const result = await this.db.select({ status: paymentWebhookEvents.status }).from(paymentWebhookEvents).where(
|
|
793
|
+
and3(
|
|
794
|
+
eq3(paymentWebhookEvents.provider, provider),
|
|
795
|
+
eq3(paymentWebhookEvents.provider_event_id, providerEventId),
|
|
796
|
+
eq3(paymentWebhookEvents.status, "processed")
|
|
797
|
+
)
|
|
798
|
+
).limit(1);
|
|
799
|
+
return result.length > 0;
|
|
800
|
+
}
|
|
801
|
+
async countByStatus(filter = {}) {
|
|
802
|
+
const conditions = this.buildFilterConditions(filter);
|
|
803
|
+
const result = await this.db.select({
|
|
804
|
+
status: paymentWebhookEvents.status,
|
|
805
|
+
count: count3()
|
|
806
|
+
}).from(paymentWebhookEvents).where(conditions.length > 0 ? and3(...conditions) : void 0).groupBy(paymentWebhookEvents.status);
|
|
807
|
+
const counts = {
|
|
808
|
+
pending: 0,
|
|
809
|
+
processing: 0,
|
|
810
|
+
processed: 0,
|
|
811
|
+
failed: 0,
|
|
812
|
+
ignored: 0
|
|
813
|
+
};
|
|
814
|
+
for (const row of result) {
|
|
815
|
+
counts[row.status] = row.count;
|
|
816
|
+
}
|
|
817
|
+
return counts;
|
|
818
|
+
}
|
|
819
|
+
// ============================================================================
|
|
820
|
+
// Private Helpers
|
|
821
|
+
// ============================================================================
|
|
822
|
+
buildFilterConditions(filter) {
|
|
823
|
+
const conditions = [];
|
|
824
|
+
const providers = normalizeArrayFilter(filter.provider);
|
|
825
|
+
if (providers && providers.length > 0) {
|
|
826
|
+
conditions.push(inArray3(paymentWebhookEvents.provider, providers));
|
|
827
|
+
}
|
|
828
|
+
const eventTypes = normalizeArrayFilter(filter.eventType);
|
|
829
|
+
if (eventTypes && eventTypes.length > 0) {
|
|
830
|
+
conditions.push(inArray3(paymentWebhookEvents.event_type, eventTypes));
|
|
831
|
+
}
|
|
832
|
+
const statuses = normalizeArrayFilter(filter.status);
|
|
833
|
+
if (statuses && statuses.length > 0) {
|
|
834
|
+
conditions.push(inArray3(paymentWebhookEvents.status, statuses));
|
|
835
|
+
}
|
|
836
|
+
if (filter.transactionId) {
|
|
837
|
+
conditions.push(eq3(paymentWebhookEvents.transaction_id, filter.transactionId));
|
|
838
|
+
}
|
|
839
|
+
if (filter.dateRange?.from) {
|
|
840
|
+
conditions.push(gte2(paymentWebhookEvents.received_at, filter.dateRange.from));
|
|
841
|
+
}
|
|
842
|
+
if (filter.dateRange?.to) {
|
|
843
|
+
conditions.push(lte2(paymentWebhookEvents.received_at, filter.dateRange.to));
|
|
844
|
+
}
|
|
845
|
+
return conditions;
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
// src/repositories/audit-log.drizzle-repository.ts
|
|
850
|
+
import { eq as eq4, and as and4, inArray as inArray4, sql as sql4, count as count4, gte as gte3, lte as lte3 } from "drizzle-orm";
|
|
851
|
+
function mapToAuditLogEntry(row) {
|
|
852
|
+
return {
|
|
853
|
+
id: row.id,
|
|
854
|
+
transactionId: row.transaction_id,
|
|
855
|
+
action: row.action,
|
|
856
|
+
previousState: row.previous_state,
|
|
857
|
+
newState: row.new_state,
|
|
858
|
+
triggeredBy: row.triggered_by,
|
|
859
|
+
triggeredById: row.triggered_by_id,
|
|
860
|
+
ipAddress: row.ip_address,
|
|
861
|
+
userAgent: row.user_agent,
|
|
862
|
+
correlationId: row.correlation_id,
|
|
863
|
+
metadata: row.metadata,
|
|
864
|
+
createdAt: row.created_at
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
var DrizzleAuditLogRepository = class {
|
|
868
|
+
constructor(db) {
|
|
869
|
+
this.db = db;
|
|
870
|
+
}
|
|
871
|
+
async findById(id) {
|
|
872
|
+
const result = await this.db.select().from(paymentAuditLog).where(eq4(paymentAuditLog.id, id)).limit(1);
|
|
873
|
+
return result[0] ? mapToAuditLogEntry(result[0]) : null;
|
|
874
|
+
}
|
|
875
|
+
async create(data) {
|
|
876
|
+
const result = await this.db.insert(paymentAuditLog).values({
|
|
877
|
+
transaction_id: data.transactionId,
|
|
878
|
+
action: data.action,
|
|
879
|
+
previous_state: data.previousState,
|
|
880
|
+
new_state: data.newState,
|
|
881
|
+
triggered_by: data.triggeredBy,
|
|
882
|
+
triggered_by_id: data.triggeredById,
|
|
883
|
+
ip_address: data.ipAddress,
|
|
884
|
+
user_agent: data.userAgent,
|
|
885
|
+
correlation_id: data.correlationId,
|
|
886
|
+
metadata: data.metadata
|
|
887
|
+
}).returning();
|
|
888
|
+
return mapToAuditLogEntry(result[0]);
|
|
889
|
+
}
|
|
890
|
+
async createMany(entries) {
|
|
891
|
+
if (entries.length === 0) return [];
|
|
892
|
+
const values = entries.map((data) => ({
|
|
893
|
+
transaction_id: data.transactionId,
|
|
894
|
+
action: data.action,
|
|
895
|
+
previous_state: data.previousState,
|
|
896
|
+
new_state: data.newState,
|
|
897
|
+
triggered_by: data.triggeredBy,
|
|
898
|
+
triggered_by_id: data.triggeredById,
|
|
899
|
+
ip_address: data.ipAddress,
|
|
900
|
+
user_agent: data.userAgent,
|
|
901
|
+
correlation_id: data.correlationId,
|
|
902
|
+
metadata: data.metadata
|
|
903
|
+
}));
|
|
904
|
+
const result = await this.db.insert(paymentAuditLog).values(values).returning();
|
|
905
|
+
return result.map(mapToAuditLogEntry);
|
|
906
|
+
}
|
|
907
|
+
async findByTransactionId(transactionId, pagination = {}) {
|
|
908
|
+
const { limit = 100, offset = 0 } = pagination;
|
|
909
|
+
const [rows, [countResult]] = await Promise.all([
|
|
910
|
+
this.db.select().from(paymentAuditLog).where(eq4(paymentAuditLog.transaction_id, transactionId)).orderBy(sql4`${paymentAuditLog.created_at} ASC`).limit(limit).offset(offset),
|
|
911
|
+
this.db.select({ total: count4() }).from(paymentAuditLog).where(eq4(paymentAuditLog.transaction_id, transactionId))
|
|
912
|
+
]);
|
|
913
|
+
const entries = rows.map(mapToAuditLogEntry);
|
|
914
|
+
return applyPagination(entries, countResult?.total ?? 0, limit, offset);
|
|
915
|
+
}
|
|
916
|
+
async findMany(filter, pagination = {}) {
|
|
917
|
+
const { limit = 50, offset = 0 } = pagination;
|
|
918
|
+
const conditions = this.buildFilterConditions(filter);
|
|
919
|
+
const [rows, [countResult]] = await Promise.all([
|
|
920
|
+
this.db.select().from(paymentAuditLog).where(conditions.length > 0 ? and4(...conditions) : void 0).orderBy(sql4`${paymentAuditLog.created_at} DESC`).limit(limit).offset(offset),
|
|
921
|
+
this.db.select({ total: count4() }).from(paymentAuditLog).where(conditions.length > 0 ? and4(...conditions) : void 0)
|
|
922
|
+
]);
|
|
923
|
+
const entries = rows.map(mapToAuditLogEntry);
|
|
924
|
+
return applyPagination(entries, countResult?.total ?? 0, limit, offset);
|
|
925
|
+
}
|
|
926
|
+
async findByCorrelationId(correlationId) {
|
|
927
|
+
const result = await this.db.select().from(paymentAuditLog).where(eq4(paymentAuditLog.correlation_id, correlationId)).orderBy(sql4`${paymentAuditLog.created_at} ASC`);
|
|
928
|
+
return result.map(mapToAuditLogEntry);
|
|
929
|
+
}
|
|
930
|
+
async getTransactionHistory(transactionId) {
|
|
931
|
+
const result = await this.db.select().from(paymentAuditLog).where(eq4(paymentAuditLog.transaction_id, transactionId)).orderBy(sql4`${paymentAuditLog.created_at} ASC`);
|
|
932
|
+
return result.map(mapToAuditLogEntry);
|
|
933
|
+
}
|
|
934
|
+
async countByAction(filter = {}) {
|
|
935
|
+
const conditions = this.buildFilterConditions(filter);
|
|
936
|
+
const result = await this.db.select({
|
|
937
|
+
action: paymentAuditLog.action,
|
|
938
|
+
count: count4()
|
|
939
|
+
}).from(paymentAuditLog).where(conditions.length > 0 ? and4(...conditions) : void 0).groupBy(paymentAuditLog.action);
|
|
940
|
+
const counts = {
|
|
941
|
+
created: 0,
|
|
942
|
+
status_changed: 0,
|
|
943
|
+
authorized: 0,
|
|
944
|
+
captured: 0,
|
|
945
|
+
voided: 0,
|
|
946
|
+
refund_initiated: 0,
|
|
947
|
+
refund_completed: 0,
|
|
948
|
+
webhook_received: 0,
|
|
949
|
+
webhook_processed: 0,
|
|
950
|
+
error_occurred: 0,
|
|
951
|
+
retry_attempted: 0,
|
|
952
|
+
manual_intervention: 0
|
|
953
|
+
};
|
|
954
|
+
for (const row of result) {
|
|
955
|
+
counts[row.action] = row.count;
|
|
956
|
+
}
|
|
957
|
+
return counts;
|
|
958
|
+
}
|
|
959
|
+
// ============================================================================
|
|
960
|
+
// Private Helpers
|
|
961
|
+
// ============================================================================
|
|
962
|
+
buildFilterConditions(filter) {
|
|
963
|
+
const conditions = [];
|
|
964
|
+
if (filter.transactionId) {
|
|
965
|
+
conditions.push(eq4(paymentAuditLog.transaction_id, filter.transactionId));
|
|
966
|
+
}
|
|
967
|
+
if (filter.correlationId) {
|
|
968
|
+
conditions.push(eq4(paymentAuditLog.correlation_id, filter.correlationId));
|
|
969
|
+
}
|
|
970
|
+
if (filter.triggeredById) {
|
|
971
|
+
conditions.push(eq4(paymentAuditLog.triggered_by_id, filter.triggeredById));
|
|
972
|
+
}
|
|
973
|
+
const actions = normalizeArrayFilter(filter.action);
|
|
974
|
+
if (actions && actions.length > 0) {
|
|
975
|
+
conditions.push(inArray4(paymentAuditLog.action, actions));
|
|
976
|
+
}
|
|
977
|
+
const triggers = normalizeArrayFilter(filter.triggeredBy);
|
|
978
|
+
if (triggers && triggers.length > 0) {
|
|
979
|
+
conditions.push(inArray4(paymentAuditLog.triggered_by, triggers));
|
|
980
|
+
}
|
|
981
|
+
if (filter.dateRange?.from) {
|
|
982
|
+
conditions.push(gte3(paymentAuditLog.created_at, filter.dateRange.from));
|
|
983
|
+
}
|
|
984
|
+
if (filter.dateRange?.to) {
|
|
985
|
+
conditions.push(lte3(paymentAuditLog.created_at, filter.dateRange.to));
|
|
986
|
+
}
|
|
987
|
+
return conditions;
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// src/repositories/provider-health.drizzle-repository.ts
|
|
992
|
+
import { eq as eq5, or, sql as sql5 } from "drizzle-orm";
|
|
993
|
+
function mapToProviderHealth(row) {
|
|
994
|
+
return {
|
|
995
|
+
id: row.id,
|
|
996
|
+
provider: row.provider,
|
|
997
|
+
circuitState: row.circuit_state,
|
|
998
|
+
failureCount: parseInt(row.failure_count ?? "0", 10),
|
|
999
|
+
successCount: parseInt(row.success_count ?? "0", 10),
|
|
1000
|
+
lastFailureAt: row.last_failure_at,
|
|
1001
|
+
lastSuccessAt: row.last_success_at,
|
|
1002
|
+
circuitOpenedAt: row.circuit_opened_at,
|
|
1003
|
+
nextRetryAt: row.next_retry_at,
|
|
1004
|
+
avgLatencyMs: row.avg_latency_ms ? parseNumeric(row.avg_latency_ms) : null,
|
|
1005
|
+
errorRate: row.error_rate ? parseNumeric(row.error_rate) : null,
|
|
1006
|
+
requestCountWindow: parseInt(row.request_count_window ?? "0", 10),
|
|
1007
|
+
lastHealthCheckAt: row.last_health_check_at,
|
|
1008
|
+
healthCheckResult: row.health_check_result,
|
|
1009
|
+
createdAt: row.created_at,
|
|
1010
|
+
updatedAt: row.updated_at
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
var DrizzleProviderHealthRepository = class {
|
|
1014
|
+
constructor(db) {
|
|
1015
|
+
this.db = db;
|
|
1016
|
+
}
|
|
1017
|
+
async findByProvider(provider) {
|
|
1018
|
+
const result = await this.db.select().from(providerHealth).where(eq5(providerHealth.provider, provider)).limit(1);
|
|
1019
|
+
return result[0] ? mapToProviderHealth(result[0]) : null;
|
|
1020
|
+
}
|
|
1021
|
+
async getOrCreate(provider) {
|
|
1022
|
+
const existing = await this.findByProvider(provider);
|
|
1023
|
+
if (existing) return existing;
|
|
1024
|
+
const result = await this.db.insert(providerHealth).values({
|
|
1025
|
+
provider,
|
|
1026
|
+
circuit_state: "closed"
|
|
1027
|
+
}).returning();
|
|
1028
|
+
return mapToProviderHealth(result[0]);
|
|
1029
|
+
}
|
|
1030
|
+
async update(provider, data) {
|
|
1031
|
+
const updateData = {
|
|
1032
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1033
|
+
};
|
|
1034
|
+
if (data.circuitState !== void 0) updateData.circuit_state = data.circuitState;
|
|
1035
|
+
if (data.failureCount !== void 0) updateData.failure_count = String(data.failureCount);
|
|
1036
|
+
if (data.successCount !== void 0) updateData.success_count = String(data.successCount);
|
|
1037
|
+
if (data.lastFailureAt !== void 0) updateData.last_failure_at = data.lastFailureAt;
|
|
1038
|
+
if (data.lastSuccessAt !== void 0) updateData.last_success_at = data.lastSuccessAt;
|
|
1039
|
+
if (data.circuitOpenedAt !== void 0) updateData.circuit_opened_at = data.circuitOpenedAt;
|
|
1040
|
+
if (data.nextRetryAt !== void 0) updateData.next_retry_at = data.nextRetryAt;
|
|
1041
|
+
if (data.avgLatencyMs !== void 0) updateData.avg_latency_ms = String(data.avgLatencyMs);
|
|
1042
|
+
if (data.errorRate !== void 0) updateData.error_rate = String(data.errorRate);
|
|
1043
|
+
if (data.requestCountWindow !== void 0)
|
|
1044
|
+
updateData.request_count_window = String(data.requestCountWindow);
|
|
1045
|
+
if (data.lastHealthCheckAt !== void 0)
|
|
1046
|
+
updateData.last_health_check_at = data.lastHealthCheckAt;
|
|
1047
|
+
if (data.healthCheckResult !== void 0)
|
|
1048
|
+
updateData.health_check_result = data.healthCheckResult;
|
|
1049
|
+
const result = await this.db.update(providerHealth).set(updateData).where(eq5(providerHealth.provider, provider)).returning();
|
|
1050
|
+
return result[0] ? mapToProviderHealth(result[0]) : null;
|
|
1051
|
+
}
|
|
1052
|
+
async findAll() {
|
|
1053
|
+
const result = await this.db.select().from(providerHealth).orderBy(sql5`${providerHealth.provider} ASC`);
|
|
1054
|
+
return result.map(mapToProviderHealth);
|
|
1055
|
+
}
|
|
1056
|
+
async recordSuccess(provider, latencyMs) {
|
|
1057
|
+
await this.getOrCreate(provider);
|
|
1058
|
+
await this.db.update(providerHealth).set({
|
|
1059
|
+
success_count: sql5`CAST(${providerHealth.success_count} AS INTEGER) + 1`,
|
|
1060
|
+
request_count_window: sql5`CAST(${providerHealth.request_count_window} AS INTEGER) + 1`,
|
|
1061
|
+
last_success_at: /* @__PURE__ */ new Date(),
|
|
1062
|
+
avg_latency_ms: sql5`CASE
|
|
1063
|
+
WHEN ${providerHealth.avg_latency_ms} IS NULL THEN ${latencyMs}
|
|
1064
|
+
ELSE (CAST(${providerHealth.avg_latency_ms} AS DECIMAL) * 0.9 + ${latencyMs} * 0.1)
|
|
1065
|
+
END`,
|
|
1066
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1067
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1068
|
+
}
|
|
1069
|
+
async recordFailure(provider, error) {
|
|
1070
|
+
await this.getOrCreate(provider);
|
|
1071
|
+
const updateData = {
|
|
1072
|
+
last_failure_at: /* @__PURE__ */ new Date(),
|
|
1073
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1074
|
+
};
|
|
1075
|
+
if (error) {
|
|
1076
|
+
updateData.health_check_result = { healthy: false, error };
|
|
1077
|
+
}
|
|
1078
|
+
await this.db.update(providerHealth).set({
|
|
1079
|
+
...updateData,
|
|
1080
|
+
failure_count: sql5`CAST(${providerHealth.failure_count} AS INTEGER) + 1`,
|
|
1081
|
+
request_count_window: sql5`CAST(${providerHealth.request_count_window} AS INTEGER) + 1`
|
|
1082
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1083
|
+
}
|
|
1084
|
+
async openCircuit(provider, retryAfterMs) {
|
|
1085
|
+
const now = /* @__PURE__ */ new Date();
|
|
1086
|
+
const nextRetry = new Date(now.getTime() + retryAfterMs);
|
|
1087
|
+
await this.getOrCreate(provider);
|
|
1088
|
+
await this.db.update(providerHealth).set({
|
|
1089
|
+
circuit_state: "open",
|
|
1090
|
+
circuit_opened_at: now,
|
|
1091
|
+
next_retry_at: nextRetry,
|
|
1092
|
+
updated_at: now
|
|
1093
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1094
|
+
}
|
|
1095
|
+
async closeCircuit(provider) {
|
|
1096
|
+
await this.db.update(providerHealth).set({
|
|
1097
|
+
circuit_state: "closed",
|
|
1098
|
+
circuit_opened_at: null,
|
|
1099
|
+
next_retry_at: null,
|
|
1100
|
+
failure_count: "0",
|
|
1101
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1102
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1103
|
+
}
|
|
1104
|
+
async halfOpenCircuit(provider) {
|
|
1105
|
+
await this.db.update(providerHealth).set({
|
|
1106
|
+
circuit_state: "half_open",
|
|
1107
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1108
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1109
|
+
}
|
|
1110
|
+
async updateHealthCheck(provider, result) {
|
|
1111
|
+
await this.getOrCreate(provider);
|
|
1112
|
+
await this.db.update(providerHealth).set({
|
|
1113
|
+
last_health_check_at: /* @__PURE__ */ new Date(),
|
|
1114
|
+
health_check_result: result,
|
|
1115
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1116
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1117
|
+
}
|
|
1118
|
+
async findOpenCircuits() {
|
|
1119
|
+
const result = await this.db.select().from(providerHealth).where(eq5(providerHealth.circuit_state, "open"));
|
|
1120
|
+
return result.map(mapToProviderHealth);
|
|
1121
|
+
}
|
|
1122
|
+
async findReadyForRetry() {
|
|
1123
|
+
const now = /* @__PURE__ */ new Date();
|
|
1124
|
+
const result = await this.db.select().from(providerHealth).where(
|
|
1125
|
+
or(
|
|
1126
|
+
eq5(providerHealth.circuit_state, "half_open"),
|
|
1127
|
+
sql5`${providerHealth.circuit_state} = 'open' AND ${providerHealth.next_retry_at} <= ${now}`
|
|
1128
|
+
)
|
|
1129
|
+
);
|
|
1130
|
+
return result.map(mapToProviderHealth);
|
|
1131
|
+
}
|
|
1132
|
+
async resetStats(provider) {
|
|
1133
|
+
await this.db.update(providerHealth).set({
|
|
1134
|
+
failure_count: "0",
|
|
1135
|
+
success_count: "0",
|
|
1136
|
+
request_count_window: "0",
|
|
1137
|
+
avg_latency_ms: null,
|
|
1138
|
+
error_rate: null,
|
|
1139
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1140
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1141
|
+
}
|
|
1142
|
+
async updateErrorRate(provider) {
|
|
1143
|
+
const health = await this.findByProvider(provider);
|
|
1144
|
+
if (!health || health.requestCountWindow === 0) return 0;
|
|
1145
|
+
const errorRate = health.failureCount / health.requestCountWindow;
|
|
1146
|
+
await this.db.update(providerHealth).set({
|
|
1147
|
+
error_rate: String(errorRate),
|
|
1148
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
1149
|
+
}).where(eq5(providerHealth.provider, provider));
|
|
1150
|
+
return errorRate;
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// src/repositories/index.ts
|
|
1155
|
+
function createDrizzleRepositories(db) {
|
|
1156
|
+
return {
|
|
1157
|
+
transactions: new DrizzleTransactionRepository(db),
|
|
1158
|
+
paymentMethods: new DrizzlePaymentMethodRepository(db),
|
|
1159
|
+
webhookEvents: new DrizzleWebhookEventRepository(db),
|
|
1160
|
+
auditLog: new DrizzleAuditLogRepository(db),
|
|
1161
|
+
providerHealth: new DrizzleProviderHealthRepository(db)
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
export {
|
|
1165
|
+
DrizzleAuditLogRepository,
|
|
1166
|
+
DrizzlePaymentMethodRepository,
|
|
1167
|
+
DrizzleProviderHealthRepository,
|
|
1168
|
+
DrizzleTransactionRepository,
|
|
1169
|
+
DrizzleWebhookEventRepository,
|
|
1170
|
+
applyPagination,
|
|
1171
|
+
createDrizzleRepositories,
|
|
1172
|
+
normalizeArrayFilter,
|
|
1173
|
+
parseNumeric,
|
|
1174
|
+
toCamelCase,
|
|
1175
|
+
toSnakeCase
|
|
1176
|
+
};
|
|
1177
|
+
//# sourceMappingURL=index.js.map
|