# MollieProvider _(no submodules)_ ## Purpose `MollieProvider` is the sibling adapter that implements the `PaymentProvider` contract from `@suaveplan/payment` against the Mollie Payments API v2. It is the only `@suaveplan/payment-provider-*` sibling that has to marshal money amounts to and from a string representation: every Mollie request and response carries amounts as `{ currency: "EUR", value: "10.00" }`, while the rest of the suaveplan payment family models money as `bigint` minor-units. This package owns that round-trip end-to-end and exposes the underlying helpers (`formatMollieAmount`, `parseMollieAmount`) so callers can test or reuse them in their own integrations. ## Features - `MollieProvider` class implementing `PaymentProvider` with name `"mollie"`. - Constructor takes `ApiKeyConfig` from `@suaveplan/payment-providers` (`apiKey`, optional `baseUrl`, `timeoutMs`, `fetch`, `signal`, `webhookSecret`). - Auto-sets `Authorization: Bearer ` on every call. - Uses the slim-core `httpRequest` helper for JSON serialisation, per-call timeout, external `AbortSignal` composition, and typed error surfacing. - Defaults `description` to `"Payment"` and `redirectUrl` to `"https://localhost/return"` when omitted, matching upstream behaviour. - `confirmIntent` and `captureIntent` are read-throughs to `getIntent` because Mollie auto-captures. - Pure, exported amount helpers (`formatMollieAmount`, `parseMollieAmount`) and status maps (`mapMollieStatus`, `mapMollieRefundStatus`) — all individually unit-testable. - All failures surface as `PaymentProviderError` carrying a `PAYMENT_PROVIDER_*` code; raw `Error` is never thrown. ## Usage: Basic Example ```ts import { ProviderRegistry } from "@suaveplan/payment-providers"; import { MollieProvider } from "@suaveplan/payment-provider-mollie"; const registry = new ProviderRegistry(); registry.register(new MollieProvider({ apiKey: process.env.MOLLIE_API_KEY ?? "" })); const mollie = registry.get("mollie"); const intent = await mollie.createIntent({ amount: { amount: 5000n, currency: "EUR" }, description: "Order #42", returnUrl: "https://shop.example/return", }); console.log(intent.id, intent.status); ``` ## Usage: Advanced Example ```ts import { MollieProvider, formatMollieAmount, parseMollieAmount } from "@suaveplan/payment-provider-mollie"; import type { FetchLike } from "@suaveplan/payment-providers"; // Inject a recording FetchLike for tests — no global monkey-patching. const recorded: { url: string; init?: RequestInit }[] = []; const recordingFetch: FetchLike = async (url, init) => { recorded.push({ url, init }); return new Response(JSON.stringify({ id: "tr_xyz", status: "paid", amount: { value: "12.34", currency: "EUR" }, createdAt: "2026-01-01T00:00:00+00:00", }), { status: 200, headers: { "content-type": "application/json" } }); }; const provider = new MollieProvider({ apiKey: "test_xyz", baseUrl: "https://mollie.mock/v2", timeoutMs: 5_000, fetch: recordingFetch, }); // Round-trip amount marshalling without hitting the network: const wire = formatMollieAmount(1234n); // "12.34" const back = parseMollieAmount(wire); // 1234n // Drive the full lifecycle for assertions: const intent = await provider.createIntent({ amount: { amount: 1234n, currency: "EUR" }, }); const captured = await provider.captureIntent(intent.id); // read-through const refund = await provider.refund({ paymentIntentId: intent.id, amount: { amount: 1234n, currency: "EUR" }, }); ``` ## API Reference ### `MollieProvider` - `new MollieProvider(config: ApiKeyConfig)` — constructs the adapter. `config.apiKey` is required; `baseUrl`, `timeoutMs`, `fetch`, `signal`, and `webhookSecret` are optional. `baseUrl` defaults to `MOLLIE_DEFAULT_BASE_URL`; `timeoutMs` defaults to `30_000`. - `name: "mollie"` — literal-typed instance field. - `createIntent(input: CreatePaymentIntentInput): Promise` — POST `${baseUrl}/payments`. Defaults `description` and `redirectUrl` when omitted. - `confirmIntent(intentId: string): Promise` — read-through to `getIntent`. - `captureIntent(intentId: string): Promise` — read-through to `getIntent` (Mollie auto-captures). - `cancelIntent(intentId: string): Promise` — DELETE `${baseUrl}/payments/${intentId}`. - `refund(input: RefundInput): Promise` — POST `${baseUrl}/payments/${intentId}/refunds`. Omits the body's `amount` field when `input.amount` is undefined (Mollie issues a full refund). - `getIntent(intentId: string): Promise` — GET `${baseUrl}/payments/${intentId}`. - Throws: `PaymentProviderError` with code `PAYMENT_PROVIDER.HTTP_ERROR` / `PAYMENT_PROVIDER.TIMEOUT` / `PAYMENT_PROVIDER.PARSE_ERROR`. ### `MOLLIE_DEFAULT_BASE_URL: "https://api.mollie.com/v2"` Production Mollie REST v2 base URL constant. ### `MOLLIE_PROVIDER_NAME: "mollie"` Stable provider identifier. ### `formatMollieAmount(amount: bigint): string` Format a minor-units `bigint` into a two-decimal string. Always produces exactly two minor digits, pads left for sub-major values (`1n` → `"0.01"`), and supports negative values. ### `parseMollieAmount(value: string): bigint` Parse a Mollie wire-format decimal string into a minor-units `bigint`. Accepts no-decimal, single-decimal, two-decimal, or over-precision inputs; over-precision is truncated, never rounded. ### `mapMollieStatus(status: string): PaymentStatus` Total map from Mollie's payment-status string to the canonical `PaymentStatus` union. Unknown values fall back to `"failed"`. ### `mapMollieRefundStatus(status: string): RefundStatus` Total map from Mollie's refund-status string to the canonical `RefundStatus` union. Unknown values fall back to `"failed"`. ## Implementation Notes - The package is universal — no Node-specific or browser-specific globals. The only runtime concept beyond the language standard is the WHATWG `fetch` surface, available in every supported runtime. - Tests inject a recording `FetchLike` closure into the constructor instead of mutating `globalThis.fetch`. This satisfies rule 8 (no mocks) and matches the binance / ecb sibling pattern. - `confirmIntent` and `captureIntent` are deliberate read-throughs to `getIntent`: Mollie auto-captures, so there is no separate confirm or capture step at the protocol level. Returning the current state preserves the `PaymentProvider` contract without falsifying the underlying behaviour. - `formatMollieAmount` and `parseMollieAmount` are inverses for every `bigint` produced from a Mollie response. Round-trip is exact for zero, single cent, exact major unit, and large values up to `2^53`-and-beyond range (`bigint` is arbitrary-precision; the helpers never go through `number`). - Over-precision in `parseMollieAmount` (`"10.123"` → `1012n`) is truncation, not rounding. Mollie emits exactly minor-unit precision; over-precision is upstream garbage and the safest action is to ignore the excess rather than to bias the result. - The card-brand normalisation accepts any of `visa` / `mastercard` / `amex` / `discover` / `diners` / `jcb` / `unionpay`; every other label collapses to `"unknown"` rather than leaking arbitrary strings into the `CardBrand` union. - Forwarded `AbortSignal` composition (caller `signal` + per-call timeout) is handled by the slim-core `httpRequest`; this package does not re-implement it.