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

# Payments

> Charge a payment method, read the payment back, and list what you have taken.

A **payment** is one charge of a [payment method](/payment-methods) you already created: a
`pay_test_…` / `pay_live_…` object your server creates with a secret key. A card is captured in the
same answer; a bank debit is `processing` until it settles, when the processor sends it to the bank in
its next batch. The
charge runs through the same engine as the rest of Payra, so it shows up in the dashboard, reconciles
and [refunds](/refunds) like any other payment.

<Steps>
  <Step title="Charge the payment method">
    ```bash theme={null}
    curl -X POST https://api-dashboard.payra.com/v1/payments \
      -H "Authorization: Bearer sk_test_..." \
      -H "Idempotency-Key: 8f2c1d4b-7a9e-4c3f-b5d6-1e2f3a4b5c6d" \
      -H "Content-Type: application/json" \
      -d '{
        "payment_method": "pm_test_9kQ2mL7Hs1pXv4cR8tWzAbCd",
        "amount": 12550,
        "currency": "USD",
        "reference": "INV-1042"
      }'
    ```

    ```json Response theme={null}
    {
      "object": "payment",
      "id": "pay_test_7Hs2kQ9mL1pXv4cR8tWzAbCd",
      "status": "succeeded",
      "amount": 12550,
      "amount_refunded": 0,
      "currency": "USD",
      "payment_method": "pm_test_9kQ2mL7Hs1pXv4cR8tWzAbCd",
      "payment_method_type": "card",
      "customer": "019e...",
      "description": null,
      "reference": "INV-1042",
      "card": { "brand": "visa", "last4": "4242", "exp_month": 12, "exp_year": 2030 },
      "us_bank_account": null,
      "failure": null,
      "next_action": null,
      "livemode": false,
      "created_at": "2026-09-18T20:00:00.000Z",
      "succeeded_at": "2026-09-18T20:00:01.312Z"
    }
    ```

    `amount` is in the smallest unit of the currency, so 12550 is \$125.50, and it is charged exactly:
    the API adds no fee or surcharge. `amount_refunded` is how much of it has since gone back
    through [refunds](/refunds), wherever they were made. `currency` is `USD` or `CAD`; charge `CAD`
    only on a workspace Payra has set up to take Canadian dollars. `description` and `reference` are
    optional, the second being your own order or invoice number: it is stored as the payment's order
    number and shown in the dashboard, and it does not apply the payment to a Payra invoice. When the
    method is saved on a customer and `reference` matches one of that customer's prepayment orders
    (orders synced from your ERP through Payra Connect), the charge counts against that order:
    `amount` may not exceed what is still due on it, other pending charges included
    (`400 parameter_invalid` on `amount`), and a succeeded charge reduces what is due. `customer`, if you send it, must be the customer the method is saved on.
  </Step>

  <Step title="Read it back while it settles">
    ```bash theme={null}
    curl https://api-dashboard.payra.com/v1/payments/pay_test_7Hs2kQ9mL1pXv4cR8tWzAbCd \
      -H "Authorization: Bearer sk_test_..."
    ```

    A declined payment stays readable at the same id: the create answered `402`, and this is where
    you see why.
  </Step>
</Steps>

An [`Idempotency-Key`](/idempotency) is required. The same key with the same body answers the same
payment, including after a crash on our side, because the payment itself remembers the key. The same
key with a different body is `400 idempotency_key_reused`; after 24 hours only `payment_method`,
`amount` and `currency` are compared.

Charging a saved payment method emails the customer Payra's payment receipt once the charge succeeds
(for a bank debit, when the processor accepts the debit, before it settles), when the customer has an
email address whose email notifications are on, the workspace's **Email payment receipts to
customers** setting is on, and its notifications are not paused. A one-time method has no customer, so
its payment gets no receipt.

A one-time payment method is spent by its first charge, approved or declined, and reads `consumed`
afterwards. Only a request refused before it reaches the processor (a `400` on `amount`, `currency` or
`customer`, `payment_provider_not_configured`, or a reused key) leaves it `active`. A saved one can be charged again.

