Corpland Revenue

Billing

Put a customer on a plan, change it, cancel it, preview every charge first, or hand them a billing page.

billing.attach

One call for a new subscription, an upgrade, a downgrade, an add on, a one off purchase, prepaid packs or a trial.

Body
{
  "customer_id": "user_123",
  "plan_id": "pro",
  "success_url": "https://app.example.com/billing?done=1",
  "redirect_mode": "if_required",
  "feature_quantities": [{ "feature_id": "seats", "quantity": 5 }],
  "promo_code": "LAUNCH20"
}
FieldMeaning
entity_idAttach the plan to a seat or workspace instead of the customer
redirect_modeif_required (default) charges the saved card when there is one. always sends them to checkout
feature_quantitiesHow many of each prepaid item to buy, rounded up to packs
currencyPrice in this currency; a customer who has paid stays in theirs
free_trialfalse skips the trial even if they are eligible
plan_scheduleimmediate or end_of_cycle overrides upgrade now and downgrade later
customizeA custom deal for this customer: { "price": { "currency": "USD", "amount": 90000 }, "items": [{ "feature_id": "messages", "included": 50000 }] }
carry_over_balances, carry_over_usages{ "enabled": true, "feature_ids"?: [...] } keeps unused balance, or keeps usage so an upgrade does not reset it
promo_codeApplies a reward before the charge
customer_data{ "name", "email" } creates the customer if they do not exist

What happens:

SituationResult
Free plan, or a trial without a cardAttached now, attached: true
Upgrade in the same groupCharged now for the days left, with a credit for unused time on the old plan; the billing date stays
Downgrade in the same groupScheduled for the end of the period, scheduled: true, nothing charged
Saved reusable cardCharged now, attached: true
No saved card, or redirect_mode: "always"payment_url to checkout. The card is saved when they pay
Answer, charged to the saved card
{
  "customer_id": "user_123",
  "plan_id": "premium",
  "action": "upgrade",
  "attached": true,
  "status": "active",
  "payment_url": null,
  "invoice": { "number": "INV-202610-8F2CKQ4M", "status": "paid", "total": 2999, "currency": "USD", "charge_amount": 46485, "charge_currency": "GHS" }
}
Answer, payment needed
{
  "attached": false,
  "payment_url": "https://checkout.paystack.com/abc123",
  "required_action": { "code": "payment_method_required", "reason": "The customer needs to pay at checkout" },
  "invoice": { "...": "..." }
}

required_action.code is payment_method_required, payment_failed (the card was declined; payment_url is a hosted invoice page where they can pay another way) or 3ds_required (their bank wants a confirmation). In every case redirect to payment_url; the plan is granted the moment the payment lands, and never twice.

Usage billed at the end of the period on the old plan is invoiced on its own when the plan changes, so nothing is lost if they abandon checkout.

billing.preview_attach

Same body as attach, nothing is charged. Show it before the customer confirms.

Answer
{
  "action": "upgrade",
  "schedule": "immediate",
  "currency": "USD",
  "line_items": [
    { "kind": "proration", "title": "Remaining time on Premium", "amount": 4999 },
    { "kind": "credit", "title": "Unused time on Pro", "amount": -2000 }
  ],
  "subtotal": 2999,
  "discount_total": 0,
  "credit_applied": 0,
  "total": 2999,
  "charge": { "currency": "GHS", "amount": 46485 },
  "trial": null,
  "next_cycle": { "starts_at": "2026-11-11T13:00:00.000Z", "total": 5000 },
  "payment_method_on_file": true
}

billing.update

Change a plan the customer already has.

BodyEffect
{ "customer_id", "plan_id", "cancel_action": "cancel_end_of_cycle" }Keeps access until the period ends, then their default plan comes back
{ ..., "cancel_action": "cancel_immediately" }Ends now and refunds the unused time to their card
{ ..., "cancel_action": "uncancel" }Undoes a pending cancellation, or removes a scheduled downgrade
{ ..., "feature_quantities": [{ "feature_id": "seats", "quantity": 8 }] }More is charged now for the days left; fewer takes effect at renewal

billing.preview_update takes the same body and answers with line_items, total, refund and effective_at. billing.cancel with { "when": "now" | "end_of_cycle" } still works.

billing.setup_payment

{ "customer_id": "user_123", "success_url"? } returns a payment_url that saves a new card with a small charge refunded at once. Use it when a card is about to expire or after a decline.

billing.open_customer_portal

{ "customer_id": "user_123", "return_url"?: "https://app.example.com/settings" } returns { "url": "...", "expires_in": 3600 }.

The hosted page shows their balances, every plan with what it would cost them now, resume for a plan that is ending, card update, unpaid invoices to pay, past invoices and a promo code field. Links last one hour, so make a fresh one each time someone clicks Billing.

Renewals and failed payments

At the end of each period Revenue builds the renewal invoice (the plan price, prepaid packs for the next period, pay as you go usage from the period that ended, extra seats), takes discounts and account credit off, and charges the saved card. Plan checkout is card only, so this works for every new subscriber. If there is still no reusable card (a subscriber from before card only, or a removed card), the invoice gets a hosted pay link that takes a card and invoice.created fires. A failed renewal makes the plan past_due and retries on day 1, 3 and 5. When the retries run out the invoice is marked uncollectible, the plan ends and their default plan comes back.

On this page