CentraPoint

Guides

Account standing

How a subscription platform uses CentraPoint to require a card on paid plans, show grace-period banners and lock an account to billing until it is paid.

On this page

Overview#

CentraPoint works out each customer's account standing from their subscriptions, saved payment methods and invoices: ok, grace (something is due; show a banner) or locked (the grace period ended; limit the account to billing). Your platform reads it from the API or follows the customer.standing.changed webhook, shows the banner and enforces the lock. CentraPoint never blocks your users itself.

It works together with saved cards: customers add a card on a hosted page, and the standing response gives you the link to send them to (links.addCardUrl) and the link to pay what is overdue (links.payUrl).

The rules#

Standing rules
SituationGraceThen
A new customer on a paid plan without a payment method3 days from the start of the paid planLocked until a payment method is added
An invoice past its due date with a balance3 days from the due dateLocked until the invoice is paid
Customers who existed when the rules were switched on14 days from that date before any rule appliesThe rules above
Free plans-No payment method needed (invoices still count)

Locked means billing only: the customer can sign in, see and pay their invoices and add a card; everything else on your platform is blocked and their data is kept. Paying or adding a card unlocks at once: standing is calculated on every request. An invoice whose card payment is with the bank does not count while that payment is pending; if the bank rejects it, the account is locked again. With automatic collection on, an invoice CentraPoint would charge to the customer's default card but cannot yet (saved cards cannot be charged until a card debit gateway is set up) does not count either: the customer is never locked because a charge could not be made.

Settings#

The rules are off until you switch them on under Settings → Cards & standing; while off the standing is always ok (rulesEnabled: false). The day you first switch them on is the go-live date the existing customers' 14 days count from.

Settings → Cards & standing
SettingDefault
Apply the grace and lock rulesOff
Paid plans need a payment methodOn
Grace period (days)3
Existing customers (days)14
Offer saved cards on the pay pageOn
Charge the default card when an invoice falls dueOff
  • Paid plan: a customer subscription that is trialing, active, past due or paused with a price above zero (including subscriptions your platform bills itself and mirrors into CentraPoint). If you keep plans only in your own system, send paidPlan=true or paidPlan=false with the request.
  • Payment method: a usable saved card (not expired, failed or removed), an active debit order mandate, or a card subscription the gateway bills.
  • Outstanding: sent or overdue invoices with a balance (pro forma invoices are not counted).

Get a customer's standing#

GET/api/v1/customers/{id}/standing
Query parameters
FieldTypeDescription
returnUrloptionalstringWhere the add-card page returns the customer (https, on an allowed origin). links.addCardUrl reuses an open add-card session for the same return URL, so polling does not create new ones.
paidPlanoptionalbooleantrue / false: overrides the paid-plan check (plans kept outside CentraPoint).
curl -X GET "https://app.centrapoint.co.za/api/v1/customers/cmg2c0s7t0003cust0001abcd/standing?returnUrl=https%3A%2F%2Fplatform.example.co.za%2Fbilling" \
  -H "Authorization: Bearer $CENTRAPOINT_API_KEY"
200 OK
{
  "object": "customer_standing",
  "customerId": "cmg2c0s7t0003cust0001abcd",
  "status": "grace",
  "reason": "unpaid_invoice",
  "reasons": [
    "unpaid_invoice"
  ],
  "graceEndsAt": "2026-10-08T07:00:00.000Z",
  "lockedSince": null,
  "lockMode": "billing_only",
  "rulesEnabled": true,
  "rulesApplyFrom": null,
  "paidPlan": true,
  "hasPaymentMethod": true,
  "paymentMethods": {
    "cards": 1,
    "defaultCard": {
      "object": "card",
      "id": "cmgcard00001visa4242abcd",
      "customerId": "cmg2c0s7t0003cust0001abcd",
      "brand": "visa",
      "last4": "4242",
      "expiryMonth": 12,
      "expiryYear": 2028,
      "holderName": "T NKOSI",
      "isDefault": true,
      "status": "active",
      "expiring": false,
      "source": "card_setup",
      "createdAt": "2026-09-01T08:00:00.000Z",
      "lastUsedAt": "2026-09-01T08:00:00.000Z"
    },
    "debitOrder": false,
    "gatewaySubscription": false
  },
  "outstanding": {
    "amount": 299,
    "overdueAmount": 299,
    "currency": "ZAR",
    "invoiceIds": [
      "cmg4i5n6v0001inv0001abcd"
    ],
    "invoices": [
      {
        "id": "cmg4i5n6v0001inv0001abcd",
        "number": "INV-000123",
        "dueDate": "2026-10-05T07:00:00.000Z",
        "balance": 299,
        "currency": "ZAR",
        "overdue": true,
        "paymentPending": false,
        "awaitingCollection": false
      }
    ]
  },
  "links": {
    "addCardUrl": "https://app.centrapoint.co.za/card/Q2x9…",
    "payUrl": "https://app.centrapoint.co.za/pay/k7Rt…"
  },
  "checkedAt": "2026-10-06T09:00:00.000Z"
}

