# `definePaymentProvider` ## Purpose Factory helper for sibling `@suaveplan/payment-provider-*` packages that want to expose a `PaymentProvider` (from `@suaveplan/payment`) without authoring a full class. The factory returns a frozen object that satisfies the structural `PaymentProvider` contract and carries the caller-supplied method references verbatim — closures are preserved, no `this` rebinding happens. Sibling packages MAY use this factory or return a class instance from their `new XProvider(...)` constructor; both register identically into {@link ProviderRegistry}. The factory exists so providers without per-instance state (deterministic test providers, in-memory mocks, thin wrappers around an HTTP closure) can skip the class boilerplate. ## Features - Literal-typed `name` — the result narrows to `PaymentProvider & { readonly name: TName }` so consumers can discriminate at the type level (`provider.name === "stripe"` narrows the rest of the object) - Frozen output — accidental mutation downstream throws in strict mode - Method-identity preservation — the function passed in is the function dispatched at call time (useful for spies / instrumentation that identify methods by reference) - Zero runtime cost beyond a single `Object.freeze` - Universal — no Node, browser, or React imports ## Usage: Basic ```ts import { definePaymentProvider, ProviderRegistry } from "@suaveplan/payment-providers"; const stub = definePaymentProvider({ name: "stub", createIntent: async (input) => ({ id: "intent_1", provider: "stub", amount: input.amount, currency: input.amount.currency, status: "created", captureMethod: input.captureMethod ?? "automatic", refunds: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }), confirmIntent: async () => { throw new Error("not used"); }, captureIntent: async () => { throw new Error("not used"); }, cancelIntent: async () => { throw new Error("not used"); }, refund: async () => { throw new Error("not used"); }, getIntent: async () => { throw new Error("not used"); }, }); const registry = new ProviderRegistry(); registry.register(stub); ``` ## Usage: Advanced ```ts import { definePaymentProvider, ProviderRegistry, } from "@suaveplan/payment-providers"; // A deterministic test provider that records every call by reference // — note that `recordedCreate` IS the function exposed on // `provider.createIntent`, so spies / assertions can identify it by // equality. const calls: string[] = []; const recordedCreate = async (input: never): Promise => { calls.push("create"); void input; throw new Error("test-only stub"); }; const provider = definePaymentProvider({ name: "test", createIntent: recordedCreate, confirmIntent: recordedCreate, captureIntent: recordedCreate, cancelIntent: recordedCreate, refund: recordedCreate, getIntent: recordedCreate, }); // Type narrowing on the literal name. if (provider.name === "test") { // `provider` here is narrowed to `{ name: "test", ... }` } // The factory result is frozen. expect(Object.isFrozen(provider)).toBe(true); expect(provider.createIntent).toBe(recordedCreate); const registry = new ProviderRegistry(); registry.register(provider); expect(registry.get("test")).toBe(provider); ``` ## API Reference | Symbol | Kind | Signature | |---|---|---| | `PaymentProviderSpec` | `interface` | `{ name: TName; createIntent; confirmIntent; captureIntent; cancelIntent; refund; getIntent }` — every method type matches the `PaymentProvider` interface from `@suaveplan/payment` | | `definePaymentProvider(spec)` | `function` | returns `PaymentProvider & { readonly name: TName }`, frozen | ## Implementation Notes `definePaymentProvider` is intentionally minimal — it does not register the result anywhere, does not wrap methods in instrumentation, and does not perform validation beyond TypeScript's structural checks. Validation belongs to the layer that decides what to do with a provider (the `ProviderRegistry`, the `PaymentEngine`, etc.); the factory's only job is to package a spec into a frozen, literal-typed `PaymentProvider`. The `Object.freeze` call freezes the top-level object only; the methods themselves remain mutable function objects so sibling packages can attach their own metadata if needed.