## Bank debits

A bank account is charged for what its holder authorized. A one-time account takes exactly the
`amount` and `currency` its [session](/payment-method-sessions) named, else `400 parameter_invalid`
on `amount`; a saved one takes the amount you send. `currency` is `USD`. The answer is a `processing`
payment with `payment_method_type: "us_bank_account"` and a `us_bank_account` block (`last4`,
`account_type`, `account_holder_type`), `card` being `null`:

```json theme={null}
{
  "object": "payment",
  "id": "pay_test_aPufFqYjiGjFAFEnQk1gWVVW",
  "status": "processing",
  "amount": 21375,
  "amount_refunded": 0,
  "currency": "USD",
  "payment_method": "pm_test_2bL7Hs9kQ1pXv4cR8tWzAbCd",
  "payment_method_type": "us_bank_account",
  "customer": null,
  "description": null,
  "reference": "INV-1043",
  "card": null,
  "us_bank_account": { "last4": "6789", "account_type": "checking", "account_holder_type": "individual" },
  "failure": null,
  "next_action": null,
  "livemode": false,
  "created_at": "2026-09-23T18:10:00.000Z",
  "succeeded_at": null
}
```

It becomes `succeeded` when it settles, once the processor's next batch sends it to the bank, and
`succeeded_at` is set then; the bank can instead refuse the debit, which turns it `returned` with
`failure.code: "ach_returned"`, or the processor can reject it outright, which turns it `failed`.
Listen for [`payment.succeeded`, `payment.returned` and `payment.failed`](/webhooks) rather than
polling, and do not ship on `processing`. Every debit carries the authorization evidence
Payra recorded when the holder accepted it, so a dispute can be answered from Payra's records.

## Status

`status` is Payra's own lifecycle. It is never a string from the processor.

| `status`          | Meaning                                                                                                                                                                                                                                                                                         |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `processing`      | Sent to the processor, no answer yet; and, on a bank debit, the normal state until it settles, when the processor sends it to the bank in its next batch. Poll, or wait for the [`payment.succeeded` / `payment.failed` / `payment.returned` webhook](/webhooks). It never times out on its own |
| `requires_action` | The processor asked for a step a server-side charge cannot take (3-D Secure); `next_action` carries the redirect. Rare, since no return address is sent                                                                                                                                         |
| `authorized`      | Approved but not captured. Not expected here, since every charge is a sale                                                                                                                                                                                                                      |
| `succeeded`       | A card: captured. A bank debit: settled, sent to the bank in the processor's batch; not final, since the bank can still return it (`returned`). It stays `succeeded` after a [refund](/refunds): read `amount_refunded` for what went back                                                      |
| `failed`          | The processor refused, or the charge never reached it. See `failure`                                                                                                                                                                                                                            |
| `canceled`        | Voided before it settled                                                                                                                                                                                                                                                                        |
| `returned`        | The money came back: a bank return (also from `processing`, when the bank refuses the debit) or a chargeback. See `failure`                                                                                                                                                                     |

## Why a payment failed

`failure` is set when `status` is `failed` or `returned`, and `null` otherwise. It carries a `code`
from a fixed vocabulary and a `message` written by Payra. The processor's own wording is never
relayed, so you can show or log `message` safely and branch on `code`.

| `failure.code`           | Meaning                                                                                                                                                                                 |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `card_declined`          | The issuer refused the card, or the processor refused the debit, including refusals Payra cannot classify further                                                                       |
| `insufficient_funds`     | The card has no money for the amount                                                                                                                                                    |
| `authentication_failed`  | The cardholder could not be authenticated, or the authentication step was not completed in time                                                                                         |
| `invalid_payment_method` | The card or bank account details were not accepted (card number, expiry, security code; routing or account number)                                                                      |
| `duplicate_transaction`  | The processor treated the charge as a duplicate of a recent one                                                                                                                         |
| `chargeback`             | The money came back on a card after it succeeded, normally a dispute                                                                                                                    |
| `ach_returned`           | The bank returned the debit: closed account, insufficient funds, or the holder disputed the authorization. The return reason, when the processor reports one, is shown in the dashboard |
| `processing_error`       | A fault on the way to the processor or in the request, not a decision about the card. Worth retrying with a new `Idempotency-Key`; quote the payment id to support if it repeats        |