The standing object#

Standing fields
FieldTypeDescription
statusrequiredstringok, grace or locked.
reasonrequiredstring | nullno_payment_method or unpaid_invoice: what locked the account, or what will lock it first.
reasonsrequiredstring[]Every reason that applies now.
graceEndsAtrequiredstring (date-time) | nullWhen the (first) grace period ends or ended.
lockedSincerequiredstring (date-time) | nullSet while locked.
lockModerequiredstringbilling_only.
rulesEnabledrequiredbooleanThe rules are switched on.
rulesApplyFromrequiredstring (date-time) | nullExisting customers: the end of their 14 days.
paidPlanrequiredbooleanThe customer is on a paid plan.
hasPaymentMethodrequiredbooleanA usable saved card, an active debit order or a gateway card subscription.
paymentMethodsrequiredobjectcards (usable saved cards), defaultCard (a card or null), debitOrder, gatewaySubscription.
outstandingrequiredobjectamount, overdueAmount, currency, invoiceIds and invoices (id, number, dueDate, balance, overdue, paymentPending, awaitingCollection: waiting for an automatic card charge that cannot be made yet, not counted).
links.addCardUrlrequiredstring | nullHosted add-card page for the customer (null when cards cannot be saved).
links.payUrlrequiredstring | nullPay page of the oldest outstanding invoice (null when nothing is outstanding).
checkedAtrequiredstring (date-time)When this was calculated.

Integrating your platform#

Sign-up: save a card#

  1. Create or update the customer (upsert by externalReference) and their subscription.
  2. For a paid plan, call POST /api/v1/customers/{id}/cards/setup with a returnUrl back into your sign-up flow and redirect the customer to url. They pay R1.00, which is credited to their next invoice.
  3. On return (?cardSetup=…&status=…), confirm with the session or the customer.payment_method.added webhook. If they skipped it, the grace period gives them 3 days.

Read the standing when the customer signs in (and cache it briefly, or keep it from the webhook). While status is grace, show a banner:

Banner text
reasonBannerButton
no_payment_methodAdd a card by graceEndsAt to keep using your account.links.addCardUrl
unpaid_invoiceInvoice number is overdue: pay by graceEndsAt to keep full access.links.payUrl

Paid plans without a payment method can show an "add a card" banner even when the rules are off (paidPlan && !hasPaymentMethod).

Locking to billing#

When status is locked, let the customer sign in and use your billing screens only: see invoices, pay (links.payUrl) and add a card (links.addCardUrl). Block everything else (your API for them included) with a clear message, and keep their data.

Unlocking#

Paying the overdue invoice (on the pay page, by EFT recorded in CentraPoint, or by a card payment the bank confirms) or adding a card changes the standing at once; customer.standing.changed with change: "unlocked" follows. Remove the lock when the standing is ok or grace.

Platforms with their own invoices#

