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

# Platform billing

> Salesive's fee on orders captured by an external payment provider: how it accrues, the monthly invoice, dunning, and the merchant endpoints that read and pay it.

## What this fee is

When a shopper pays through an **external payment provider**, meaning a provider registered by an
installed payment app, that provider settles the money straight to the merchant. Salesive never
holds the funds, so it cannot keep its cut at the moment of sale. Instead it records what the store
owes and bills it monthly.

The fee applies only to an order that carries an external provider marker:

```json theme={null}
{
  "payment": {
    "externalProvider": {
      "providerId": "pay_with_x",
      "appId": "66b1f0a3c2d4e5f6a7b8c9d0",
      "name": "Pay with X"
    },
    "currency": "NGN"
  }
}
```

**Paystack sales and wallet sales do not accrue this fee.** An order paid through Salesive's own
gateways, or from a store wallet, has no external provider marker and writes no row.

> [!NOTE]
>
> This is not the 5% app charge fee described in [Payments](https://docs.salesive.com/apps/payments.md). That fee is taken when
> an app charges a merchant. This fee is taken by Salesive on sales a payment app captured through
> its own provider.

## Amounts

Every amount on this page is in the **major unit** of its currency: NGN naira, USD dollars, never
kobo or cents. The ledger, the invoice and the order all use the same unit, so the numbers compare
directly. This is the same rule as the rest of the Apps API; see
[Money and units](https://docs.salesive.com/apps-api/introduction.md#money-and-units) for the canonical statement and a worked
example.

The fee is computed on order GMV:

```text theme={null}
GMV = subtotal + shippingCost - discount
```

Never below zero. Tax is excluded because it is collected for a tax authority and is not merchant
revenue.

The rate is a fraction of GMV and comes from the store's plan:

| Plan | Rate |
| - | - |
| Starter | 2% |
| Basic | 1.75% |
| Pro | 1.5% |
| Premium | 1% |
| No plan, or an unknown plan | 1.5% |

The rate in force is written onto each accrual row and never moves afterwards. A later plan change
or a rate change applies only to new accruals, and a refund on an old order uses that order's own
rate.

## How an accrual is written

A recurring job looks for orders updated in the last 48 hours that are `paid`, `completed` or
`refunded` and carry `payment.externalProvider.providerId`. It asks the API to accrue each one. By
default the job runs once a day at 01:10 UTC, so a captured order is picked up on the next run and a
missed run catches up on the 48 hour window.

The write is idempotent: one order and one provider produce at most one open row. A second run
inside the window returns the row already there, unchanged.

| Field | Meaning |
| - | - |
| `orderId` | The captured order. |
| `providerId`, `providerName` | The external provider, as the order recorded it. |
| `appId` | The payment app that registered the provider, or `null`. |
| `gmv` | The order GMV the fee was computed on. |
| `currency` | The charged currency, uppercase. |
| `rate` | The fraction in force when the row was written. |
| `amount` | `gmv * rate`, rounded to the currency's cent. Negative on a credit row. |
| `status` | `accrued`, `invoiced` or `void`. |
| `refundedAmount` | GMV refunded so far, capped at `gmv`. |
| `capturedAt` | When the order became payable. |

### Refunds

* An accrual that has **not** been invoiced is reduced in place. A full refund sets its amount to
  zero and its status to `void`.
* An accrual that is **already on an invoice** is never edited. A negative `void` row carries the
  difference, and the next invoice nets it out. A credit larger than the next month's subtotal stays
  unapplied and rolls forward instead of producing a negative bill.
* Repeated refund calls with the same amount write nothing further.

## The monthly invoice

One invoice covers one store, one currency and one UTC calendar month. It is issued on the first of
the following month (02:10 UTC by default) from every `accrued` row and every unapplied credit for
the period.

| Field | Meaning |
| - | - |
| `number` | `PF-YYYYMM-0001`, sequential per store and month. |
| `periodStart`, `periodEnd` | The UTC month the invoice covers. |
| `subtotal` | The month's positive accruals. |
| `credits` | Negative adjustment rows applied to this invoice. |
| `total` | `subtotal - credits`, never negative. |
| `paidAmount` | What has been collected so far. |
| `balance` | `total - paidAmount`. |
| `status` | See the table below. |
| `issuedAt`, `dueAt`, `paidAt` | Issue date, due date and settlement date. `dueAt` is 14 days after issue. |
| `reminderCount`, `lastReminderAt` | Dunning state. |
| `payments` | Every collection attempt, successful or failed. |

| Status | Meaning |
| - | - |
| `open` | Issued and waiting for payment. |
| `partially_paid` | Some money arrived and a balance remains. |
| `paid` | Nothing remains. An invoice fully covered by credits is issued as `paid`. |
| `overdue` | The due date passed with a balance. |
| `void` | Cancelled and never collected. |
| `draft` | Created but not issued. The close job issues straight to `open`, so a merchant does not normally see this. |

## Dunning

A daily job (09:10 UTC by default) marks an `open` or `partially_paid` invoice whose `dueAt` has
passed as `overdue`. It then reminds the store owner in the dashboard and by email. A reminder goes
out at most once every seven days and never more than three times per invoice. Paying the balance
stops it.

## Authentication

These routes are for the **store owner's merchant session**, never an `app_` token:

```http theme={null}
Authorization: Bearer <merchant-session-token>
x-shop-id: <shop-id>
```

All four require the owner. Staff members and installed apps are refused with `403`, because the
response carries the store's wallet balance and the right to spend it.

## Summary

```http theme={null}
GET /api/v1/platform-billing/summary
```

Returns what the store owes in each currency, the invoice counts and the next due date. A store can
owe in more than one currency, so `currencies` is a list rather than one total.

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Platform billing",
  "data": {
    "currencies": [
      {
        "currency": "NGN",
        "outstanding": 48000,
        "overdue": 0,
        "accrued": 12500
      }
    ],
    "invoices": {
      "open": 1,
      "partiallyPaid": 0,
      "overdue": 0,
      "paid": 4,
      "total": 5
    },
    "nextDueAt": "2026-11-15T02:10:00.000Z"
  }
}
```

| Field | Meaning |
| - | - |
| `outstanding` | The unpaid balance of open, partially paid and overdue invoices in this currency. |
| `overdue` | The part of `outstanding` that is past due. |
| `accrued` | Accrued rows not yet invoiced, net of unapplied credits. |
| `nextDueAt` | The earliest due date among unpaid invoices, or `null`. |

## List invoices

```http theme={null}
GET /api/v1/platform-billing/invoices?status=open&limit=20&page=1
```

| Parameter | Notes |
| - | - |
| `status` | One of `open`, `partially_paid`, `paid`, `overdue`, `void` or `draft`. Omit for every status. |
| `limit` | 1 to 100. Default 20. |
| `page` | 1 or more. Default 1. |

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Invoices",
  "data": {
    "items": [
      {
        "id": "6910f3a2c4d5e6f7a8b9c0d1",
        "number": "PF-202610-0001",
        "periodStart": "2026-10-01T00:00:00.000Z",
        "periodEnd": "2026-10-31T23:59:59.999Z",
        "currency": "NGN",
        "subtotal": 48000,
        "credits": 0,
        "total": 48000,
        "paidAmount": 0,
        "balance": 48000,
        "status": "open",
        "issuedAt": "2026-11-01T02:10:00.000Z",
        "dueAt": "2026-11-15T02:10:00.000Z",
        "paidAt": null,
        "reminderCount": 0,
        "lastReminderAt": null,
        "payments": [],
        "createdAt": "2026-11-01T02:10:00.000Z"
      }
    ],
    "total": 1,
    "page": 1,
    "limit": 20
  }
}
```

Newest period first.

## Get one invoice

```http theme={null}
GET /api/v1/platform-billing/invoices/:id
```

Returns the invoice plus the accrual rows that make it up.

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Invoice",
  "data": {
    "id": "6910f3a2c4d5e6f7a8b9c0d1",
    "number": "PF-202610-0001",
    "periodStart": "2026-10-01T00:00:00.000Z",
    "periodEnd": "2026-10-31T23:59:59.999Z",
    "currency": "NGN",
    "subtotal": 48000,
    "credits": 0,
    "total": 48000,
    "paidAmount": 0,
    "balance": 48000,
    "status": "open",
    "issuedAt": "2026-11-01T02:10:00.000Z",
    "dueAt": "2026-11-15T02:10:00.000Z",
    "paidAt": null,
    "reminderCount": 0,
    "lastReminderAt": null,
    "payments": [],
    "createdAt": "2026-11-01T02:10:00.000Z",
    "accruals": [
      {
        "id": "6910f3a2c4d5e6f7a8b9c0e1",
        "orderId": "6910f3a2c4d5e6f7a8b9c0f1",
        "providerId": "pay_with_x",
        "providerName": "Pay with X",
        "appId": "66b1f0a3c2d4e5f6a7b8c9d0",
        "gmv": 240000,
        "currency": "NGN",
        "rate": 0.02,
        "amount": 4800,
        "status": "invoiced",
        "refundedAmount": 0,
        "capturedAt": "2026-10-14T09:12:00.000Z",
        "adjusts": null
      }
    ]
  }
}
```

A `void` row has a negative `amount` and an `adjusts` id pointing at the accrual it corrects.

## Pay an invoice

```http theme={null}
POST /api/v1/platform-billing/invoices/:id/pay
```

No body. The store's wallet is used first, for as much as it covers, then a saved card for the
remainder if one exists.

* The wallet is chosen by the invoice currency first, then the store's default wallet, then its first
  active wallet. A wallet in another currency is converted at the current exchange rate.
* The card is the one the store already saved for its own Salesive subscription. If there is no
  saved card, the card step is skipped quietly. It is not an error.
* Every attempt is recorded in `payments` with `rail` (`wallet` or `card`), `amount`, `currency`,
  `reference`, `status` (`succeeded` or `failed`), `error` and `at`.
* An invoice that is already paid returns `paid` values of zero rather than charging again.

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Payment attempt recorded",
  "data": {
    "invoice": {
      "id": "6910f3a2c4d5e6f7a8b9c0d1",
      "number": "PF-202610-0001",
      "currency": "NGN",
      "subtotal": 48000,
      "credits": 0,
      "total": 48000,
      "paidAmount": 20000,
      "balance": 28000,
      "status": "partially_paid",
      "issuedAt": "2026-11-01T02:10:00.000Z",
      "dueAt": "2026-11-15T02:10:00.000Z",
      "paidAt": null,
      "payments": [
        {
          "rail": "wallet",
          "amount": 20000,
          "currency": "NGN",
          "reference": "pfw_9a1c2d3e4f5a6b7c",
          "status": "succeeded",
          "error": null,
          "at": "2026-11-08T10:15:00.000Z"
        }
      ]
    },
    "paid": {
      "wallet": 20000,
      "card": 0,
      "total": 20000
    },
    "remaining": 28000,
    "currency": "NGN"
  }
}
```

A positive `remaining` means the invoice is still open. Report it as settled only when it is `0`.

**No card is required to use an external provider.** A merchant can install a payment app and accept
provider sales with no saved card at all. The fee still accrues, and the invoice can be settled from
the wallet, or from a card once the store saves one. The card step only makes collection automatic.

## Errors

| Status | Meaning |
| - | - |
| `401` | No valid merchant session. |
| `403` | The caller is not the store owner, is staff, or is an `app_` token. |
| `404` | The invoice does not exist for this store. |
| `409` | The invoice was cancelled (`void`), so it cannot be paid. |
| `500` | An unexpected error. Retry later. |

## Next steps

> [card] [Wallet sources](https://docs.salesive.com/apps/wallet-sources.md)
>
> Show a payment app's own balance next to the store wallet.

> [card] [Payments](https://docs.salesive.com/apps/payments.md)
>
> The separate fee an app pays when it charges a merchant.


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