## Declines

A refusal is an answer, not an outage: the create responds `402 card_declined` and leaves the
payment behind for you to read.

```json theme={null}
{
  "error": {
    "type": "card_error",
    "code": "card_declined",
    "message": "The card has insufficient funds.",
    "decline_code": "insufficient_funds",
    "payment": "pay_test_7Hs2kQ9mL1pXv4cR8tWzAbCd",
    "request_id": "8f0c2b1e-4a7d-4c1e-9b8a-2f6d3e5a1c90"
  }
}
```

`code` is `card_declined` for every refusal, so `decline_code` is what you branch on. It uses the
same vocabulary as `failure.code`, which means it can be `processing_error`: that one is not the
issuer refusing the card, and it is worth retrying with a new `Idempotency-Key` (the same key
replays this `402`). A saved method can simply be charged again; a one-time method was spent by the
attempt, so collect the card again with a new session. A
charge the processor refuses, or that Payra could not send at all (the workspace has no processor
account for that currency, say), answers this same `402`, never a `500`: the failed payment exists,
reads back and lists. When Payra cannot tell whether the processor received the charge (the request
timed out, or the connection failed), the create answers `201` with `status: "processing"` instead,
and a later [webhook](/webhooks) says how it ended.

## Listing payments

`GET /payments` answers the payments your keys have made, newest first, a page at a time. It takes
`limit` and `starting_after`, and works the same as every other list; see
[pagination](/pagination) for walking the pages.

```bash theme={null}
curl "https://api-dashboard.payra.com/v1/payments?limit=20" \
  -H "Authorization: Bearer sk_test_..."
```

```json Response (abbreviated) theme={null}
{
  "object": "list",
  "data": [{ "object": "payment", "id": "pay_test_7Hs2kQ9mL1pXv4cR8tWzAbCd", "status": "succeeded" }],
  "has_more": true
}
```

The list only ever holds your own workspace's payments, in the environment of the key you sent. The
list takes no filters; when you already know an id, read it with retrieve.

## Errors

| Error                                   | What happened                                                                                                                                                                                       |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `parameter_invalid` on `payment_method` | Unknown method, or one that belongs to another workspace or environment                                                                                                                             |
| `parameter_invalid` on `customer`       | The customer sent is not the one the method is saved on, or is no longer available                                                                                                                  |
| `parameter_invalid` on `amount`         | `reference` names a prepayment order of the method's customer with less than `amount` still due, or a one-time bank account is being debited for an amount other than the one its holder authorized |
| `parameter_invalid` on `currency`       | A saved card is charged in a currency other than the one it was saved in, or a saved bank account in a currency other than `USD`                                                                    |
| `parameter_invalid` on `starting_after` | The cursor is not a payment of this workspace and environment (list)                                                                                                                                |
| `idempotency_key_invalid`               | The `Idempotency-Key` is empty or longer than 255 characters                                                                                                                                        |
| `payment_method_unusable`               | The method was already spent, expired, or detached from its customer                                                                                                                                |
| `idempotency_key_required`              | The `Idempotency-Key` header is missing                                                                                                                                                             |
| `idempotency_key_reused`                | The key was already used for a different request (another body, or another endpoint), whatever that request answered                                                                                |
| `idempotency_key_in_flight` (409)       | The same key's first request is still running. Retry shortly                                                                                                                                        |
| `payment_provider_not_configured`       | Card payments, or ACH debits, are not set up for the workspace                                                                                                                                      |
| `conflict` (409)                        | Another request is charging the same thing right now. The answer is stored under your key: list or retrieve the payment first, then retry with a new `Idempotency-Key`                              |
| `card_declined` (402, `card_error`)     | The processor refused. See Declines above                                                                                                                                                           |
| `resource_missing` (404)                | The payment id is unknown, or belongs to another workspace or environment                                                                                                                           |
