feat: add gateway-neutral payment package
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
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,payload_json) VALUES (?,?,?,?,?,?) ON CONFLICT(id) DO NOTHING",
|
||||
[row.id, row.intentId, row.type, row.status, row.at, 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 created_at 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),
|
||||
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,
|
||||
]);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user