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

# Overview

> Payment records and virtual bank accounts for wallet top-ups and subscription billing.

The **Payments** API lets you list the payments collected for your project and mint
one-time deposit accounts that settle into a wallet top-up or a subscription. Depending
on the settlement rail, a deposit account is either a **fiat bank account** (bank
transfer in NGN) or a **stablecoin deposit wallet** (USDC or cNGN, on-chain).

## How funding works

1. **Mint a deposit account** with [Create deposit account](/api-reference/payments/create-deposit-account),
   stating the purpose: a subscription (`planId` + `billingCycle`) or a wallet
   top-up (USD `amount`).
2. **The payer sends the exact `amountExpected`** to the account before `expiryAt`:
   a bank transfer for `FIAT`, an on-chain token transfer for `STABLECOIN`.
3. **Settlement is automatic**: the deposit is verified, an idempotent payment
   record is written, and the purpose is applied: the subscription activates (with
   product access granted) or the wallet balance is credited.

The step-by-step version, including per-rail payer instructions, lives in the
[Fund and subscribe](/guides/fund-and-subscribe) guide.

## The payment object

| Attribute        | Type              | Description                                                                                                           |
| ---------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| `id`             | `string`          | Unique payment id.                                                                                                    |
| `purpose`        | `string`          | `WALLET_TOPUP` or `SUBSCRIPTION`.                                                                                     |
| `status`         | `string`          | `PENDING`, `SUCCESS`, `FAILED`, or `REFUNDED`.                                                                        |
| `amount`         | `number`          | Amount in major units, denominated in `currency` (e.g. `1500` + `NGN` = ₦1,500).                                      |
| `currency`       | `string`          | ISO currency code the payment was collected in (`NGN` for bank transfers and cNGN, `USD` for USD-pegged stablecoins). |
| `paymentRef`     | `string`          | Provider payment reference.                                                                                           |
| `subscriptionId` | `string \| null`  | Linked subscription, for `SUBSCRIPTION` payments.                                                                     |
| `projectId`      | `string`          | Project the payment belongs to.                                                                                       |
| `planId`         | `integer \| null` | Plan billed, for subscription payments.                                                                               |
| `billingCycle`   | `string \| null`  | `MONTHLY` or `YEARLY`, for subscription payments.                                                                     |
| `businessId`     | `string`          | Owning business.                                                                                                      |
| `providerId`     | `string`          | Upstream provider id.                                                                                                 |
| `reference`      | `string`          | Idempotent reference for the payment.                                                                                 |
| `amountUsd`      | `string`          | USD value as a decimal string.                                                                                        |
| `description`    | `string`          | Free-text note.                                                                                                       |
| `createdBy`      | `string`          | Actor that created the record.                                                                                        |
| `createdAt`      | `string`          | ISO 8601 UTC timestamp.                                                                                               |
| `updatedAt`      | `string`          | ISO 8601 UTC timestamp.                                                                                               |

## The deposit account object

Returned by [Create deposit account](/api-reference/payments/create-deposit-account) in the
`data` array, one entry per active provider. Each account is bound to a single expected
amount and expires.

The object is a **discriminated union on `kind`**: the keys present depend on the rail, and
you only ever receive the keys for that rail. Always branch on `kind`; do not assume a field
exists across both.

<Warning>
  A `FIAT` account carries **no** on-chain keys (`walletAddress`, `token`, `chainId`, …) and a
  `STABLECOIN` account carries **no** bank keys (`accountNumber`, `bankName`, …). The
  irrelevant keys are **absent**, not `null`.
</Warning>

### `FIAT`: bank transfer

| Attribute        | Type     | Description                                                                    |
| ---------------- | -------- | ------------------------------------------------------------------------------ |
| `kind`           | `string` | Always `FIAT`.                                                                 |
| `accountNumber`  | `string` | Virtual bank account number to pay into.                                       |
| `accountName`    | `string` | Account holder name.                                                           |
| `bankName`       | `string` | Bank the account is held with.                                                 |
| `bankCode`       | `string` | Bank code.                                                                     |
| `reference`      | `string` | Reference that ties the funding to this account.                               |
| `amountExpected` | `number` | Exact amount to pay, in **NGN**.                                               |
| `fxRate`         | `number` | NGN/USD rate (USD per 1 NGN) the quote was priced at, locked until `expiryAt`. |
| `expiryAt`       | `string` | ISO 8601 UTC expiry. Pay before this time (\~1h).                              |

### `STABLECOIN`: on-chain deposit

| Attribute           | Type      | Description                                                                                                             |
| ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| `kind`              | `string`  | Always `STABLECOIN`.                                                                                                    |
| `walletAddress`     | `string`  | Temporary on-chain deposit address to send tokens to.                                                                   |
| `token`             | `string`  | Token symbol (e.g. `USDC`, `cNGN`).                                                                                     |
| `pegCurrency`       | `string`  | Fiat peg of the token: `USD` (USDC) or `NGN` (cNGN, 1:1 naira).                                                         |
| `contractAddress`   | `string`  | Token contract address.                                                                                                 |
| `network`           | `string`  | Chain family (e.g. `EVM`).                                                                                              |
| `blockchainNetwork` | `string`  | Human-readable network name (e.g. `Base`).                                                                              |
| `chainId`           | `integer` | EVM chain id (e.g. `8453`).                                                                                             |
| `reference`         | `string`  | Reference that ties the funding to this wallet.                                                                         |
| `amountExpected`    | `number`  | Exact amount to send, in **token major units**, denominated in the token's peg currency (e.g. `15` = 15 USDC).          |
| `amountExpectedNgn` | `number`  | Naira value to display to the payer. Present **only** for naira-pegged tokens (cNGN), where it equals `amountExpected`. |
| `fxRate`            | `number`  | NGN/USD rate (USD per 1 NGN) the quote was priced at, locked until `expiryAt`.                                          |
| `expiryAt`          | `string`  | ISO 8601 UTC expiry. Send before this time (\~30m).                                                                     |

<Tip>
  For a cNGN deposit, show the payer the naira value, e.g.
  `Send ₦22,556.39 (22,556.39 cNGN)`. For USD-pegged tokens, show the token
  amount, e.g. `Send 15 USDC`.
</Tip>

## FX rate lock

Every amount in a deposit-account response is priced at a single NGN/USD rate,
returned as `fxRate` (USD per 1 NGN). The rate is **locked for the life of the
account**: when the deposit lands, settlement converts at the quoted rate, not
the live market rate, so what the payer owes and what gets credited can't
drift inside the deposit window. After `expiryAt` the lock (and the account)
expires; request a new account to get a fresh rate.

## Subscription upgrades are prorated

If the project already holds an **active paid subscription to a different plan
of the same product**, a subscription request is treated as an upgrade:

* The unused value of the current subscription (`current plan price × fraction
  of the billing period remaining`) is credited against the new plan's price.
* Only the difference is charged. All amounts in the response already reflect
  the credit, priced at the locked `fxRate`.
* When the payment settles, the new subscription starts a full fresh period,
  the old subscription is cancelled, and its product access is replaced by the
  new plan's.
* Moving from a **free** plan to a paid plan of the same product works the same
  way, just with no credit: full price is charged, and the free subscription
  and its access grant are replaced on settlement.
* If the credit would cover the entire new price (a downgrade or near-equal
  switch), the request is rejected: no-charge plan switches are not supported.

No special request field is needed: upgrades are detected server-side, and a
renewal is just a new request with the same `planId`.
