102 lines
3.3 KiB
TypeScript
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 });
|
|
},
|
|
};
|
|
}
|