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
MollieProviderclass implementingPaymentProviderwith name"mollie".- Constructor takes
ApiKeyConfigfrom@suaveplan/payment-providers(apiKey, optionalbaseUrl,timeoutMs,fetch,signal,webhookSecret). - Auto-sets
Authorization: Bearer <apiKey>on every call. - Uses the slim-core
httpRequesthelper for JSON serialisation, per-call timeout, externalAbortSignalcomposition, and typed error surfacing. - Defaults
descriptionto"Payment"andredirectUrlto"https://localhost/return"when omitted, matching upstream behaviour. confirmIntentandcaptureIntentare read-throughs togetIntentbecause Mollie auto-captures.- Pure, exported amount helpers (
formatMollieAmount,parseMollieAmount) and status maps (mapMollieStatus,mapMollieRefundStatus) — all individually unit-testable. - All failures surface as
PaymentProviderErrorcarrying aPAYMENT_PROVIDER_*code; rawErroris 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.apiKeyis required;baseUrl,timeoutMs,fetch,signal, andwebhookSecretare optional.baseUrldefaults toMOLLIE_DEFAULT_BASE_URL;timeoutMsdefaults to30_000.name: "mollie"— literal-typed instance field.createIntent(input: CreatePaymentIntentInput): Promise<PaymentIntent>— POST${baseUrl}/payments. DefaultsdescriptionandredirectUrlwhen omitted.confirmIntent(intentId: string): Promise<PaymentIntent>— read-through togetIntent.captureIntent(intentId: string): Promise<PaymentIntent>— read-through togetIntent(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'samountfield wheninput.amountis undefined (Mollie issues a full refund).getIntent(intentId: string): Promise<PaymentIntent>— GET${baseUrl}/payments/${intentId}.- Throws:
PaymentProviderErrorwith codePAYMENT_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
fetchsurface, available in every supported runtime. - Tests inject a recording
FetchLikeclosure into the constructor instead of mutatingglobalThis.fetch. This satisfies rule 8 (no mocks) and matches the binance / ecb sibling pattern. confirmIntentandcaptureIntentare deliberate read-throughs togetIntent: Mollie auto-captures, so there is no separate confirm or capture step at the protocol level. Returning the current state preserves thePaymentProvidercontract without falsifying the underlying behaviour.formatMollieAmountandparseMollieAmountare inverses for everybigintproduced from a Mollie response. Round-trip is exact for zero, single cent, exact major unit, and large values up to2^53-and-beyond range (bigintis arbitrary-precision; the helpers never go throughnumber).- 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 theCardBrandunion. - Forwarded
AbortSignalcomposition (callersignal+ per-call timeout) is handled by the slim-corehttpRequest; this package does not re-implement it.