# Payments

Initialize one-time payments and redirect your customers to a secure checkout page to pay with cards, bank transfers, USSD, QR codes, and more.

***

Every product that moves money inward needs a starting point — a way to say "this customer owes this amount, let's collect it." On Tendar, that starting point is the Payment API. It lets you create a payment, redirect your customer to a hosted checkout page, and receive confirmation once the money lands.

If you've used Paystack's "Accept Payments" flow before, this will feel familiar. You initialize a transaction on your server, get back a checkout URL, send your customer there, and listen for a webhook (or verify manually) when they're done. The difference is that Tendar sits in front of multiple payment providers — Paystack, Flutterwave, and Stripe — so you get a single, unified API regardless of which provider is processing the charge behind the scenes.

The Payment API is designed for **one-time payments**: a customer pays a specific amount for a specific thing, and the transaction is done. It's not for recurring charges (that's what [Card Tokenization](https://docs.tendar.co/documentation/recollection/broken-reference), [Direct Debit](https://docs.tendar.co/documentation/recollection/broken-reference), and [Collect](https://docs.tendar.co/documentation/recollection/broken-reference) are for). Think of it as the simplest way to move money from a customer's pocket into your Tendar wallet.

## Before you begin

{% stepper %}
{% step %}

### An active Tendar account

with a valid API key. All requests require a Bearer token:

```http
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
```

{% endstep %}

{% step %}

### An active subscription

on your Tendar company account. Every payment endpoint checks your subscription status before processing.
{% endstep %}

{% step %}

### A registered user

payments are tied to users. The customer must already exist in your company's user directory on Tendar (you'll need their `user_id`).
{% endstep %}

{% step %}

### (Optional) A payment provider configured

