# Pay an invoice by card

> The invoices.hosted.pay operation: POST /api/v1/public/invoices/{invoiceId}/pay in the REST API and the invoices_hosted_pay tool in the MCP server.

Starts a card payment of an open invoice's amount due at the brand's processor: a one-off payment (no card is
saved), one payment intent per invoice and amount. `expectedAmount` is the amount due the customer saw; if it
changed meanwhile, the payment is refused.

- **fake processor:** the payment is confirmed here with the chosen test card (`paymentMethod`): `succeeded`,
  `processing`, `requires_action` (confirm again to pass the fake 3-D Secure challenge) or a decline;
- **Stripe:** the answer is `client_confirm` with the Payment Element's `clientSecret`; the browser confirms;
- **Mollie:** the answer is `redirect` with `redirectUrl`, Mollie's payment page; the customer comes back to this
  page (`processor_return=1`), which waits for the webhook. A payment that failed or expired on Mollie's page is
  replaced by a new one when the customer pays again.

The invoice is marked paid when the processor's webhook confirms the payment (`invoice.paid`; a past-due or
unpaid subscription recovers): read the invoice again with `invoices.hosted.get`. A declined card on this page
doesn't count as a dunning attempt. Public: the page calls it with the invoice link's `token`.

## At a glance

- **REST:** `POST /api/v1/public/invoices/{invoiceId}/pay`
- **MCP tool:** `invoices_hosted_pay`
- **Operation ID:** `invoices.hosted.pay`
- **Scope:** Brand
- **Permission:** `invoices:write`
- **Risk tier:** `financial`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** None (a public endpoint, rate-limited per IP address)
- **Since:** M5

## Request

### Path parameters

| Field       | Type       | Description |
| ----------- | ---------- | ----------- |
| `invoiceId` | `inv_…` ID |             |

### Body

A JSON object (`Content-Type: application/json`):

| Field                     | Type   | Required | Description                                                                  |
| ------------------------- | ------ | -------- | ---------------------------------------------------------------------------- |
| `token`                   | string | No       | The invoice link's token (needed without an API key).                        |
| `expectedAmount`          | object | Yes      | The amount due the customer saw; a different amount due refuses the payment. |
| `expectedAmount.amount`   | string | Yes      | Integer micros as a string.                                                  |
| `expectedAmount.currency` | string | Yes      |                                                                              |
| `paymentMethod`           | string | No       | Fake processor only: the test card, e.g. `pm_fake_visa`.                     |

## Response

`200 OK` with a JSON object:

| Field            | Type                                                                              | Description                                                                                                                                                                                                                                                                                                                             |
| ---------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`         | `"hosted_invoice_payment"`                                                        |                                                                                                                                                                                                                                                                                                                                         |
| `invoiceId`      | `inv_…` ID                                                                        |                                                                                                                                                                                                                                                                                                                                         |
| `outcome`        | one of `succeeded`, `processing`, `requires_action`, `client_confirm`, `redirect` | `succeeded` (the invoice is marked paid when the processor's webhook arrives), `processing`, `requires_action` (confirm again: a fake 3-D Secure challenge), `client_confirm` (Stripe: confirm in the Payment Element with `clientSecret`) or `redirect` (Mollie: send the buyer to `redirectUrl`; they come back to the invoice page). |
| `amount`         | money                                                                             |                                                                                                                                                                                                                                                                                                                                         |
| `clientSecret`   | string or null                                                                    |                                                                                                                                                                                                                                                                                                                                         |
| `publishableKey` | string or null                                                                    |                                                                                                                                                                                                                                                                                                                                         |
| `redirectUrl`    | string or null                                                                    |                                                                                                                                                                                                                                                                                                                                         |

## Examples

### Pay with a fake test card

```http
POST /api/v1/public/invoices/inv_01m4tx5nj8emwbyf7y9vcf47zn/pay
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "token": "WyJvcmdfMDEiLCJlbnRfMDEiLCJicmRfMDEiLDBd.c2lnbmF0dXJl",
  "expectedAmount": {
    "amount": "1200000000",
    "currency": "EUR"
  },
  "paymentMethod": "pm_fake_visa"
}
```

Response (`200`):

```json
{
  "object": "hosted_invoice_payment",
  "invoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
  "outcome": "succeeded",
  "amount": {
    "amount": "1200000000",
    "currency": "EUR"
  },
  "clientSecret": null,
  "publishableKey": null,
  "redirectUrl": null
}
```

## Errors

| Code                                                                                                       | HTTP status | When                                                                                                   | What to do                                                                                                                                                                                 |
| ---------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                     | 400         | A field is malformed or out of range.                                                                  | Fix the fields listed in `details.issues` and send the request again.                                                                                                                      |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                   | 404         | No issued invoice has this ID in the brand, or the link's token doesn't belong to it.                  | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                       |
| [`invoice_payment_unavailable`](https://ownrecord.io/docs/reference/errors.md#invoice_payment_unavailable) | 409         | The invoice is paid or voided, has nothing due, its amount due changed, or the brand has no processor. | The invoice is paid, voided or has nothing due, or the brand has no processor connection: check `details.reason`. Pay by bank transfer with the details on the invoice, or ask the seller. |
| [`processor_payment_declined`](https://ownrecord.io/docs/reference/errors.md#processor_payment_declined)   | 502         | The card was declined; try another card or a bank transfer.                                            | Ask the customer for another payment method; `details.decline_code` says why the card was declined.                                                                                        |
| [`processor_request_failed`](https://ownrecord.io/docs/reference/errors.md#processor_request_failed)       | 502         | The processor couldn't create or confirm the payment; try again.                                       | If `retryable` is true, retry with the same Idempotency-Key after a short wait; otherwise check `details.code`.                                                                            |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
