Skip to main content
The Customer Portal turns billing from a support conversation into a self-service action. A customer with an outstanding invoice pays it from the invoice row. A customer running low on credits tops up, or turns on auto top-up so it never happens again. Cards are collected on the payment provider’s own hosted page, so no card data reaches Flexprice or your servers. Every payment action follows the same shape: the portal asks the API to start the action, the API returns a payment_action describing what to do next, and the portal opens it. Credits and payment records are written by the provider’s webhook once the money actually moves, never optimistically on the customer’s screen.
Portal Overview with the wallet balance and Add credits action above the saved payment methods list

Prerequisites

Portal payment actions require a payment provider connection published in the environment the session token was issued in. Without one, the portal renders its read-only surface and hides the payment actions.
1

Connect a payment provider

Set up a Chargebee, Razorpay or Stripe connection and publish it.
2

Check the capability you need is supported

Not every provider supports every action. See the capability matrix below.
3

Enable the portal sections

In Settings, turn on the sections that carry the actions. See Portal configuration.

Provider capabilities

Flexprice names each payment operation as a capability and resolves the provider per capability from the connections published in the environment. A capability is what Flexprice implements for that gateway, not everything the gateway’s own API can do. Read the capabilities available to a session from GET /v1/customer/portal/integrations:
is_default marks the provider Flexprice picks for that capability when the caller names none.
When exactly one provider serves a capability, the portal never shows its name to the customer. A provider picker appears only when two or more connected providers can serve the same action.
A requested provider must be both connected and capablePassing payment_provider for a gateway that is not connected in the environment returns a validation error. Passing one that is connected but does not implement the capability returns a validation error naming the candidates that do. Omit the field to let Flexprice resolve it.

Paying an invoice

An invoice row in the Invoices section carries a Pay now action when the invoice is awaiting payment. The customer clicks it, a hosted checkout opens in a new tab, and the invoice is marked paid by the provider’s webhook when the payment settles. Endpoint: POST /v1/customer/portal/invoices/{id}/pay
Response:
Read payment_action rather than reaching for a URL field of your own. Whether a provider hands back a hosted checkout page or a payment link is a property of the provider, and the type says which.
The amount comes from the invoiceThere is no amount field in the request. A customer cannot part-pay an invoice from the portal.

When Pay now is not available

The API rejects a payment and the portal disables the action when:
  • The invoice is still a draft, or is voided
  • The invoice payment status is already succeeded, or a payment is already in flight
  • The invoice belongs to a subscription that is not in a state that allows collection. The portal shows a message asking the customer to contact support
Invoices section with a pending invoice row and its action menu open on Pay now

Topping up a wallet

Add credits on the wallet card opens a top-up dialog. The customer enters a credit amount, sees the exact charge stated above the confirm button, and continues. Endpoint: POST /v1/customer/portal/wallets/{id}/top-up
idempotency_key is required on portal top-upsThe server’s fallback key is timestamp-derived, so a retried request without an explicit key reads as a fresh top-up and grants the credits a second time.

Charge now or invoice later

The checkout object decides which of the two flows runs. The portal always sends checkout. The invoiced path is available to your own integrations calling the portal API directly.
Credits land after the payment, not on the clickThe portal reports “Top-up requested, your balance will update once payment completes” rather than claiming success. The wallet transaction is written by the provider’s webhook. After a return, the portal rechecks the balance on a short schedule (immediately, then at 1.5s and 4s) and stops as soon as the wallet actually changes, so a webhook arriving a moment after the redirect still shows up without a manual refresh.

Paying with a saved card

When use_saved_method is true, the charge runs against the card on file with the customer present, and no redirect is involved. The response comes back with checkout_status: "completed" and no payment_action. Read checkout_status, not the presence of a URL: If no usable saved method exists, or the charge declines, the response falls back to a payment_action with a link. The portal shows the saved-card option only when the resolved provider supports auto_charge and a chargeable card is on file.

Minimum top-up amount

Top-ups below the tenant’s per-currency floor are rejected before the customer is redirected, with a message naming the minimum for that currency. Configure the floor under the wallet_topup_config setting key. See Settings.

A top-up already in flight

If the customer already has a pending top-up session on the wallet, the API hands back the session already in flight rather than a conflict. The customer can complete the payment they started instead of being blocked by an abandoned tab.
Add credits dialog showing the credit amount, the charge summary, the payment provider choice and the saved-card toggle

Auto top-up

