Skip to content
ABUNA
DocsInvoices and payments

API reference

Invoices and payments

Billing periods, their invoices, and the payments made against them, and how to list and read invoices.

A subscription bills one billing period at a time. Each period has one invoice, and each attempt to pay it is a payment. You read all three when you retrieve a subscription. To browse invoices across every subscription, list invoices. To send a customer to a hosted page to pay an open invoice, use Pay an invoice.

The billing period object

Creating a subscription returns its first billing period as entry. Abuna creates the next one when a paid period ends, and issues its invoice.

Attributes

  • idstring

    Unique identifier for the billing period.
  • subscription_idstring

    The subscription the period belongs to.
  • price_idstring

    The price the period bills.
  • pay_tokenstring

    The token in the period's pay link.
  • starts_attimestamp

    When the period starts, in Unix seconds.
  • ends_attimestamp

    When the period ends, in Unix seconds.
  • period_indexinteger

    The period's place in the subscription, starting at 0.
  • grace_period_ends_attimestamp

    The date to pay by: the app's grace period after the period starts, but never after it ends. If it passes unpaid, Abuna cancels the subscription.
  • paid_attimestampnullable

    When the period was paid.
  • paused_attimestampnullable

    Reserved for paused periods. Always null.
  • in_forceboolean

    false for an upgrade's period while it waits for payment, and for good if the upgrade lapses. Such a period doesn't count as one of the subscription's periods, and the next period reuses its period_index.
  • created_attimestamp

    When the period was created, in Unix seconds.
The billing period object
{
  "id": "01J9ZQ7E1F2G3H4J5K6M7N8P9Q",
  "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
  "price_id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
  "pay_token": "3a7e1c9f5b2d8a4e6c0f7b3d9a1e5c8f2b6d",
  "starts_at": 1727600000,
  "ends_at": 1730192000,
  "period_index": 0,
  "grace_period_ends_at": 1727686400,
  "paid_at": null,
  "paused_at": null,
  "in_force": true,
  "created_at": 1727600000
}

The invoice object

An invoice keeps the amount, buyer, and seller as they were when Abuna issued it. Later changes don't alter it.

Attributes

  • idstring

    Unique identifier for the invoice.
  • app_idstring

    The app that issued the invoice.
  • entry_idstring

    The billing period the invoice bills.
  • subscription_idstring

    The subscription the invoice belongs to.
  • numberstring

    The invoice number.
  • statusstring

    open until paid, then paid. void when the subscription was canceled before it was paid, or a plan change replaced it.
  • kindstring

    period for a billing period, or plan_change for the first period of an upgrade. See Upgrades.
  • amountinteger

    The amount due, in the currency's smallest unit.
  • creditinteger

    The unused time of the previous period taken off the price. 0 unless kind is plan_change.
  • currencystring

    XAF or XOF.
  • item_labelstring

    The price's name.
  • buyer_namestringnullable

    The customer's name.
  • buyer_emailstringnullable

    The customer's email address.
  • seller_namestring

    The app's name.
  • period_starttimestamp

    When the billed period starts.
  • period_endtimestamp

    When the billed period ends.
  • due_attimestamp

    The date to pay by.
  • created_attimestamp

    When the invoice was issued, in Unix seconds.
  • paid_attimestampnullable

    When the invoice was paid.
  • voided_attimestampnullable

    When the invoice was voided.
The invoice object
{
  "id": "01J9ZQ7V1W2X3Y4Z5A6B7C8D9E",
  "app_id": "01J9ZQ4Y7R3T6V8W2X5B1C0DEF",
  "entry_id": "01J9ZQ7E1F2G3H4J5K6M7N8P9Q",
  "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
  "number": "01J9ZQ7N1P2Q3R4S5T6V7W8X9Y",
  "status": "open",
  "kind": "period",
  "amount": 5000,
  "credit": 0,
  "currency": "XAF",
  "item_label": "Monthly",
  "buyer_name": "Jane Doe",
  "buyer_email": "jane@example.com",
  "seller_name": "My store",
  "period_start": 1727600000,
  "period_end": 1730192000,
  "due_at": 1727686400,
  "created_at": 1727600000,
  "paid_at": null,
  "voided_at": null
}