if you want payments to go through your own Paystack, Flutterwave, or Stripe account (so the money lands in *your* provider account, not Tendar's), you need to set up your provider credentials on the Tendar dashboard. If you don't configure one, payments are processed through Tendar's default provider (Paystack), and the funds are deposited into your Tendar wallet.
{% endstep %}
{% endstepper %}

## How it works

The payment flow has three stages: **initialize**, **pay**, and **verify**. Here's how they fit together:

```
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   Your server    │     │   Tendar         │     │   Payment        │
│   initializes    │────▶│   creates the    │────▶│   provider       │
│   a payment      │     │   payment &      │     │   hosts the      │
│                  │     │   returns a      │     │   checkout page   │
│                  │     │   checkout URL   │     │                  │
└──────────────────┘     └──────────────────┘     └──────────────────┘
                                                           │
                                                  Customer completes
                                                  payment on the
                                                  checkout page
                                                           │
                         ┌──────────────────┐     ┌────────▼─────────┐
                         │   Tendar         │     │   Provider       │
                         │   verifies the   │◀────│   sends webhook  │
                         │   payment &      │     │   to Tendar      │
                         │   updates status │     │                  │
                         └────────┬─────────┘     └──────────────────┘
                                  │
                         ┌────────▼─────────┐
                         │   Tendar sends   │
                         │   webhook to     │
                         │   your server    │
                         └──────────────────┘
```

Let's walk through each stage.

***

{% stepper %}
{% step %}

### Step 1: Initialize a payment

To start accepting a payment, send a `POST` request with the customer's details and the amount you want to collect.

**Endpoint**

```http
POST /api/v1/payment/initialize
```

**Request body**

```json
{
    "user_id": "usr-hnlfkizfoc",
    "email": "",
    "amount": 1000,
    "currency": "NGN",
    "reference": "",
    "narration": "Order #1234 — Premium Plan",
    "callback_url": "",
    "channels": ["card"],
    "checkout": {
        "success_url": "https://yourapp.com/payment/success",
        "cancel_url": "https://yourapp.com/payment/cancel"
    },
    "payment_service": "",
    "metadata": {
        "order_id": "ord-9823",
        "plan": "premium"
    }
}
```

#### What each field means

| Field                  | Type   | Required | Description                                                                                                                                                                                                                               |
| ---------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user_id`              | string | Yes\*    | The Tendar user ID of the customer. If provided, Tendar looks up their email automatically.                                                                                                                                               |
| `email`                | string | Yes\*    | The customer's email address. Required if `user_id` is not provided. If both are given, the user's email on file takes precedence.                                                                                                        |
| `amount`               | number | Yes      | The amount to charge, in the currency's major unit (e.g., `1000` means ₦1,000 or $10.00). Must be greater than 0.                                                                                                                         |
| `currency`             | string | Yes      | The currency for the payment. Supports `NGN` and `USD` (depending on your provider).                                                                                                                                                      |
| `reference`            | string | No       | A unique reference for this payment. If left empty, Tendar generates one (prefixed `pay-`). Useful for idempotency — if you retry a request with the same reference, Tendar returns the existing payment instead of creating a duplicate. |
| `narration`            | string | No       | A description of what the payment is for. This may appear on the customer's bank statement or the checkout page, depending on the provider.                                                                                               |
| `callback_url`         | string | No       | A URL where Tendar will POST a webhook event when the payment status changes. This is in addition to your global company webhook URL.                                                                                                     |
| `channels`             | array  | No       | The payment methods to show on the checkout page. Options include `"card"`, `"bank"`, `"ussd"`, `"qr"`, `"mobile_money"`, and `"bank_transfer"`. If omitted, all available channels are shown.                                            |
| `checkout.success_url` | string | Yes      | Where to redirect the customer after a successful payment.                                                                                                                                                                                |
| `checkout.cancel_url`  | string | No       | Where to redirect the customer if they abandon the checkout.                                                                                                                                                                              |
| `payment_service`      | string | No       | Which payment provider to use: `"paystack"`, `"flutterwave"`, or `"stripe"`. Leave empty to use your company's default (or Tendar's default, which is Paystack).                                                                          |
| `metadata`             | object | No       | Any custom key-value pairs you want to attach to the payment. These are stored with the payment and returned in webhooks and API responses.                                                                                               |

> **Note:** The `charge` field exists on the payment object but is **not allowed** when initializing a payment through the API. If you pass a non-zero `charge`, the request will be rejected. Charges are managed internally by Tendar.

#### What happens behind the scenes

{% stepper %}
{% step %}

### Validates the user

if you provided a `user_id`, Tendar looks up the user and pulls their email. If the user doesn't exist, you get a `404` error.
{% endstep %}

{% step %}

### Validates the input

checks that the amount is positive, the currency is supported, and the checkout URLs are present.
{% endstep %}

{% step %}

### Resolves the payment provider

determines whether to use your company's configured provider or Tendar's default. If you specified `payment_service`, Tendar uses that provider; otherwise, it falls back to your company's default payment service configuration.
{% endstep %}

{% step %}

### Initializes the checkout with the provider

sends the payment details to Paystack, Flutterwave, or Stripe, which creates a checkout session and returns a hosted checkout URL.
{% endstep %}

{% step %}

### Creates the payment record

stores the payment in Tendar's database with a `pending` status.
{% endstep %}

{% step %}

### Funds your wallet (pending)

if the payment is going through Tendar's provider (not your own), a pending wallet transaction is created so the funds can be credited once the payment succeeds.
{% endstep %}
{% endstepper %}

#### Response

**`200 OK`**

```json
{
    "data": {
        "created_at": "2024-07-03T21:54:18.246Z",
        "updated_at": "2024-07-03T21:54:18.246Z",
        "id": "6685c88a9b29e25a0cfa83e7",
        "user": "6685bb34af274480e2251d85",
        "user_id": "usr-hnlfkizfoc",
        "email": "john@example.com",
        "payment_service": "paystack",
        "amount": 1000,
        "fees": 0,
        "currency": "NGN",
        "reference": "pay-mtylgckvjj",
        "status": "pending",
        "narration": "Order #1234 — Premium Plan",
        "payment_response": null,
        "metadata": {
            "order_id": "ord-9823",
            "plan": "premium",
            "company": "",
            "event": "payment",
            "server": "recollection"
        },
        "service_ref": "pay-mtylgckvjj",
        "message": "",
        "checkout": {
            "success_url": "https://yourapp.com/payment/success",
            "cancel_url": "https://yourapp.com/payment/cancel",
            "url": "https://checkout.paystack.com/dh915o8y5t6fggf"
        },
        "channels": ["card"]
    },
    "error": false,
    "message": "Payment initialized successfully"
}
```

The key field in the response is **`checkout.url`** — this is the hosted checkout page where your customer will enter their payment details. Redirect them there.

Notice that `status` is `"pending"`. The payment exists, but no money has moved yet. The customer still needs to complete the checkout.
{% endstep %}

{% step %}

### Step 2: Customer completes payment

This step happens entirely outside your application. You redirect the customer to the `checkout.url`, and the payment provider takes over.

#### What the customer sees

The checkout experience depends on which provider is processing the payment and which channels you enabled:

* **Paystack** — The customer sees a Paystack-hosted checkout page where they can pay with card, bank transfer, USSD, QR code, or mobile money.
* **Flutterwave** — Similar hosted checkout with support for cards, bank transfers, mobile money, and more.
* **Stripe** — A Stripe Checkout page supporting cards, bank debits, and other payment methods depending on your Stripe configuration.

The customer selects their preferred payment method, enters the required details (card number, bank account, etc.), and completes the payment.

#### After payment

1. **They are redirected** to your `success_url` (or `cancel_url` if they abandoned the page).
2. **The provider sends a webhook to Tendar** notifying it that the payment has settled.
3. **Tendar verifies the payment** with the provider to confirm the amount, currency, and status.
4. **Tendar updates the payment record** — the status moves from `pending` to `success` (or `failed`).
5. **Tendar sends a webhook to you** — a `payment.success` or `payment.failed` event is delivered to your global webhook URL and, if provided, to the `callback_url` on the payment.

You don't need to call any API during this step. Just make sure your `success_url` shows the customer an appropriate message — something like "Payment received, thank you!" or "Processing your payment..." — and wait for the webhook to confirm the outcome.

> **Important:** Don't rely solely on the redirect to your `success_url` to confirm payment. A customer can close their browser before being redirected, or the redirect can fail for other reasons. Always use the webhook or the verify endpoint to confirm that the payment actually succeeded.
> {% endstep %}

{% step %}

### Step 3: Verify the payment

There are two ways to confirm that a payment succeeded: **webhooks** (recommended) and **manual verification**.

#### Option A: Listen for webhooks (recommended)

When the payment settles, Tendar sends a webhook event to your registered URL. The event looks like this:

```json
{
    "company_id": "64245949dbcc9f021a769e65",
    "event": "payment.success",
    "service": "recollection",
    "data": {
        "id": "6685d1a4f6078f4b8a3a7542",
        "user": "6685bb34af274480e2251d85",
        "user_id": "usr-hnlfkizfoc",
        "email": "john@example.com",
        "payment_service": "paystack",
        "amount": 1000,
        "fees": 15,
        "currency": "NGN",
        "reference": "pay-tnwaczzqsu",
        "status": "success",
        "narration": "Order #1234 — Premium Plan",
        "metadata": {
            "order_id": "ord-9823",
            "plan": "premium"
        },
        "paid_at": "2024-07-03T22:33:26Z",
        "payment_channel": "card"
    }
}
```

The `event` field tells you the outcome:

| Event              | What it means                                                                          |
| ------------------ | -------------------------------------------------------------------------------------- |
| `payment.success`  | The payment succeeded. The customer has been charged and the funds are in your wallet. |
| `payment.failed`   | The payment failed. The customer was not charged.                                      |
| `payment.refunded` | A previously successful payment has been refunded.                                     |

When you receive a `payment.success` event, you can safely fulfill the customer's order, activate their subscription, or do whatever your business logic requires.

If you provided a `callback_url` on the payment, the event is sent to **both** your global webhook URL and the payment-specific callback URL. This is useful when different parts of your system need to react to different payments.

#### Option B: Fetch the payment

If you prefer to check the payment status on demand — for example, when the customer is redirected to your `success_url` — you can fetch the payment by its ID or reference.

**Fetch by ID**

```http
GET /api/v1/payment/fetch/:id
```

**Fetch by reference**

```http
GET /api/v1/payment/fetch/reference/:ref
```

Both endpoints return the full payment object, including the current `status`, `amount`, `fees`, `paid_at`, `payment_channel`, and the raw `payment_response` from the provider.

**Example response**

```json
{
    "data": {
        "created_at": "2024-07-03T22:33:08.011Z",
        "updated_at": "2024-07-03T22:33:30.614Z",
        "id": "6685d1a4f6078f4b8a3a7542",
        "user": {
            "created_at": "2024-07-03T20:57:24.815Z",
            "email": "john@example.com",
            "first_name": "John",
            "id": "6685bb34af274480e2251d85",
            "last_name": "Doe",
            "phone": "+2348181765499",
            "user_id": "usr-hnlfkizfoc"
        },
        "user_id": "usr-hnlfkizfoc",
        "email": "john@example.com",
        "payment_service": "paystack",
        "amount": 1000,
        "fees": 15,
        "currency": "NGN",
        "reference": "pay-tnwaczzqsu",
        "status": "success",
        "narration": "Order #1234 — Premium Plan",
        "metadata": {
            "order_id": "ord-9823",
            "plan": "premium"
        },
        "service_ref": "pay-tnwaczzqsu",
        "message": "",
        "checkout": {
            "success_url": "https://yourapp.com/payment/success",
            "cancel_url": "https://yourapp.com/payment/cancel",
            "url": "https://checkout.paystack.com/zhro5pjfi8ziz6l"
        },
        "channels": ["card"],
        "paid_at": "2024-07-03T22:33:26Z",
        "payment_channel": "card"
    },
    "error": false,
    "message": "Payment fetched successfully"
}
```

Check the `status` field. If it's `"success"`, the payment is confirmed. If it's still `"pending"`, the customer hasn't completed the checkout yet (or the webhook hasn't been processed).

> **Tip:** Use the `reference` field for lookups when possible. References are stable, unique identifiers that you control. The `id` is Tendar's internal identifier and is useful when you've stored it from a previous response.
> {% endstep %}
> {% endstepper %}

***

## The payment object

Here's the complete breakdown of every field on a payment:

| Field              | Type          | Description                                                                                                               |
| ------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `id`               | string        | Tendar's internal ID for the payment.                                                                                     |
| `user`             | object/string | The user who made the payment. Populated as a full object when fetching, or as an ID string in creation responses.        |
| `user_id`          | string        | The Tendar user ID (e.g., `usr-hnlfkizfoc`).                                                                              |
| `email`            | string        | The email address used for the payment.                                                                                   |
| `payment_service`  | string        | Which provider processed the payment: `paystack`, `flutterwave`, or `stripe`.                                             |
| `amount`           | number        | The payment amount in the currency's major unit.                                                                          |
| `fees`             | number        | Transaction fees charged by the provider.                                                                                 |
| `currency`         | string        | The currency (`NGN`, `USD`, etc.).                                                                                        |
| `reference`        | string        | The unique reference for this payment (e.g., `pay-mtylgckvjj`).                                                           |
| `status`           | string        | The payment status: `pending`, `success`, `failed`, or `refunded`.                                                        |
| `narration`        | string        | A description of what the payment is for.                                                                                 |
| `payment_response` | string        | The raw response from the payment provider (JSON-stringified). Useful for debugging.                                      |
| `metadata`         | object        | Custom key-value pairs you attached to the payment.                                                                       |
| `service_ref`      | string        | The provider's reference for the transaction. Usually matches `reference`.                                                |
| `message`          | string        | A status message, if any (e.g., provider error details).                                                                  |
| `callback_url`     | string        | The payment-specific webhook URL, if provided.                                                                            |
| `checkout`         | object        | The checkout URLs: `success_url`, `cancel_url`, and the provider-generated `url`.                                         |
| `channels`         | array         | The payment channels that were available on the checkout page.                                                            |
| `paid_at`          | datetime      | When the payment was successfully completed. `null` for pending or failed payments.                                       |
| `payment_channel`  | string        | The actual payment method used by the customer (e.g., `card`, `bank_transfer`, `ussd`). Empty until the payment succeeds. |

***

## Payment statuses

A payment moves through a simple lifecycle:

| Status     | What it means                                                                                                       |
| ---------- | ------------------------------------------------------------------------------------------------------------------- |
| `pending`  | The payment has been initialized but the customer hasn't completed the checkout yet.                                |
| `success`  | The customer paid, the provider confirmed the charge, and the funds have been credited.                             |
| `failed`   | The payment attempt failed — the card was declined, the transfer timed out, or the customer abandoned the checkout. |
| `refunded` | A previously successful payment has been reversed. The funds have been returned to the customer.                    |

```
    ┌─────────┐
    │ pending │
    └────┬────┘
         │
    Customer completes
    checkout (or doesn't)
         │
    ┌────▼────┐       ┌──────────┐
    │ success │──────▶│ refunded │
    └─────────┘       └──────────┘
         │
    ┌────▼────┐
    │ failed  │
    └─────────┘
```

Most payments will land in `success` or stay in `pending` (if the customer never completes the checkout). Failed payments typically result from declined cards or abandoned sessions. Refunded payments are triggered explicitly by you through the refund flow.

***

## Listing payments

To see all payments for your company, use the list endpoint:

**Endpoint**

```http
GET /api/v1/payment/list
```

**Query parameters**

| Parameter         | Description                                                |
| ----------------- | ---------------------------------------------------------- |
| `page`            | Page number (default: 1).                                  |
| `limit`           | Results per page (default: 20).                            |
| `status`          | Filter by status (e.g., `success`, `pending`, `failed`).   |
| `user_id`         | Filter by user ID.                                         |
| `provider`        | Filter by payment provider.                                |
| `payment_channel` | Filter by payment channel (e.g., `card`, `bank_transfer`). |
| `created_at`      | Filter by creation date.                                   |
| `paid_at`         | Filter by payment date.                                    |
| `sort-created_at` | Sort by creation date (`asc` or `desc`).                   |
| `sort-updated_at` | Sort by last update (`asc` or `desc`).                     |

**Example response**

```json
{
    "data": {
        "total": 512,
        "page": 1,
        "per_page": 20,
        "prev": 0,
        "next": 2,
        "total_page": 26,
        "docs": [
            {
                "id": "6685d1a4f6078f4b8a3a7542",
                "user": {
                    "first_name": "John",
                    "last_name": "Doe",
                    "email": "john@example.com",
                    "user_id": "usr-hnlfkizfoc"
                },
                "user_id": "usr-hnlfkizfoc",
                "email": "john@example.com",
                "payment_service": "paystack",
                "amount": 1000,
                "fees": 15,
                "currency": "NGN",
                "reference": "pay-tnwaczzqsu",
                "status": "success",
                "narration": "Order #1234 — Premium Plan",
                "paid_at": "2024-07-03T22:33:26Z",
                "payment_channel": "card"
            }
        ]
    },
    "error": false,
    "message": "Payment listed successfully"
}
```

The list endpoint returns payments without the `payment_response` field to keep the response size manageable. Use the fetch endpoint if you need the full provider response for a specific payment.

***

## Choosing a payment provider

Tendar supports three payment providers. Which one gets used depends on your configuration:

| Provider        | Currencies                         | Payment methods                             | When to use                                                                     |
| --------------- | ---------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------- |
| **Paystack**    | NGN, GHS, ZAR, USD                 | Card, bank transfer, USSD, QR, mobile money | Default choice for Nigerian businesses. Broadest local payment method coverage. |
| **Flutterwave** | NGN, USD, GHS, KES, and more       | Card, bank transfer, mobile money, USSD     | When you need multi-country support or specific Flutterwave features.           |
| **Stripe**      | USD, EUR, GBP, and 135+ currencies | Card, bank debit, and more                  | When you're primarily serving international customers or already use Stripe.    |

### How provider selection works

1. If you pass a `payment_service` value in your request (e.g., `"flutterwave"`), that provider is used.
2. If you leave `payment_service` empty and your company has configured a default payment provider on the Tendar dashboard, that provider is used.
3. If neither is set, Tendar falls back to **Paystack**.

### Using your own provider credentials vs. Tendar's

There's an important distinction in how funds flow depending on your setup:

* **Using your own credentials** (configured on the Tendar dashboard): The payment is processed through your Paystack/Flutterwave/Stripe account. Funds land in *your* provider account directly. Tendar records the transaction but doesn't touch the money.
* **Using Tendar's default credentials** (no provider configured): The payment is processed through Tendar's account. When the payment succeeds, the funds are deposited into your **Tendar wallet**. You can then withdraw from your wallet to your bank account.

For most businesses, configuring your own provider credentials is the recommended approach — you get the funds faster and have full control over your provider dashboard.

***

## Controlling payment channels

The `channels` array lets you control which payment methods appear on the checkout page. This is useful when you want to restrict how customers can pay.

| Channel         | What it is                                                     |
| --------------- | -------------------------------------------------------------- |
| `card`          | Credit or debit card.                                          |
| `bank`          | Online bank payment (redirects to the customer's bank portal). |
| `bank_transfer` | Manual bank transfer to a generated account number.            |
| `ussd`          | Payment via USSD code (Nigeria).                               |
| `qr`            | Scan-to-pay QR code.                                           |
| `mobile_money`  | Mobile money (Ghana, Kenya, etc.).                             |

**Examples:**

* Accept only card payments: `"channels": ["card"]`
* Accept card and bank transfer: `"channels": ["card", "bank_transfer"]`
* Accept everything (default): omit the `channels` field entirely, or pass all options.

Not all channels are available on all providers. Paystack has the broadest channel support for Nigerian payments. Stripe primarily supports cards and bank debits. Flutterwave supports cards, bank transfers, mobile money, and USSD.

***

## Using metadata

The `metadata` field is a flexible key-value store that lets you attach any custom data to a payment. This data is stored with the payment, included in webhook payloads, and returned in all API responses.

Common use cases:

* **Tracking order IDs**: `"metadata": { "order_id": "ord-9823" }`
* **Recording which product was purchased**: `"metadata": { "product": "premium_plan", "billing_cycle": "monthly" }`
* **Internal routing**: `"metadata": { "team": "sales", "campaign": "q4_promo" }`

Tendar adds a few internal metadata fields (`company`, `server`, `event`) for its own tracking. These don't interfere with your custom data.

***

## Using references for idempotency

Every payment gets a unique `reference`. If you don't provide one, Tendar generates one with a `pay-` prefix (e.g., `pay-mtylgckvjj`). If you *do* provide one, Tendar uses it as-is.

References serve as **idempotency keys**. If you send two requests with the same reference, the second request returns the existing payment instead of creating a duplicate. This is critical for handling network failures — if your request times out and you're not sure whether the payment was created, just retry with the same reference. You'll either get back the existing payment or create a new one, but you'll never accidentally charge the customer twice.

**Best practice:** Generate a unique reference on your side (e.g., based on the order ID) and pass it with every initialization request. This gives you full control over idempotency and makes it easy to look up payments later.

***

## What about the wallet?

When a payment is processed through Tendar's provider (i.e., you haven't configured your own provider credentials), the funds are deposited into your **Tendar wallet** upon successful payment. Here's what that means in practice:

1. When you initialize a payment, Tendar creates a pending wallet transaction.
2. When the payment succeeds, the wallet transaction is finalized and your wallet balance increases by the payment amount (minus any provider fees).
3. When a payment fails, the pending wallet transaction is cleaned up.
4. When a payment is refunded, the wallet transaction is reversed.

If you're using your own provider credentials, the funds go directly to your provider account and the wallet is not involved.

***

## Refunding a payment

Sometimes you need to return money to a customer — a cancelled order, a billing mistake, or a goodwill gesture. The Payment API doesn't expose a direct HTTP refund endpoint, but refunds are processed internally through the gRPC interface (used by the Tendar API gateway and internal services).

When a refund is processed:

1. Tendar sends a refund request to the original payment provider (Paystack, Flutterwave, or Stripe).
2. The payment status is updated to `refunded`.
3. If the payment was wallet-backed, the wallet transaction is reversed (your wallet balance decreases).
4. A `payment.refunded` webhook event is sent.

Refunds can only be issued against payments with a `success` status. You cannot refund a pending or failed payment.

***

## Putting it all together

Here's a complete integration example, from initialization to confirmation:

{% stepper %}
{% step %}

### 1. Initialize the payment

```bash
curl -X POST https://api.tendar.io/api/v1/payment/initialize \
  -H "Authorization: Bearer sk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "usr-hnlfkizfoc",
    "amount": 5000,
    "currency": "NGN",
    "narration": "Premium Plan — Monthly",
    "channels": ["card", "bank_transfer"],
    "checkout": {
      "success_url": "https://yourapp.com/payment/success",
      "cancel_url": "https://yourapp.com/payment/cancel"
    },
    "metadata": {
      "order_id": "ord-1234",
      "plan": "premium"
    }
  }'
```

Save the `checkout.url` and `reference` from the response.
{% endstep %}

{% step %}

### 2. Redirect the customer

Send the customer to the `checkout.url`. They'll see a payment page hosted by your provider (e.g., Paystack's checkout page) with the options you specified.

```
https://checkout.paystack.com/dh915o8y5t6fggf
```

{% endstep %}

{% step %}

### 3. Customer pays

The customer enters their card details (or completes a bank transfer, USSD payment, etc.) on the hosted checkout page. You don't handle any sensitive payment information — the provider does.

After payment, the customer is redirected to your `success_url`:

```
https://yourapp.com/payment/success
```

{% endstep %}

{% step %}

### 4. Listen for the webhook

Your webhook endpoint receives a `payment.success` event:

```json
{
    "company_id": "64245949dbcc9f021a769e65",
    "event": "payment.success",
    "service": "recollection",
    "data": {
        "id": "6685d1a4f6078f4b8a3a7542",
        "user_id": "usr-hnlfkizfoc",
        "amount": 5000,
        "currency": "NGN",
        "reference": "pay-tnwaczzqsu",
        "status": "success",
        "paid_at": "2024-07-03T22:33:26Z",
        "payment_channel": "card",
        "metadata": {
            "order_id": "ord-1234",
            "plan": "premium"
        }
    }
}
```

At this point, you can safely activate the customer's premium plan, mark the order as paid, or trigger any other business logic.
{% endstep %}

{% step %}

### 5. (Optional) Verify manually

If you want to double-check the payment status — for example, when the customer lands on your success page — call the fetch endpoint:

```bash
curl https://api.tendar.io/api/v1/payment/fetch/reference/pay-tnwaczzqsu \
  -H "Authorization: Bearer sk_live_xxxxxxxx"
```

Check that `status` is `"success"` and `amount` matches what you expected.
{% endstep %}
{% endstepper %}

***

## Error handling

Here are the most common errors you might encounter:

| Error                                     | HTTP Status | Cause                                                                    | Resolution                                                                                           |
| ----------------------------------------- | ----------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `user not found`                          | 404         | The `user_id` doesn't exist in your company's user directory.            | Verify the user ID or create the user first.                                                         |
| `charge is not allowed`                   | 400         | You passed a non-zero `charge` field.                                    | Remove the `charge` field from your request. Charges are managed by Tendar.                          |
| `payment service key not found`           | 400         | You specified a `payment_service` that isn't configured on your account. | Configure the provider on the Tendar dashboard, or leave `payment_service` empty to use the default. |
| `[provider] has been deactivated`         | 400         | The specified payment provider has been deactivated.                     | Re-activate the provider on your dashboard or use a different one.                                   |
| `payment not found`                       | 404         | The payment ID or reference doesn't exist.                               | Double-check the ID or reference.                                                                    |
| `payment still pending`                   | 400         | You tried to verify a payment that the provider hasn't confirmed yet.    | Wait for the provider webhook, or try again later.                                                   |
| `amount mismatch`                         | 400         | The amount returned by the provider doesn't match the original.          | This is a security check. Investigate the discrepancy — it may indicate tampering.                   |
| `currency mismatch`                       | 400         | The currency returned by the provider doesn't match the original.        | Same as above — investigate the discrepancy.                                                         |
| `only successful payment can be refunded` | 400         | You tried to refund a payment that isn't in `success` status.            | You can only refund successful payments.                                                             |

***

## Security considerations

### Never trust the redirect alone

When the customer is redirected to your `success_url`, it doesn't necessarily mean the payment succeeded. A malicious user could navigate to your success URL directly without paying. **Always verify the payment** via webhooks or the fetch endpoint before fulfilling an order.

### Validate webhook payloads

When you receive a webhook, verify that the `company_id` matches your account and the `reference` corresponds to a payment you actually initialized. This prevents spoofed webhook attacks.

### Use HTTPS for all URLs

Your `success_url`, `cancel_url`, and `callback_url` should all use HTTPS. Payment providers require it, and it ensures your customers' data is protected in transit.

***

## When to use Accept Payments vs. other collection methods

The Accept Payments flow is great for **one-time, customer-initiated payments** — the customer clicks a button, gets redirected, pays, and comes back. But Tendar offers several other collection methods that might be a better fit depending on your use case:

| Scenario                                                                    | Recommended method                                                                      |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| One-time payment where the customer is present (e.g., checkout flow)        | **Accept Payments** (this guide)                                                        |
| Recurring charges on a saved card (e.g., subscriptions, loan repayments)    | [Card Tokenization](https://docs.tendar.co/documentation/recollection/broken-reference) |
| Recurring debits from a bank account (e.g., loan repayments, subscriptions) | [Direct Debit](https://docs.tendar.co/documentation/recollection/broken-reference)      |
| Customers paying via bank transfer on their own schedule                    | [Virtual Accounts](https://docs.tendar.co/documentation/recollection/broken-reference)  |
| Automated, mandate-based collection plans with installments                 | [Collect](https://docs.tendar.co/documentation/recollection/broken-reference)           |
| Recording a loan repayment (manual or provider-backed)                      | [Recollect Loan](https://docs.tendar.co/documentation/recollection/broken-reference)    |

In many real-world integrations, you'll combine multiple methods. For example, you might use Accept Payments for the initial purchase, Card Tokenization to save the card for future charges, and Direct Debit as a fallback if the card fails.

***

## What to read next

| Guide                                                                                   | What you'll learn                                                                          |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| [Card Tokenization](https://docs.tendar.co/documentation/recollection/broken-reference) | Save a customer's card and charge it repeatedly without a checkout redirect.               |
| [Direct Debit](https://docs.tendar.co/documentation/recollection/broken-reference)      | Set up a bank account authorization for recurring debits.                                  |
| [Virtual Accounts](https://docs.tendar.co/documentation/recollection/broken-reference)  | Assign a dedicated bank account number to a customer for inbound transfers.                |
| [Collect](https://docs.tendar.co/documentation/recollection/broken-reference)           | Automate installment-based collections with mandates, consent flows, and scheduled debits. |
| [Recollect Loan](https://docs.tendar.co/documentation/recollection/broken-reference)    | Record loan repayments — manually or through a saved payment method.                       |
| [Overview](https://docs.tendar.co/documentation/recollection/broken-reference)          | The full Recollection Service overview with all available collection methods.              |
