Files
WRNexusJS/packages/payment/src/database.ts
T
Clintchiz fe44bc2091
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
fix: make payment event reduction monotonic
2026-08-23 21:34:59 +05:30

221 lines
7.2 KiB
TypeScript

import { getDb } from "@wrnexus/db";
import type { PaymentIntent, PaymentRefund, PaymentStore } from "./types.ts";
const parse = <T>(value: unknown, fallback: T): T => {
try {
return typeof value === "string" ? JSON.parse(value) : (value as T);
} catch {
return fallback;
}
};
export function databasePaymentStore(database = "default"): PaymentStore {
const db = () => getDb(database);
const intent = (row: any): PaymentIntent => ({
id: String(row.id),
subject: String(row.subject),
gateway: String(row.gateway),
gatewayRef: String(row.gateway_ref),
amount: Number(row.amount),
currency: String(row.currency),
idempotencyKey: String(row.idempotency_key),
createdAt: Number(row.created_at),
checkoutUrl: row.checkout_url ? String(row.checkout_url) : undefined,
client: parse(row.client_json, {}),
metadata: parse(row.metadata_json, {}),
});
const refund = (row: any): PaymentRefund => ({
id: String(row.id),
intentId: String(row.intent_id),
gatewayRef: String(row.gateway_ref ?? ""),
amount: Number(row.amount),
currency: String(row.currency),
reason: String(row.reason),
idempotencyKey: String(row.idempotency_key),
status: row.status,
createdAt: Number(row.created_at),
});
return {
async findIntentByKey(key) {
const row = await db().one("SELECT * FROM wrn_payment_intent WHERE idempotency_key = ?", [
key,
]);
return row ? intent(row) : null;
},
async putIntent(row) {
await db().exec(
"INSERT INTO wrn_payment_intent (id,subject,gateway,gateway_ref,amount,currency,idempotency_key,created_at,checkout_url,client_json,metadata_json) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
[
row.id,
row.subject,
row.gateway,
row.gatewayRef,
row.amount,
row.currency,
row.idempotencyKey,
row.createdAt,
row.checkoutUrl ?? null,
JSON.stringify(row.client),
JSON.stringify(row.metadata),
],
);
},
async getIntent(id) {
const row = await db().one("SELECT * FROM wrn_payment_intent WHERE id = ?", [id]);
return row ? intent(row) : null;
},
async listIntents(filter = {}) {
const clauses: string[] = [],
values: unknown[] = [];
if (filter.subject) {
clauses.push("subject = ?");
values.push(filter.subject);
}
if (filter.gateway) {
clauses.push("gateway = ?");
values.push(filter.gateway);
}
return (
await db().all(
`SELECT * FROM wrn_payment_intent${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY created_at DESC`,
values,
)
).map(intent);
},
async appendEvent(row) {
try {
const result = await db().exec(
"INSERT INTO wrn_payment_event (id,intent_id,type,status,created_at,occurred_at,gateway_sequence,payload_json) VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(id) DO NOTHING",
[
row.id,
row.intentId,
row.type,
row.status,
row.at,
row.occurredAt ?? row.at,
row.sequence === undefined ? null : String(row.sequence),
JSON.stringify(row.payload ?? null),
],
);
return result.changes > 0;
} catch {
return false;
}
},
async events(id) {
return (
await db().all<any>(
"SELECT * FROM wrn_payment_event WHERE intent_id = ? ORDER BY occurred_at ASC, gateway_sequence ASC, id ASC",
[id],
)
).map((row) => ({
id: String(row.id),
intentId: String(row.intent_id),
type: String(row.type),
status: row.status,
at: Number(row.created_at),
occurredAt: Number(row.occurred_at ?? row.created_at),
sequence: row.gateway_sequence == null ? undefined : String(row.gateway_sequence),
payload: parse(row.payload_json, null),
}));
},
async reserveRefund(row, captured) {
try {
const result = await db().exec(
"INSERT INTO wrn_payment_refund (id,intent_id,gateway_ref,amount,currency,reason,idempotency_key,status,created_at) SELECT ?,?,?,?,?,?,?,?,? WHERE ? >= COALESCE((SELECT SUM(amount) FROM wrn_payment_refund WHERE intent_id = ? AND status <> 'failed'),0) + ?",
[
row.id,
row.intentId,
row.gatewayRef,
row.amount,
row.currency,
row.reason,
row.idempotencyKey,
row.status,
row.createdAt,
captured,
row.intentId,
row.amount,
],
);
return result.changes > 0;
} catch {
return false;
}
},
async updateRefund(id, status, gatewayRef) {
await db().exec("UPDATE wrn_payment_refund SET status = ?, gateway_ref = ? WHERE id = ?", [
status,
gatewayRef,
id,
]);
},
async refunds(id) {
return (
await db().all("SELECT * FROM wrn_payment_refund WHERE intent_id = ? ORDER BY created_at", [
id,
])
).map(refund);
},
async methods(subject) {
return (
await db().all<any>(
"SELECT * FROM wrn_payment_method WHERE subject = ? ORDER BY is_default DESC, created_at",
[subject],
)
).map((row) => ({
id: String(row.id),
subject: String(row.subject),
gateway: String(row.gateway),
gatewayRef: String(row.gateway_ref),
brand: row.brand ? String(row.brand) : undefined,
last4: row.last4 ? String(row.last4) : undefined,
expires: row.expires ? String(row.expires) : undefined,
default: Boolean(row.is_default),
}));
},
async getMethod(id) {
const row = await db().one<any>("SELECT * FROM wrn_payment_method WHERE id = ?", [id]);
return row
? {
id: String(row.id),
subject: String(row.subject),
gateway: String(row.gateway),
gatewayRef: String(row.gateway_ref),
brand: row.brand ? String(row.brand) : undefined,
last4: row.last4 ? String(row.last4) : undefined,
expires: row.expires ? String(row.expires) : undefined,
default: Boolean(row.is_default),
}
: null;
},
async putMethod(row) {
await db().exec(
"INSERT INTO wrn_payment_method (id,subject,gateway,gateway_ref,brand,last4,expires,is_default,created_at) VALUES (?,?,?,?,?,?,?,?,?)",
[
row.id,
row.subject,
row.gateway,
row.gatewayRef,
row.brand ?? null,
row.last4 ?? null,
row.expires ?? null,
row.default ? 1 : 0,
Date.now(),
],
);
},
async removeMethod(id) {
await db().exec("DELETE FROM wrn_payment_method WHERE id = ?", [id]);
},
async defaultMethod(subject, id) {
await db().tx(async (tx) => {
await tx.exec("UPDATE wrn_payment_method SET is_default = 0 WHERE subject = ?", [subject]);
await tx.exec("UPDATE wrn_payment_method SET is_default = 1 WHERE subject = ? AND id = ?", [
subject,
id,
]);
});
},
};
}