Auto top-up refills the wallet without the customer present. It reads as a plain-language summary in the Credits section: “Automatically add 100.00whenyourbalancefallsbelow100.00 when your balance falls below 20.00”, with a Manage action, or “Auto top-up is off” with Enable. Endpoint: PUT /v1/customer/portal/wallets/{id}/auto-topup
Clearing a cooldownSend { "value": 0, "unit": "hour" } to remove a stored cooldown. Sending null reads as an absent field on the server and leaves the existing cooldown in place.
Enabling auto top-up is itself the customer’s consent to be charged unattended, so there is no separate auto-charge flag in the portal request. Whether the resulting credits are invoiced is pinned server-side, because it selects the transaction reason and is your decision, not the customer’s.

Requirements

Auto top-up needs a saved card that can be charged off-session. The portal shows “Add a payment method to enable automatic top-ups” and keeps the toggle unavailable until one exists, which means:
  • The resolved provider supports the auto_charge capability (Chargebee or Razorpay)
  • The customer has at least one saved card with can_auto_charge: true
For the mandate rules behind unattended charging, see Autopay mandates.
Auto top-up dialog showing the enable toggle, threshold, top-up amount and the cooloff duration and unit

Saved payment methods

The payment methods list sits on Overview, beside the balance it tops up and the subscriptions it pays for. It shows each saved card as brand and last four digits with its expiry, marks the default, and offers Add card, Set as default and Remove. List: GET /v1/customer/portal/payment-methods

Adding a card

Endpoint: POST /v1/customer/portal/payment-methods
The response returns an action, not a payment method. Nothing is stored until the customer completes the provider’s form.
The portal opens the URL in a new tab and shows the same link on screen, so a customer whose popup blocker stopped the open still has a way through. Card details are entered on the provider’s page and never touch Flexprice.
The setup URL is an API string. The portal refuses to open anything that is not http or https, so a javascript: URL cannot be executed. Apply the same check if you build your own portal against these endpoints.

Setting a default and removing

Set default: POST /v1/customer/portal/payment-methods/default with payment_provider and payment_method_id. Remove: POST /v1/customer/portal/payment-methods/delete with the same two fields. The portal confirms first, warning that the card will no longer be available for payments or automatic top-ups. Setting a default requires the set_default_method capability. On a provider without it, the portal explains that a default card is not supported rather than offering an action that would fail.
Payment methods listing two saved cards, one marked Default, with Set as default and Remove in the row menu

Returning from a hosted checkout

Hosted checkout pages cannot be framed, so the portal opens them in a new tab. That raises a problem worth knowing about if you embed the portal or build your own: the provider redirects the tab it is in, which is the new one, leaving the customer looking at a second copy of their account while the tab they started from still shows pre-payment numbers. The portal resolves this itself:
  1. The return URL is rebuilt from the portal path with the session token removed, so the token never reaches the provider, its redirect logs, or the customer’s browser history at the provider. A checkout_return marker is added in its place, and only recognised portal parameters such as section are carried across.
  2. The tab the provider returns to reads the marker, announces itself over a BroadcastChannel, and closes. The announcement carries no outcome.
  3. The original tab holds the checkout session id and asks the API what happened, so a message from any same-origin page cannot make the portal claim a payment succeeded.
  4. A tab that cannot close, which browsers only permit for script-opened windows, strips the marker and renders as a normal portal instead.
  5. The session token is held in localStorage for 30 minutes so the return trip can restore it. sessionStorage would not survive, because the provider tab is opened with noopener and inherits none.
Checking a session directly: GET /v1/customer/portal/checkout-sessions/{id} returns the session with its checkout_status, failure_reason and linked checkout_invoice_id and checkout_payment_id. POST /v1/customer/portal/checkout-sessions/{id}/cancel abandons one.
Polling gives up after roughly 40 seconds without discarding the session id, so a customer who lingers on the provider’s page still has their result resolved when they come back.

Chargebee as a portal provider

Chargebee is the only connected provider that currently serves every portal capability, including saved-card management and default-card selection. Alongside the portal work, Chargebee invoice sync now pushes Flexprice invoices to Chargebee for collection and reconciles payments back through the Chargebee webhook, so an invoice paid on either side settles on both.

Chargebee invoice sync

Troubleshooting

Customer Portal

Sessions, sections and the security model

Portal configuration

Control which sections and actions appear

Portal API reference

Every endpoint under /v1/customer/portal

Autopay mandates

Mandates behind unattended charging