import type { CreatePaymentIntentInput, PaymentIntent, PaymentProvider, PaymentStatus, Refund, RefundInput, } from "@suaveplan/payment"; interface MoneyLike { readonly amount: bigint; readonly currency: string; } function nowIso() { return new Date().toISOString(); } function makeMoney(amount: bigint, currency: string): MoneyLike { return { amount, currency }; } /** * Test-only `PaymentProvider` implementing the real interface from * `@suaveplan/payment`. Stores intents and refunds in two in-memory * maps so the tests can assert against deterministic state. No * external IO. */ export class FakePaymentProvider implements PaymentProvider { public readonly name = "fake"; private readonly intents = new Map(); private readonly idemMap = new Map(); private seq = 0; public failCreate = false; public returnEmptyOnCreate = false; public reset(): void { this.intents.clear(); this.idemMap.clear(); this.seq = 0; this.failCreate = false; this.returnEmptyOnCreate = false; } public async createIntent( input: CreatePaymentIntentInput ): Promise { if (this.failCreate) { throw new Error("simulated provider failure"); } if (this.returnEmptyOnCreate) { return null as unknown as PaymentIntent; } if (input.idempotencyKey !== undefined) { const existingId = this.idemMap.get(input.idempotencyKey); if (existingId !== undefined) { const found = this.intents.get(existingId); if (found) return found; } } this.seq += 1; const id = `pi_${this.seq}`; const intent: PaymentIntent = { id, status: input.captureMethod === "manual" ? "created" : "created", amount: makeMoney(input.amount.amount, input.amount.currency), capturedAmount: makeMoney(0n, input.amount.currency), refundedAmount: makeMoney(0n, input.amount.currency), captureMethod: input.captureMethod ?? "automatic", refunds: [], createdAt: nowIso(), updatedAt: nowIso(), ...(input.description !== undefined ? { description: input.description } : {}), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}), ...(input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {}), }; this.intents.set(id, intent); if (input.idempotencyKey !== undefined) { this.idemMap.set(input.idempotencyKey, id); } return intent; } public async confirmIntent(id: string): Promise { return this.updateStatus(id, "authorized"); } public async captureIntent( id: string, amount?: MoneyLike ): Promise { const current = this.mustGet(id); const captureAmt = amount?.amount ?? current.amount.amount; const newStatus: PaymentStatus = "captured"; const updated: PaymentIntent = { ...current, status: newStatus, capturedAmount: makeMoney(captureAmt, current.amount.currency), updatedAt: nowIso(), }; this.intents.set(id, updated); return updated; } public async cancelIntent(id: string): Promise { return this.updateStatus(id, "canceled"); } public async refund(input: RefundInput): Promise { const current = this.mustGet(input.paymentIntentId); const amt = input.amount?.amount ?? current.capturedAmount.amount; const refundedSoFar = current.refundedAmount.amount + amt; const refund: Refund = { id: `re_${this.seq + 1}`, paymentIntentId: input.paymentIntentId, amount: makeMoney(amt, current.amount.currency), status: "succeeded", ...(input.reason !== undefined ? { reason: input.reason } : {}), createdAt: nowIso(), ...(input.metadata !== undefined ? { metadata: input.metadata } : {}), }; this.seq += 1; const newStatus: PaymentStatus = refundedSoFar >= current.capturedAmount.amount ? "refunded" : "partially_refunded"; const updated: PaymentIntent = { ...current, status: newStatus, refundedAmount: makeMoney(refundedSoFar, current.amount.currency), refunds: [...current.refunds, refund], updatedAt: nowIso(), }; this.intents.set(current.id, updated); return refund; } public async getIntent(id: string): Promise { return this.mustGet(id); } private mustGet(id: string): PaymentIntent { const found = this.intents.get(id); if (found === undefined) { throw new Error(`provider has no intent "${id}"`); } return found; } private updateStatus(id: string, status: PaymentStatus): PaymentIntent { const current = this.mustGet(id); const updated: PaymentIntent = { ...current, status, updatedAt: nowIso(), }; this.intents.set(id, updated); return updated; } }