first commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
- `OwnedIntent` carries `id`, `ownerId`, optional `tenantId`, and `createdAt` for sorting / GDPR retention.
|
||||
- Asynchronous everywhere so storage adapters can plug in network IO without monkey-patching the surface.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user