> ## Documentation Index
> Fetch the complete documentation index at: https://docs.attesso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mollie

> Execute an approved Attesso action on Mollie and report finality.

Take an `ALLOW` from Attesso and execute it on Mollie as a payment, then report the outcome. This is the Mollie recipe for the pattern in [Bind an authorization to your PSP](../guides/bind-to-your-psp): authorize first, execute second, report finality. Attesso authorizes and records; your backend calls Mollie to execute, then reports the outcome to Attesso. Attesso never calls Mollie.

API host: `https://api.mollie.com/v2`. Test mode uses a `test_` API key and live uses `live_` (or the `testmode` parameter with an organization access token); test and live data are fully isolated. Mollie's test mode supports EUR only.

New to Attesso? The [quickstart](../quickstart) covers creating a workspace, a test key, and the approval trust this recipe assumes.

## Execute the approved action

Create the payment from the same immutable proposed action you sent to `/authorizations`; bind the two systems with your `external_action_reference`.

```js theme={"theme":"github-light"}
const payment = await (await fetch('https://api.mollie.com/v2/payments', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MOLLIE_API_KEY}`,   // test_ or live_ key, server-side only
    'Content-Type': 'application/json',
    'Idempotency-Key': 'booking-2026-08-04-001',             // your external_action_reference
  },
  body: JSON.stringify({
    amount: { currency: 'EUR', value: '189.00' },            // decimal string: EUR 189.00
    description: 'Attesso booking 2026-08-04-001',
    redirectUrl: 'https://your-app.example/mollie/return',
    webhookUrl: 'https://your-app.example/mollie/webhook',   // must be publicly reachable; Mollie validates at creation
    metadata: { attesso_mandate_id: 'mnd_...', attesso_authorization_id: 'aut_...', external_action_reference: 'booking-2026-08-04-001' },
  }),
})).json();

// payment.id (tr_...) is your provider_transaction_reference; redirect the
// payer to payment._links.checkout.href when a checkout is needed.
```

Keep the request derived from the authorized action: same amount, same currency, same reference. Mollie validates `webhookUrl` reachability at creation (a loopback URL is rejected); in local development point it at a public tunnel.

The loop runs once per Authorization. Under a [RECURRING mandate](../concepts/core-model), every execution takes this same authorize, execute, report path, each one counted against the mandate's per-period budget.

## Report the outcome to Attesso

* Definite acceptance (`paid`, or `authorized` for manually captured methods): commit with `provider_transaction_reference: payment.id`.
* Definite non-acceptance (`failed`, `expired`, `canceled`): cancel with `reason: EXECUTION_FAILED` (use `EXECUTOR_REJECTED` when Mollie refuses the action outright). With an earlier ALLOW, the disposition is `NO_LONGER_POSSIBLE`.
* A refund after commit is not an authorization transition; report it so the signed evidence carries the claim ([Refund reports](../guides/reconcile-authorizations#refund-reports)).
* `open`, `pending` (or a still-open checkout): not final; do not commit or cancel yet.
* A timeout or lost response: reconcile before deciding (below).

## Webhooks

One rule covers both flows in this loop: a notification is a trigger, never the proof.

**From Mollie.** Mollie posts only the payment `id` (form-encoded) to the `webhookUrl` you set on creation; fetch the payment yourself and act on its state. Respond `200 OK` immediately, before processing. Mollie retries failed deliveries ten times over about 26 hours.

**From Attesso.** Attesso delivers signed lifecycle notifications: mandate approvals and revocations, authorization outcomes, and the rest of the journal. Register an endpoint with [Webhooks](../guides/webhooks), verify every delivery, then fetch current state and the signed evidence bundle.

## Reconcile a lost response

* Retry the identical create call with the same `Idempotency-Key`: Mollie returns the original response (`Idempotent-Replayed: true` header); a `409 Conflict` means the first request is still in flight; a reused key with different bytes is a `400`.
* Keys survive for one hour; after that the same key is a new request.
* Find the payment by listing payments and matching your `metadata` values, or retrieve it directly by its `tr_` id.

The full decision table is in [Reconcile authorization outcomes](../guides/reconcile-authorizations).

## Test mode

Verified against Mollie test mode (Oct 2026):

* Selecting **paid** in the test checkout completes the payment and returns the payer to `redirectUrl`.
* To exercise a **failed** payment, pin `"method": "creditcard"` at creation and use a documented magic amount from [Mollie's testing reference](https://docs.mollie.com/reference/testing), for example `1007.00` -> `insufficient_funds`, then select **Failed** on the status screen. A payment created without a pinned method bounces non-paid selections back to method selection and stays `open`; open test payments are not cancelable via the API (`DELETE /payments/{id}` -> 422).
* On paid test payments, Mollie adds a `changePaymentState` link for testing refunds and chargebacks.

## Rehearsal

[mollie-e2e.mjs](https://raw.githubusercontent.com/douwe-kramer/docs/main/examples/mollie-e2e.mjs) drives the full loop against this recipe: Mandate create, passkey approval, an over-cap DENY, an in-cap ALLOW executed as a Mollie test payment, then commit (paid) or cancel (failed), with the signed evidence verified locally. Every create is replayed under its idempotency key first, and the replay must return the same resource. Settle each printed checkout URL in a browser; pass `MOLLIE_WEBHOOK_URL` (a public tunnel URL ending in `/mollie-webhook`) to exercise real webhook delivery, or leave it unset to settle by polling.

```zsh theme={"theme":"github-light"}
curl -O https://raw.githubusercontent.com/douwe-kramer/docs/main/examples/mollie-e2e.mjs
MOLLIE_API_KEY=test_... node mollie-e2e.mjs
```

[mollie-e2e-drive.mjs](https://raw.githubusercontent.com/douwe-kramer/docs/main/examples/mollie-e2e-drive.mjs) wraps the harness with Playwright: a virtual passkey for the approval, scripted checkout walks. `node mollie-e2e-drive.mjs --runs 3` completes three full loops without a human at the keyboard. It needs Playwright installed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.