Files
WRNexusJS/packages/payment/src/store.ts
T
Clintchiz 98e0813061
Quality / quality (ubuntu-latest) (push) Failing after 9m55s
Quality / quality (windows-latest) (push) Canceled after 0s
feat: add gateway-neutral payment package
2026-08-23 21:05:27 +05:30

102 lines
3.3 KiB
TypeScript

import type {
PaymentEvent,
PaymentIntent,
PaymentMethod,
PaymentRefund,
PaymentStore,
} from "./types.ts";
export function memoryPaymentStore(): PaymentStore {
const intents = new Map<string, PaymentIntent>();
const keys = new Map<string, string>();
const eventRows = new Map<string, PaymentEvent>();
const refundRows = new Map<string, PaymentRefund>();
const methodRows = new Map<string, PaymentMethod>();
let lock = Promise.resolve();
const exclusive = async <T>(run: () => T | Promise<T>): Promise<T> => {
const previous = lock;
let release!: () => void;
lock = new Promise<void>((resolve) => (release = resolve));
await previous;
try {
return await run();
} finally {
release();
}
};
return {
async findIntentByKey(key) {
const id = keys.get(key);
return id ? structuredClone(intents.get(id)!) : null;
},
async putIntent(intent) {
intents.set(intent.id, structuredClone(intent));
keys.set(intent.idempotencyKey, intent.id);
},
async getIntent(id) {
const row = intents.get(id);
return row ? structuredClone(row) : null;
},
async listIntents(filter = {}) {
return [...intents.values()]
.filter(
(row) =>
(!filter.subject || row.subject === filter.subject) &&
(!filter.gateway || row.gateway === filter.gateway),
)
.map((row) => structuredClone(row));
},
async appendEvent(event) {
if (eventRows.has(event.id)) return false;
eventRows.set(event.id, structuredClone(event));
return true;
},
async events(intentId) {
return [...eventRows.values()]
.filter((row) => row.intentId === intentId)
.sort((a, b) => a.at - b.at)
.map((row) => structuredClone(row));
},
async reserveRefund(refund, captured) {
return exclusive(() => {
if ([...refundRows.values()].some((row) => row.idempotencyKey === refund.idempotencyKey))
return false;
const used = [...refundRows.values()]
.filter((row) => row.intentId === refund.intentId && row.status !== "failed")
.reduce((sum, row) => sum + row.amount, 0);
if (used + refund.amount > captured) return false;
refundRows.set(refund.id, structuredClone(refund));
return true;
});
},
async updateRefund(id, status, gatewayRef) {
const row = refundRows.get(id);
if (row) refundRows.set(id, { ...row, status, gatewayRef });
},
async refunds(intentId) {
return [...refundRows.values()]
.filter((row) => row.intentId === intentId)
.map((row) => structuredClone(row));
},
async methods(subject) {
return [...methodRows.values()]
.filter((row) => row.subject === subject)
.map((row) => structuredClone(row));
},
async getMethod(id) {
const row = methodRows.get(id);
return row ? structuredClone(row) : null;
},
async putMethod(method) {
methodRows.set(method.id, structuredClone(method));
},
async removeMethod(id) {
methodRows.delete(id);
},
async defaultMethod(subject, id) {
for (const [key, row] of methodRows)
if (row.subject === subject) methodRows.set(key, { ...row, default: key === id });
},
};
}