Files
skills/docs/money/payment/payment-provider-mollie/src/mollie/mollie.md
2026-05-29 08:20:56 +01:00

7.4 KiB

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 <apiKey> 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

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

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<PaymentIntent> — POST ${baseUrl}/payments. Defaults description and redirectUrl when omitted.
  • confirmIntent(intentId: string): Promise<PaymentIntent> — read-through to getIntent.
  • captureIntent(intentId: string): Promise<PaymentIntent> — read-through to getIntent (Mollie auto-captures).
  • cancelIntent(intentId: string): Promise<PaymentIntent> — DELETE ${baseUrl}/payments/${intentId}.
  • refund(input: RefundInput): Promise<Refund> — 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<PaymentIntent> — 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.