The standing endpoint looks at invoices in CentraPoint. If your platform keeps its own plans and invoices (and takes payments through payment links and saved card charges), work out the standing in your platform with the same rules, and use CentraPoint only for the payment method:

  • Payment method: the customer has a card with status: "active" in GET /api/v1/customers/{id}/cards (keep it up to date from customer.payment_method.* webhooks).
  • No payment method on a paid plan: grace until the plan started (sign-up or upgrade) + 3 days, then locked to billing.
  • Unpaid invoice: grace until its due date + 3 days, then locked to billing. An invoice with a saved card charge still pending does not count until that charge fails. If you cannot charge saved cards yet (capabilities.chargeSavedCards is false) and the customer has a card, do not lock them for an invoice you would have charged.
  • Existing customers (signed up before you switch the rules on): nothing locks before your go-live date + 14 days.
  • Locked to billing: as above: sign in, see and pay invoices, add a card; everything else blocked, data kept. Unlock as soon as the invoice is paid or a card is added.

You can still call the standing endpoint with paidPlan=true for its hasPaymentMethod and links.addCardUrl, but its status does not include your own invoices.

Webhooks#

customer.standing.changed is sent when a customer's standing changes. data is the standing object (without links values) plus change, previous and customer:

change values
changeMeaning
grace_startedWas ok, now in grace.
lockedThe grace period ended.
unlockedWas locked, now grace or ok.
resolvedWas in grace, now ok.
reason_changedSame status, another reason.
customer.standing.changed
{
  "id": "evt_7f3b19c9e1a5d2b8f4c6e0a3",
  "type": "customer.standing.changed",
  "created": "2026-10-08T07:05:00.000Z",
  "data": {
    "object": "customer_standing",
    "customerId": "cmg2c0s7t0003cust0001abcd",
    "status": "locked",
    "reason": "unpaid_invoice",
    "reasons": [
      "unpaid_invoice"
    ],
    "graceEndsAt": "2026-10-08T07:00:00.000Z",
    "lockedSince": "2026-10-08T07:00:00.000Z",
    "lockMode": "billing_only",
    "rulesEnabled": true,
    "rulesApplyFrom": null,
    "paidPlan": true,
    "hasPaymentMethod": true,
    "paymentMethods": {
      "cards": 1,
      "defaultCard": {
        "object": "card",
        "id": "cmgcard00001visa4242abcd",
        "customerId": "cmg2c0s7t0003cust0001abcd",
        "brand": "visa",
        "last4": "4242",
        "expiryMonth": 12,
        "expiryYear": 2028,
        "holderName": "T NKOSI",
        "isDefault": true,
        "status": "active",
        "expiring": false,
        "source": "card_setup",
        "createdAt": "2026-09-01T08:00:00.000Z",
        "lastUsedAt": "2026-09-01T08:00:00.000Z"
      },
      "debitOrder": false,
      "gatewaySubscription": false
    },
    "outstanding": {
      "amount": 299,
      "overdueAmount": 299,
      "currency": "ZAR",
      "invoiceIds": [
        "cmg4i5n6v0001inv0001abcd"
      ],
      "invoices": [
        {
          "id": "cmg4i5n6v0001inv0001abcd",
          "number": "INV-000123",
          "dueDate": "2026-10-05T07:00:00.000Z",
          "balance": 299,
          "currency": "ZAR",
          "overdue": true,
          "paymentPending": false,
          "awaitingCollection": false
        }
      ]
    },
    "links": {
      "addCardUrl": "https://app.centrapoint.co.za/card/Q2x9…",
      "payUrl": "https://app.centrapoint.co.za/pay/k7Rt…"
    },
    "checkedAt": "2026-10-06T09:00:00.000Z",
    "change": "locked",
    "previous": {
      "status": "grace",
      "reason": "unpaid_invoice"
    },
    "customer": {
      "id": "cmg2c0s7t0003cust0001abcd",
      "externalReference": "platform-org:42"
    }
  }
}

Changes caused by time passing (a grace period ending) are picked up by a scheduled check, so the webhook can come some time after graceEndsAt. The standing endpoint is always current. See Webhooks for signatures, retries and ordering, and saved card events.

Checklist#

  • Allow your platform's origin under Settings → API keys → Allowed return URLs.
  • Check GET /api/v1/me: capabilities.savedCards and capabilities.accountStanding.
  • Paid plan sign-ups go to the add-card page.
  • Show the grace banner with the right link; enforce the billing-only lock on the server.
  • Subscribe to customer.standing.changed and customer.payment_method.*.
  • Switch the rules on under Settings → Cards & standing when your platform is ready.