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

# Billing and Payments: Cards, M-Pesa, and Subscriptions

> Accept Paystack card payments, M-Pesa STK pushes, or charges to saved methods, and manage mail subscriptions with volume-based seat pricing.

Nyota Imara's billing system is powered by Paystack and supports three payment paths: a redirect-based card checkout for new cards, an M-Pesa STK push for mobile money, and a one-click charge against a saved payment method. All billing endpoints operate in organization context and require the `X-Organization-Id` header.

***

## List saved payment methods

Before initiating a payment, check what methods are already on file:

```bash theme={null}
curl https://api.nyotaimara.com/v1/billing/methods \
  -H "Authorization: Bearer <your_token>" \
  -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy"
```

**Response:**

```json theme={null}
{
  "success": true,
  "data": [
    {
      "id": "pm_01jb4kqp8a3bvc0nqrtzwmde9xy",
      "channel": "card",
      "cardType": "visa",
      "last4": "4242",
      "expMonth": 12,
      "expYear": 2027,
      "network": "visa",
      "phone": null
    }
  ]
}
```

***

## Initiate a payment

All payment requests go to `POST /v1/billing/pay`. The `method` field determines which payment flow is triggered.

<Tabs>
  <Tab title="New card checkout">
    Redirect the user to a Paystack-hosted checkout page to enter their card details. The server returns a `checkoutUrl` and a `reference`.

    ```bash theme={null}
    curl -X POST https://api.nyotaimara.com/v1/billing/pay \
      -H "Authorization: Bearer <your_token>" \
      -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy" \
      -H "Content-Type: application/json" \
      -d '{
        "method": "card",
        "amount": 5000
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "success": true,
      "checkoutUrl": "https://checkout.paystack.com/access_abc123",
      "reference": "NI-7f3a9c1e-..."
    }
    ```

    Redirect your user to `checkoutUrl`. Paystack returns them to `https://accounts.nyotaimara.com/billing` after completion.
  </Tab>

  <Tab title="M-Pesa STK push">
    Trigger an STK push directly to an M-Pesa number. The user enters their PIN on their phone to complete payment — no redirect required.

    ```bash theme={null}
    curl -X POST https://api.nyotaimara.com/v1/billing/pay \
      -H "Authorization: Bearer <your_token>" \
      -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy" \
      -H "Content-Type: application/json" \
      -d '{
        "method": "mobile_money",
        "phone": "0712345678",
        "amount": 5000
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "success": true,
      "message": "STK Push sent! Please check your phone to enter your M-Pesa PIN.",
      "reference": "NI-9a2f1b4c-..."
    }
    ```

    <Note>
      Only M-Pesa (Safaricom) numbers are supported. Airtel Money numbers return a `400` error. Phone numbers are auto-formatted to the `254XXXXXXXXX` format.
    </Note>
  </Tab>

  <Tab title="Saved payment method">
    Charge a card or send an STK push using a method already saved on the account. Use the `id` from `GET /v1/billing/methods`.

    ```bash theme={null}
    curl -X POST https://api.nyotaimara.com/v1/billing/pay \
      -H "Authorization: Bearer <your_token>" \
      -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy" \
      -H "Content-Type: application/json" \
      -d '{
        "method": "saved_method",
        "paymentMethodId": "pm_01jb4kqp8a3bvc0nqrtzwmde9xy",
        "amount": 5000
      }'
    ```

    **Response:**

    ```json theme={null}
    {
      "success": true,
      "message": "Payment processed successfully!"
    }
    ```
  </Tab>
</Tabs>

***

## Pay for a mail subscription

To purchase or renew a mail subscription, pass `metadata` describing the plan. The server calculates and enforces the correct price server-side — the `amount` field in your request body is ignored for subscription payments.

```bash theme={null}
curl -X POST https://api.nyotaimara.com/v1/billing/pay \
  -H "Authorization: Bearer <your_token>" \
  -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "card",
    "amount": 0,
    "metadata": {
      "type": "mail_subscription",
      "plan": "core",
      "seats": 10,
      "billingCycle": "monthly"
    }
  }'
```

<Warning>
  Never trust a client-provided `amount` for subscription payments. Nyota Imara's server always overrides `amount` with the value calculated from your plan, seat count, and billing cycle. Attempting to submit a lower amount has no effect.
</Warning>

### Pricing tiers

Pricing is based on seat volume, plan tier, and billing cycle.

**Base price per seat per month (Core plan):**

| Seats       | Price per seat / month |
| ----------- | ---------------------- |
| 1 – 50      | 100 KES                |
| 51 – 500    | 80 KES                 |
| 501 – 5,000 | 60 KES                 |
| 5,001+      | 40 KES                 |

**Plan markups (added per seat per month):**

| Plan   | Additional cost per seat |
| ------ | ------------------------ |
| Core   | +0 KES                   |
| Growth | +50 KES                  |
| Scale  | +120 KES                 |

**Yearly billing** charges 10 months for the price of 12 — giving you 2 months free.

<Info>
  Example: 20 seats on the Growth plan, billed monthly = (100 + 50) × 20 = **3,000 KES/month**. Billed yearly = 3,000 × 10 = **30,000 KES/year**.
</Info>

***

## Preview an upgrade

Before committing to a plan change, fetch a prorated cost breakdown:

```bash theme={null}
curl "https://api.nyotaimara.com/v1/billing/upgrade/preview?newPlan=growth&newSeats=20&newCycle=monthly" \
  -H "Authorization: Bearer <your_token>" \
  -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy"
```

The response includes `amountDueToday` — the prorated charge for the remainder of your current billing period.

***

## Execute an upgrade

Once you have confirmed the preview, execute the upgrade. A saved **card** is required (M-Pesa cannot be used for instant mid-cycle upgrades):

```bash theme={null}
curl -X POST https://api.nyotaimara.com/v1/billing/upgrade \
  -H "Authorization: Bearer <your_token>" \
  -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy" \
  -H "Content-Type: application/json" \
  -d '{
    "newPlan": "growth",
    "newSeats": 20,
    "newCycle": "monthly",
    "paymentMethodId": "pm_01jb4kqp8a3bvc0nqrtzwmde9xy"
  }'
```

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Subscription upgraded successfully!"
}
```

If you switch from monthly to yearly billing, your next billing date is set to one year from today.

***

## Cancel a subscription

Cancellation takes effect at the end of your current billing period. You retain full access until then.

```bash theme={null}
curl -X POST https://api.nyotaimara.com/v1/billing/cancel \
  -H "Authorization: Bearer <your_token>" \
  -H "X-Organization-Id: org_01j9kxp8a3bvc0nqrtzwmde4fy"
```

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Subscription cancelled. You will retain access until 30/11/2025."
}
```

***

## Endpoint reference

| Method | Endpoint                      | Description                       |
| ------ | ----------------------------- | --------------------------------- |
| `GET`  | `/v1/billing/methods`         | List saved payment methods        |
| `POST` | `/v1/billing/pay`             | Initiate a payment                |
| `GET`  | `/v1/billing/upgrade/preview` | Preview prorated upgrade cost     |
| `POST` | `/v1/billing/upgrade`         | Execute a plan upgrade            |
| `POST` | `/v1/billing/cancel`          | Cancel subscription at period end |
