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