221 lines
7.2 KiB
TypeScript
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,
|
|
]);
|
|
});
|
|
},
|
|
};
|
|
}
|