feat: add gateway-neutral payment package
This commit is contained in:
1 parent
a9670c2a1c
commit
98e0813061
23 files changed
+1902
-6
No files matched your search
@@ -0,0 +1,101 @@
|
||||
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 });
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user