The payment object

Attributes

  • idstring

    Unique identifier for the payment.
  • app_idstring

    The app the payment belongs to.
  • entry_idstring

    The billing period the payment is for.
  • driverstring

    The payment provider, like pawapay. sandbox for a simulated Test payment.
  • providerstring

    The network the provider charged, like MTN_MOMO_CMR.
  • provider_referencestringnullable

    The provider's own ID for the charge.
  • amountinteger

    The amount charged, in the currency's smallest unit.
  • currencystring

    XAF or XOF.
  • statusstring

    pending, succeeded, or failed.
  • messagestringnullable

    The failure code, like insufficient_funds, or null when the payment hasn't failed.
  • created_attimestamp

    When the payment started, in Unix seconds.
  • feeinteger

    The provider's fee, in the currency's smallest unit.
  • session_idstringnullable

    The session used to make this payment, or null for a payment made another way.
  • method_labelstring

    The network's name, in words.
The payment object
{
  "amount": 5000,
  "app_id": "01M4047ET949K8JPEZ25NMS0T2",
  "created_at": 1791005801,
  "currency": "XAF",
  "driver": "sandbox",
  "entry_id": "01M4047ETRRSRY6EY5FH0X9XCG",
  "fee": 100,
  "id": "01M4047ETZM9J6NNBFK50EYY8Z",
  "message": null,
  "method_label": "Successful payment (test)",
  "provider": "test",
  "provider_reference": "test_08abc6ec9bdd456b8bbc",
  "session_id": null,
  "status": "succeeded"
}

The invoice summary object

The invoice endpoints return invoices in this shape. It adds the customer and whether the invoice is overdue, and leaves out app_id, entry_id, and seller_name.

Attributes

  • idstring

    Unique identifier for the invoice.
  • numberstring

    The invoice number.
  • statusstring

    open until paid, then paid. void when the subscription was canceled before it was paid, or a plan change replaced it.
  • overdueboolean

    true when the invoice is open and its due_at has passed. Abuna works this out each time you ask. It is not a status.
  • kindstring

    period or plan_change, as on the invoice object.
  • amountinteger

    The amount due, in the currency's smallest unit.
  • creditinteger

    The unused time taken off the price, as on the invoice object.
  • currencystring

    XAF or XOF.
  • item_labelstring

    The price's name.
  • buyer_namestringnullable

    The customer's name.
  • buyer_emailstringnullable

    The customer's email address.
  • subscription_idstring

    The subscription the invoice belongs to.
  • customer_idstring

    The subscription's customer.
  • period_starttimestamp

    When the billed period starts.
  • period_endtimestamp

    When the billed period ends.
  • due_attimestamp

    The billing period's date to pay by (grace_period_ends_at), as it stands now. It can differ from the due_at on the invoice object, which is set when the invoice is issued.
  • created_attimestamp

    When the invoice was issued, in Unix seconds.
  • paid_attimestampnullable

    When the invoice was paid.
  • voided_attimestampnullable

    When the invoice was voided.
The invoice summary object
{
  "id": "01J9ZQ7V1W2X3Y4Z5A6B7C8D9E",
  "number": "01J9ZQ7N1P2Q3R4S5T6V7W8X9Y",
  "status": "open",
  "overdue": false,
  "kind": "period",
  "amount": 5000,
  "credit": 0,
  "currency": "XAF",
  "item_label": "Monthly",
  "buyer_name": "Jane Doe",
  "buyer_email": "jane@example.com",
  "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
  "customer_id": "01J9ZQ6A1B2C3D4E5F6G7H8J9K",
  "period_start": 1727600000,
  "period_end": 1730192000,
  "due_at": 1727686400,
  "created_at": 1727600000,
  "paid_at": null,
  "voided_at": null
}

List invoices

GET/v1/apps/{appID}/invoices

Lists the app's invoices, newest first, one page at a time. Test mode and Live mode have separate invoices, so a list shows only the mode of the app you ask about. All parameters are optional, and a blank one is ignored.

