Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# @suaveplan/payment-provider-mollie - Universal Mollie Payments API v2 adapter for @suaveplan/payment. Sibling provider package on the slim @suaveplan/payment-providers core: implements PaymentProvider with Mollie's string-amount marshalling ({ currency, value: "X.YY" }) round-trip via exported formatMollieAmount / parseMollieAmount helpers.
|
||||
|
||||
Universal Mollie Payments API v2 adapter for `@suaveplan/payment`.
|
||||
Sibling provider package on the slim `@suaveplan/payment-providers`
|
||||
core: implements `PaymentProvider` with Mollie's string-amount
|
||||
marshalling (`{ currency, value: "X.YY" }`) round-trip via the
|
||||
exported `formatMollieAmount` / `parseMollieAmount` helpers.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [mollie](./src/mollie/mollie.md) — `MollieProvider` is the sibling adapter that implements the `PaymentProvider` contract from `@suaveplan/payment…
|
||||
- [mollie](./src/mollie/mollie.md) — `MollieProvider` is the sibling adapter that implements the `PaymentProvider` contract from `@suaveplan/payment…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/payment-provider-mollie @suaveplan/payment-providers @suaveplan/payment
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- `MollieProvider` — full `PaymentProvider` lifecycle
|
||||
(`createIntent`, `confirmIntent`, `captureIntent`, `cancelIntent`,
|
||||
`refund`, `getIntent`) on top of the slim `httpRequest` helper.
|
||||
- Pure, exported amount helpers — `formatMollieAmount(amount: bigint)`
|
||||
and `parseMollieAmount(value: string)` — that round-trip losslessly
|
||||
via `bigint` and cover the 1-cent, exact-major, large-value, and
|
||||
over-precision edges.
|
||||
- Total exported status maps — `mapMollieStatus` and
|
||||
`mapMollieRefundStatus` — each individually unit-testable without
|
||||
invoking the network path.
|
||||
- `Authorization: Bearer <apiKey>` header auto-applied on every call.
|
||||
- Universal — no Node-specific or browser-specific globals; runs
|
||||
on Node 18+, Bun, browsers, and Web Workers.
|
||||
- All failures surface as `PaymentProviderError` from
|
||||
`@suaveplan/payment-providers` carrying a `PAYMENT_PROVIDER_*` code.
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/mollie/mollie.md`](./src/mollie/mollie.md) —
|
||||
`MollieProvider` class, helpers, and status maps.
|
||||
|
||||
## Sibling packages
|
||||
|
||||
Part of the `@suaveplan/payment-provider-*` family. See
|
||||
[`@suaveplan/payment-providers`](../payment-providers/README.md) for the
|
||||
shared foundation (`ProviderRegistry`, `httpRequest`,
|
||||
`composeAbortSignal`, `PaymentProviderError`, the `PAYMENT_PROVIDER_*`
|
||||
code registry, and the shared config shapes).
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# MollieProvider
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```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<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.
|
||||
Reference in New Issue
Block a user