import type { CheckoutSessionInfo, CreateCheckoutParams, OffSessionResult, PaymentGateway, PaymentIntentInfo, } from "@/lib/payment-gateway"; import type { StripeMode } from "@/lib/sales-gate"; type StoredSession = CheckoutSessionInfo & { params: CreateCheckoutParams }; /** * Stripe-Attrappe im Speicher. Bildet die fuer die Geschaeftslogik * relevanten Eigenschaften nach: Idempotenzschluessel, Session-Zustaende, * PaymentIntents und das Ergebnis von Off-Session-Einzuegen. */ export class FakeGateway implements PaymentGateway { private seq = 0; private readonly byKey = new Map(); readonly sessions = new Map(); readonly paymentIntents = new Map(); readonly offSessionCharges: { key: string; amountCents: number; installmentId: string }[] = []; readonly detached: string[] = []; readonly refunds: { id: string; paymentIntentId: string; amountCents: number }[] = []; /** Ergebnisse fuer die naechsten Off-Session-Einzuege (Standard: Erfolg). */ offSessionOutcomes: OffSessionResult["outcome"][] = []; failNextCheckout = false; constructor(readonly mode: StripeMode = "test") {} private id(prefix: string): string { this.seq++; return `${prefix}_test_${this.seq}`; } private once(key: string, create: () => T): T { if (!this.byKey.has(key)) this.byKey.set(key, create()); return this.byKey.get(key) as T; } async createCustomer(_params: { email: string; name: string }, key: string) { return this.once(`cus:${key}`, () => ({ id: this.id("cus") })); } async createCheckoutSession(params: CreateCheckoutParams, key: string) { if (this.failNextCheckout) { this.failNextCheckout = false; throw new Error("Stripe nicht erreichbar (simuliert)"); } return this.once(`cs:${key}`, () => { const id = this.id("cs"); const url = `https://checkout.stripe.test/${id}`; this.sessions.set(id, { id, status: "open", paymentStatus: "unpaid", paymentIntentId: null, customerId: params.customerId, url, metadata: params.metadata, params, }); return { id, url }; }); } async retrieveCheckoutSession(id: string): Promise { const s = this.sessions.get(id); if (!s) throw new Error(`Unbekannte Session ${id}`); return { ...s }; } async expireCheckoutSession(id: string): Promise { const s = this.sessions.get(id); if (s && s.status === "open") s.status = "expired"; } /** Simuliert die Zahlung durch die Kundin in Stripe Checkout. */ completeSession(id: string, opts: { paid?: boolean; paymentMethodId?: string; amountCents?: number } = {}): CheckoutSessionInfo { const s = this.sessions.get(id); if (!s) throw new Error(`Unbekannte Session ${id}`); const piId = this.id("pi"); const paid = opts.paid ?? true; this.paymentIntents.set(piId, { id: piId, status: paid ? "succeeded" : "processing", amountReceived: paid ? (opts.amountCents ?? s.params.amountCents) : 0, paymentMethodId: opts.paymentMethodId ?? this.id("pm"), chargeId: paid ? this.id("ch") : null, metadata: s.metadata, }); s.status = "complete"; s.paymentStatus = paid ? "paid" : "unpaid"; s.paymentIntentId = piId; return { ...s }; } async retrievePaymentIntent(id: string): Promise { const pi = this.paymentIntents.get(id); if (!pi) throw new Error(`Unbekannter PaymentIntent ${id}`); return { ...pi }; } async chargeOffSession( params: { customerId: string; paymentMethodId: string; amountCents: number; metadata: Record }, key: string, ): Promise { return this.once(`pi:${key}`, () => { const outcome = this.offSessionOutcomes.shift() ?? "succeeded"; const piId = this.id("pi"); const chargeId = outcome === "succeeded" ? this.id("ch") : null; this.paymentIntents.set(piId, { id: piId, status: outcome === "succeeded" ? "succeeded" : outcome === "processing" ? "processing" : outcome === "requires_action" ? "requires_action" : "requires_payment_method", amountReceived: outcome === "succeeded" ? params.amountCents : 0, paymentMethodId: params.paymentMethodId, chargeId, metadata: params.metadata, }); if (outcome === "succeeded") { this.offSessionCharges.push({ key, amountCents: params.amountCents, installmentId: params.metadata.installmentId! }); } return { outcome, paymentIntentId: piId, chargeId, failureCode: outcome === "failed" ? "card_declined" : outcome === "requires_action" ? "authentication_required" : null, }; }); } async detachPaymentMethod(id: string): Promise { this.detached.push(id); } async createRefund(params: { paymentIntentId: string; amountCents: number }, key: string) { return this.once(`re:${key}`, () => { const refund = { id: this.id("re"), paymentIntentId: params.paymentIntentId, amountCents: params.amountCents }; this.refunds.push(refund); return { id: refund.id, status: "succeeded", amount: params.amountCents }; }); } }