Parameters

  • statusstring

    open, paid, void, or overdue. open includes the overdue invoices. overdue returns the open invoices whose date to pay by has passed.
  • searchstring

    Part of the invoice number, buyer name, or buyer email. Ignores case.
  • fromtimestamp

    Only invoices issued at or after this time, in Unix seconds.
  • totimestamp

    Only invoices issued before this time, in Unix seconds.
  • limitinteger

    How many invoices to return, from 1 to 100. Defaults to 25.
  • cursorstring

    The next_cursor from the previous page. Send the same filters with it.

Response attributes

  • itemsarray

    The page's invoices. Empty when nothing matches.
  • next_cursorstringnullable

    Pass it as cursor to get the next page. It is null on the last page. Treat it as opaque.

Returns 200. A value that isn't allowed returns 422 with code invalid and the parameter's name in errors.

GET /v1/apps/{appID}/invoices
curl "https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/invoices?status=overdue&limit=25" \
  -H "Authorization: Bearer sk_test_..."
Response
{
  "items": [
    {
      "id": "01J9ZQ7V1W2X3Y4Z5A6B7C8D9E",
      "number": "01J9ZQ7N1P2Q3R4S5T6V7W8X9Y",
      "status": "open",
      "overdue": false,
      "kind": "period",
      "amount": 5000,
      "credit": 0,
      "currency": "XAF",
      "item_label": "Monthly",
      "buyer_name": "Jane Doe",
      "buyer_email": "jane@example.com",
      "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
      "customer_id": "01J9ZQ6A1B2C3D4E5F6G7H8J9K",
      "period_start": 1727600000,
      "period_end": 1730192000,
      "due_at": 1727686400,
      "created_at": 1727600000,
      "paid_at": null,
      "voided_at": null
    }
  ],
  "next_cursor": "MTcyNzYwMDAwMDAwMDAwMC4wMUo5WlE3VjFXMlgzWTRaNUE2QjdDOEQ5RQ"
}

Retrieve an invoice

GET/v1/apps/{appID}/invoices/{id}

Retrieves an invoice with its links and its history, from when it was issued to when it was paid or voided.

Response attributes

  • invoiceobject

  • pay_urlstringnullable

    The pay link for the billing period. It is null once the invoice is paid or void, or when the subscription is canceled.
  • invoice_urlstring

    A link to the customer's invoice page, which also shows paid and void invoices. It expires after 7 days.
  • timelinearray

    What happened to the invoice, oldest first. Events in the same second come in the order below.
    Show child attributesHide child attributes
    • typestring

      • issued: Abuna issued the invoice.
      • reminder: Abuna queued a reminder.
      • notice: a message to the customer about the invoice.
      • payment: an attempt to pay, whether it succeeded, failed, or is still pending.
      • paid: the invoice was paid.
      • voided: the invoice was voided.
    • attimestamp

      When it happened, in Unix seconds. For a notice, when it was sent, or created if it is not sent yet.
    • noticeobject

      On a notice event: the notice object. Its recipient is empty for Telegram.
    • paymentobject

      On a payment event: the payment object. Its status and message say how the attempt ended.
    • reminderobject

      On a reminder event: an object with kind, one of renewal_upcoming, payment_overdue, or payment_final_notice.

Returns 200. If the app has no invoice with that ID, returns 404 with code not_found.

GET /v1/apps/{appID}/invoices/{id}
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/invoices/01J9ZQ7V1W2X3Y4Z5A6B7C8D9E \
  -H "Authorization: Bearer sk_test_..."
