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

# Payments

> Read a customer's payment attempts: what was paid, how, and whether it arrived.

A **Payment** is one attempt to move a customer's money to Ndovu. You create one with [`invest`](https://docs.ndovu.co/investing). It starts `PENDING` and ends `COMPLETED` (the money arrived) or `FAILED` (it never will). It never changes after that: to try again, you invest again and get a new Payment.

Every Payment has a **reference**, such as `ACM7F3K9Q`: your `referenceCode` followed by 6 characters. It's unique across Ndovu, and it's the memo a bank transfer must carry.

## List payments

```
GET /api/v3/partners/{shortName}/customers/{externalUserId}/payments
```

Every payment attempt for the customer, across all plans, newest first.

```bash cURL theme={null}
curl "https://be-dev-1.dev.ndovu.co/api/v3/partners/YOUR_SHORT_NAME/customers/cust_8842/payments?status=PENDING&limit=20" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```javascript Node.js theme={null}
const res = await fetch(
  `https://be-dev-1.dev.ndovu.co/api/v3/partners/${shortName}/customers/${externalUserId}/payments?limit=20`,
  { headers: { Authorization: `Bearer ${accessToken}` } }
);

const { data: page } = await res.json();
console.log(page.data, page.nextCursor);
```

```python Python theme={null}
res = requests.get(
    f"https://be-dev-1.dev.ndovu.co/api/v3/partners/{short_name}/customers/{external_user_id}/payments",
    headers={"Authorization": f"Bearer {access_token}"},
    params={"limit": 20},
)
res.raise_for_status()
page = res.json()["data"]
```

### Query parameters

| Parameter | Description |
| - | - |
| `status` | Only `PENDING`, `COMPLETED` or `FAILED` payments. |
| `planId` | Only payments into this plan. |
| `limit` | Up to 50. Default 20. |
| `cursor` | The `nextCursor` from the previous page. |

### Response

```json theme={null}
{
  "message": "Request executed successfully",
  "data": {
    "data": [
      {
        "reference": "ACM7F3K9Q",
        "channel": "BANK_TRANSFER",
        "amount": 5000,
        "finalAmount": 5000,
        "currency": "KES",
        "status": "COMPLETED",
        "mnoReference": null,
        "bankReference": "FT26296ABCD",
        "phoneNumber": null,
        "plan": { "id": 12, "name": "Ndovu Money Market Fund" },
        "transactionId": 48213,
        "createdAt": "2026-10-23T08:00:00.000Z",
        "completedAt": "2026-10-23T11:42:10.000Z"
      }
    ],
    "nextCursor": null
  }
}
```

`nextCursor` is `null` on the last page. Otherwise pass it as `cursor` to get the next page. Pages stay consistent while new payments are added.

## Get a payment

```
GET /api/v3/partners/{shortName}/customers/{externalUserId}/payments/{reference}
```

```bash theme={null}
curl https://be-dev-1.dev.ndovu.co/api/v3/partners/YOUR_SHORT_NAME/customers/cust_8842/payments/ACM7F3K9Q \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

The reference is matched without regard to case, spaces or hyphens, so `acm-7f3k9q` finds `ACM7F3K9Q`. The response is the payment object plus `bankDetails`.

## The payment object

| Field | Type | Description |
| - | - | - |
| `reference` | string | The payment reference. For a bank transfer, the memo the transfer must carry. |
| `channel` | string | `BANK_TRANSFER` or `MPESA`. |
| `amount` | number | The amount invested. |
| `finalAmount` | number | The amount to pay. |
| `currency` | string | The payment currency. |
| `status` | string | `PENDING`, `COMPLETED` or `FAILED`. See below. |
| `mnoReference` | string \| null | The M-Pesa receipt, once paid. |
| `bankReference` | string \| null | The bank's transaction ID, once matched. |
| `phoneNumber` | string \| null | The number that paid by M-Pesa, partly hidden, e.g. `25471...5678`. |
| `plan` | object \| null | `id` and `name` of the plan. |
| `transactionId` | integer \| null | The [Transaction](https://docs.ndovu.co/portfolio-and-transactions.md#transactions) this Payment funds. `null` until `COMPLETED`. |
| `createdAt` | string | When the Payment was created. |
| `completedAt` | string \| null | When it completed. |
| `bankDetails` | object \| null | Detail only. Where to send a bank transfer: `bankName`, `bankBranch`, `accountName`, `accountNumber`, `swiftCode`. Only while a bank transfer is `PENDING`. |

## Payment status

| Status | Meaning |
| - | - |
| `PENDING` | Waiting for the money: an M-Pesa prompt not yet answered, or a bank transfer not yet received. |
| `COMPLETED` | The money arrived. The Transaction now exists (`transactionId`) and the investment is on its way. |
| `FAILED` | The money will never arrive on this Payment: the prompt was cancelled or rejected, or a bank transfer expired after 5 days. Invest again to retry. |

A failed Payment never becomes a Transaction, so it appears here but not in the customer's transactions.

## Errors

| Status | Code | Cause |
| - | - | - |
| `400` | `BAD_REQUEST` | An invalid `status`, `planId`, `limit` or `cursor`. |
| `404` | `CUSTOMER_NOT_FOUND` | You have no customer with this `externalUserId`. |
| `404` | `PAYMENT_NOT_FOUND` | No payment with this reference for this customer. |


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