2.7 KiB
payment-intent-store
Purpose
Persistence contract for the intent-id → owner map every payment plugin needs to enforce per-user scoping on lookups, lists, and mutations. Without this map, a sequential or guessable intent id (which is what most provider APIs return) would let any authenticated user enumerate or even mutate other users' intents — the textbook insecure-direct-object-reference defect.
The interface ships as an injectable dependency so operators can wire a SurrealDB, Postgres, Redis, or DynamoDB backing store without changing any code in the plugin. The default in-process MemoryPaymentIntentStore (in the sibling module) is provided for tests, ephemeral dev environments, and single-process deployments.
Features
- Three-method contract:
put,get,listByOwner— minimal surface so adapters stay small. OwnedIntentcarriesid,ownerId, optionaltenantId, andcreatedAtfor sorting / GDPR retention.- Asynchronous everywhere so storage adapters can plug in network IO without monkey-patching the surface.
Usage: Basic
import {
type PaymentIntentStore,
MemoryPaymentIntentStore,
} from "@suaveplan/api-plugin-payment/store/payment-intent-store";
const store: PaymentIntentStore = new MemoryPaymentIntentStore();
await store.put({ id: "pi_abc", ownerId: "u1", createdAt: Date.now() });
const owned = await store.listByOwner("u1");
Usage: Advanced
class SurrealPaymentIntentStore implements PaymentIntentStore {
public constructor(private readonly db: SurrealAdapter) {}
public async put(record: OwnedIntent): Promise<void> {
await this.db.create("payment_intent", record);
}
public async get(id: string): Promise<OwnedIntent | undefined> {
const rows = await this.db.select<OwnedIntent>(`payment_intent:${id}`);
return rows[0];
}
public async listByOwner(ownerId: string): Promise<readonly OwnedIntent[]> {
return this.db.query<OwnedIntent[]>(
"SELECT * FROM payment_intent WHERE ownerId = $o",
{ o: ownerId }
);
}
}
API Reference
OwnedIntent—{ id: string; ownerId: string; tenantId?: string; createdAt: number }.PaymentIntentStore—put(record): Promise<void>,get(id): Promise<OwnedIntent | undefined>,listByOwner(ownerId): Promise<readonly OwnedIntent[]>.
Implementation Notes
The interface intentionally does NOT include delete — payment intents are a regulated audit surface and removal is the operator's responsibility (GDPR erasure, retention policy enforcement) outside this plugin's scope. The createdAt field is number (Unix ms) rather than Date so the structure stays JSON-safe for stores that round-trip through external serialisers.