Response
{
  "invoice": {
    "id": "01J9ZQ7V1W2X3Y4Z5A6B7C8D9E",
    "number": "01J9ZQ7N1P2Q3R4S5T6V7W8X9Y",
    "status": "open",
    "overdue": false,
    "kind": "period",
    "amount": 5000,
    "credit": 0,
    "currency": "XAF",
    "item_label": "Monthly",
    "buyer_name": "Jane Doe",
    "buyer_email": "jane@example.com",
    "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
    "customer_id": "01J9ZQ6A1B2C3D4E5F6G7H8J9K",
    "period_start": 1727600000,
    "period_end": 1730192000,
    "due_at": 1727686400,
    "created_at": 1727600000,
    "paid_at": null,
    "voided_at": null
  },
  "pay_url": "https://app.abuna.app/pay/3a7e1c9f5b2d8a4e6c0f7b3d9a1e5c8f2b6d",
  "invoice_url": "https://app.abuna.app/invoice/01J9ZQ7E1F2G3H4J5K6M7N8P9Q?exp=1728204800&sig=b4e1c8f2a7d3960e5b1f8c4a2d7e3b9f6a0c5e8d1b4f7a2c9e6d3b0f8a5c1e7d",
  "timeline": [
    { "type": "issued", "at": 1727600000 },
    {
      "type": "notice",
      "at": 1727600002,
      "notice": {
        "id": "01J9ZQ9N1P2Q3R4S5T6V7W8X9Y",
        "customer_id": "01J9ZQ6A1B2C3D4E5F6G7H8J9K",
        "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
        "kind": "subscription_started",
        "channel": "email",
        "recipient": "jane@example.com",
        "subject": "Pay FCFA 5,000 to start your Pro · Monthly plan with My store",
        "status": "sent",
        "attempts": 1,
        "last_error": null,
        "next_attempt_at": 1727600000,
        "created_at": 1727600000,
        "sent_at": 1727600002
      }
    },
    {
      "type": "payment",
      "at": 1727600090,
      "payment": {
        "id": "01J9ZQ7P1Q2R3S4T5V6W7X8Y9Z",
        "app_id": "01J9ZQ4Y7R3T6V8W2X5B1C0DEF",
        "entry_id": "01J9ZQ7E1F2G3H4J5K6M7N8P9Q",
        "driver": "pawapay",
        "provider": "MTN_MOMO_CMR",
        "provider_reference": "9f3b7d1a-5c2e-8f4b-6d0a-3e7c1b5a9d2f",
        "amount": 5000,
        "currency": "XAF",
        "status": "failed",
        "message": "insufficient_funds",
        "created_at": 1727600090,
        "fee": 100,
        "session_id": null,
        "method_label": "MTN Mobile Money Cameroon"
      }
    },
    { "type": "reminder", "at": 1727686400, "reminder": { "kind": "payment_overdue" } }
  ]
}

Pay a billing period

POST/v1/apps/{appID}/entries/{id}/pay

Charges the customer's saved phone number for an unpaid billing period. It uses the first network your payment provider lists. With simulated Test payments, the charge always succeeds. To have the customer pay on a hosted page instead, use Pay an invoice.

Response attributes

  • statusstring

    succeeded, or pending while the customer approves the charge on their phone.
  • paidboolean

    true when status is succeeded.
  • entryobject

    The billing period object.

Returns 200. A declined charge returns 402 with the reason as its code, like insufficient_funds or payment_declined. These also fail:

  • 403 plan_limit: the customer's first payment would pass your plan's Live subscriber limit.
  • 404 not_found: the app has no billing period with that ID.
  • 409 already_paid: the period is paid.
  • 409 invoice_not_open: the invoice is void, because the subscription was canceled.
  • 409 payment_in_progress: another payment for the period is still pending.
  • 422 not_accepting_payments: a Live app can't take payments yet.
  • 422 payment_unavailable: the network can't charge the invoice's currency.
POST /v1/apps/{appID}/entries/{id}/pay
curl https://api.abuna.app/v1/apps/01J9ZQ4Y7R3T6V8W2X5B1C0DEF/entries/01J9ZQ7E1F2G3H4J5K6M7N8P9Q/pay \
  -X POST \
  -H "Authorization: Bearer sk_test_..."
Response
{
  "status": "succeeded",
  "paid": true,
  "entry": {
    "id": "01J9ZQ7E1F2G3H4J5K6M7N8P9Q",
    "subscription_id": "01J9ZQ6M1N2P3Q4R5S6T7V8W9X",
    "price_id": "01J9ZQ5N1P2Q3R4S5T6V7W8XYZ",
    "pay_token": "3a7e1c9f5b2d8a4e6c0f7b3d9a1e5c8f2b6d",
    "starts_at": 1727600000,
    "ends_at": 1730192000,
    "period_index": 0,
    "grace_period_ends_at": 1727686400,
    "paid_at": 1727600100,
    "paused_at": null,
    "in_force": true,
    "created_at": 1727600000
  }
}

Next steps