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

# Payment lifecycle

> Payment statuses, the client_secret, and how next_action drives 3DS, redirects and wallet steps.

A **Payment** is one attempt to collect an amount from a shopper. Your server creates it, the shopper's browser confirms it, and the API routes it to a processor according to the store's payment policy. If the processor or the issuer declines, the API can move it to a backup processor before it concludes.

## Statuses

| Status | Meaning | What you do |
| - | - | - |
| `requires_payment_method` | Created without a payment method. | Confirm it with a payment method. |
| `requires_confirmation` | Has a payment method, not confirmed yet. | Confirm it. |
| `processing` | Confirmed; the API is working with the processor. | Wait. Read it again, or let the browser client poll. |
| `requires_action` | The shopper must do something: 3DS, a redirect, a wallet step. | Perform `next_action` (the browser client does it for you). |
| `requires_capture` | Authorized, not captured (`capture_method: "manual"`). | [Capture](https://docs.tagada.io/payments-api/reference/payments/capture-an-authorized-payment.md) or cancel it. |
| `succeeded` | Final. The money is collected. | Fulfil the order. |
| `failed` | Final. Declined or errored. `last_outcome` says why. | Show the reason; offer another payment. |
| `canceled` | Final. Canceled before it concluded. | Nothing. |

`succeeded`, `failed` and `canceled` are final. Refunds and disputes later change the amounts (`amount_refunded`, `amount_disputed`), not the status.

```mermaid theme={null}
flowchart LR
  A[requires_payment_method] --> C[processing]
  B[requires_confirmation] --> C
  C --> D[requires_action]
  D --> C
  C --> E[requires_capture]
  E --> S[succeeded]
  C --> S
  C --> F[failed]
  E --> X[canceled]
```

`status` is an open enum: handle a value you do not know as "not final yet".

## Create on the server

```bash theme={null}
curl https://api.tagada.io/v1/payments \
  -H "Authorization: Bearer tsk_test_..." \
  -H "Payments-Version: 2026-11-01" \
  -H "Idempotency-Key: 4c1e9a7b-2f3d-4e5a-9b8c-7d6e5f4a3b2c" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1999,
    "currency": "USD",
    "store": "store_0123456789ab",
    "purpose": "sale",
    "return_url": "https://shop.example.com/checkout/return"
  }'
```

`amount` and `currency` are required. `amount` is an integer in the currency minor unit. Useful optional fields:

| Field | Description |
| - | - |
| `store` | The store the payment belongs to. It must be on the Payments API, otherwise `409 store_not_on_payments_api`. |
| `customer` | A `cus_` id, to attach the payment and save cards. |
| `capture_method` | `automatic`, or `manual` to authorize now and capture later. When you omit it, the store's payment policy decides. |
| `purpose` | `sale`, `signup`, `upsell`, `renewal` or `verification`. |
| `future_usage` with `save_payment_method` | Save the card for later `on_session` or `off_session` payments. |
| `return_url` | Where a redirect brings the shopper back. |
| `metadata` | Your own string key-value pairs (values up to 500 characters). |
| `merchant_reference` | Your order id. |

The full schema is in [Create a payment](https://docs.tagada.io/payments-api/reference/payments/create-a-payment.md).

## The client secret

The create response carries `client_secret`, shaped `pay_<id>_secret_<random>`:

* It is returned **only** to the server that created the payment, and on an idempotent replay of that create. Every other read returns `client_secret: null`.
* Pass it to the page that collects the payment, for example in your server-rendered HTML or an authenticated JSON response. It lets a browser holding your publishable key confirm and read **this one payment**, nothing else.
* Never put it in a URL, never log it, never store it beyond the checkout.

## Confirm in the browser

The browser client confirms the payment and performs every next action until the payment is final or the shopper leaves for a redirect:

```typescript theme={null}
import { loadPayments } from '@tagadapay/core-js/payments-v3';

const tp = await loadPayments({ publishableKey: 'tpk_test_...' });
const payment = await tp.confirmPayment({
  clientSecret,
  paymentMethod: 'inst_0123456789ab',
  returnUrl: 'https://shop.example.com/checkout/return',
});

if (payment.status === 'succeeded' || payment.status === 'requires_capture') {
  // Show the confirmation. Your server still checks the payment before fulfilling.
}
```

See the [browser client guide](https://docs.tagada.io/payments-api/browser-client.md) for card fields, wallets and redirect returns.

> [!WARNING]
>
> A resolved `confirmPayment` does not mean success. It returns the payment as it is: it can still be `processing` or `requires_action` after a polling limit, or `failed`. Fulfil orders from your server, after reading the payment with your secret key.

## next\_action

When a payment is `requires_action`, `next_action` describes the step. It is `null` otherwise.

| `type` | Fields | Step | Browser client |
| - | - | - | - |
| `three_ds` | `step: "device_data"`, `render: "emv"`, `method_url`, `method_data` | 3DS device fingerprint in a hidden frame. | Handled |
| `three_ds` | `step: "challenge"`, `render: "emv"`, `acs_url`, `creq`, `window_size` | 3DS challenge in a modal frame. | Handled |
| `three_ds` | `render: "provider_sdk"`, `provider`, `data` | 3DS run by a provider's own SDK (Stripe, Basis Theory). | Handled |
| `redirect_to_url` | `url`, `return_url` | Send the shopper to `url` (bank page, PayPal, Klarna). They come back to `return_url`. | Handled |
| `wallet_confirmation` | `wallet`, `data` | Confirm a wallet payment with the processor's SDK. | Handled for Stripe |
| `widget` | `provider`, `data` | A provider widget. | Not handled yet |

The browser reports each finished 3DS step to `POST /v1/payments/{id}/authentication`. The API reads the result from the provider or processor itself; nothing the browser sends is taken as the result.

`next_action.type` is an open enum. The browser client raises `UnsupportedNextActionError` for a type it does not know.

## After a redirect

When the shopper returns to your `return_url`, call `handleReturn` with the same `clientSecret`. It reports the return to the API and waits for the answer:

```typescript theme={null}
const payment = await tp.handleReturn({ clientSecret });
```

## Capture, cancel, retry

| Call | When |
| - | - |
| `POST /v1/payments/{id}/capture` | Capture a `requires_capture` payment. |
| `POST /v1/payments/{id}/cancel` | Cancel a payment that has not concluded, for example an uncaptured authorization. Otherwise `409 payment_not_cancelable`. |
| `POST /v1/payments/{id}/retry` | After a decline: creates a **new** payment with its own `client_secret`, linked by `retry_of`. The failed payment stays failed. |
| `GET /v1/payments/{id}/timeline` | Read what happened, step by step. |

Refunds are their own resource: `POST /v1/refunds`.


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