Skip to main content
BillingPortal displays subscriptions, usage, invoice history/PDFs, billing details, and wallets. Add cancellationActions for Cancel/Keep and planChangeActions for upgrades, scheduled downgrades, and withdrawal of a pending change. These controls also work independently on a custom billing page.
These exports and subscription-management additions are merged into the SDK repositories but await a package release. The published React 0.9.0, Node 0.8.0, and Python 0.8.0 packages do not include these additions. Use the merged source examples below to evaluate them. The matching hosted backend endpoints are available.

How requests flow

Your backend authenticates the user and chooses the Nozle customer. The browser sends the selected external subscription ID, and your backend and Nozle validate its ownership. Provider secret keys stay in Nozle’s saved connection. Embedded Stripe can also require its matching public key. Portal reads use a customer-scoped session issued by your backend. Subscription changes use authenticated merchant callbacks. BillingPortal requires neither BillingProvider nor Tailwind.

Connect the portal

Serve these example merchant routes on the same HTTPS origin as your app, or proxy them there. Keep your application’s session and CSRF protection. The route names below are examples from the SDK repositories, not public Nozle API paths.
The callbacks above are stable across renders. Remount the portal when the authenticated account changes so the previous customer’s session, dialogs, and in-flight work are discarded. Never serialize AbortSignal into a request body.

Issue the portal session

On the authenticated merchant backend, call Core’s GET /api/v1/customers/{external_id}/portal_url with a server secret key. Use the customer from your application session. Parse the token from the returned /customer-portal/{token} URL, validating the expected URL origin and path, then return:
apiUrl is the Core base without /graphql. The component sends customer-portal-token on GraphQL requests and keeps the token in memory. Return session responses with Cache-Control: no-store; do not embed a token in static JavaScript or store it in local storage. Self-hosted Core must allow your app origin and the portal header through CORS.

Portal props

Customize with --nozle-portal-accent, --nozle-portal-background, --nozle-portal-text, --nozle-portal-muted, --nozle-portal-muted-background, and --nozle-portal-border.

Cancel and Keep

CancellationActions.preview receives { subscriptionId, operation, signal }, where operation is cancel or uncancel. Return { operation, effectiveAt, renewalAt } from the backend preview. Dates are ISO strings and renewalAt may be null. apply receives the same identity/operation plus idempotencyKey and, for cancellation, expectedEffectiveAt. Choose end-of-period cancellation on your server and forward the exact preview timestamp, including its timezone and fractional seconds. A changed date returns HTTP 409 and requires a new confirmation. The subscription remains active until its saved ending time. Keep removes the ending before it takes effect; it does not restart a terminated subscription. Cancellation clears a pending downgrade, and Keep does not recreate that removed change.

Upgrade, downgrade, and recover payment

The merchant adapter converts SDK responses into the exported React types: PlanChangeState includes subscriptionId, status, currentPlan, endingAt, pendingChange, eligiblePlans, blockedReason, checkout, and checkoutStatus. Plans use { code, name, amountCents, currency, interval }. Each eligible plan adds operation: "upgrade" | "downgrade"; a pending change is { id, plan, effectiveAt } or null. checkoutStatus is none, awaiting_payment, processing, succeeded, failed, expired, or needs_review. PlanChangePreview contains operation, timing, currency, creditAmountCents, debitAmountCents, netAmountCents, amountDueNowCents, amountDueAtEffectiveCents, effectiveAt, renewalAt, and quoteToken. Use the SDK’s exact minor-unit values and dates; do not recompute proration in the browser or divide every currency by 100. quoteToken maps to the SDK’s opaque quote_id response and quoteId/quote_id request argument. The SDK responses use snake_case while these React adapters use camelCase. The runnable merchant examples below implement that mapping, eligible-plan validation, cancellation guards, checkout reconciliation and replay storage. Do not return an unmodified SubscriptionOptions object as PlanChangeState. For each confirmed change:
  1. Load policy-authorized targets for the selected subscription. The public catalog alone does not establish eligibility.
  2. Show the backend quote, including amount due now, currency, effective date and renewal date.
  3. Apply an upgrade through quoted checkout. Apply a downgrade through the existing end-of-period transition with keep_anchor.
  4. Reload persisted state after submission, payment, uncertainty or a page reload. Keep the original idempotency key when retrying the same action.
A downgrade stays pending until renewal. Withdrawal targets its exact internal pending UUID; it does not cancel the active subscription. A stale pending ID conflicts instead of removing a replacement change. A Razorpay payment must be captured before the upgrade is fulfilled. A Stripe callback or redirect alone does not activate the plan. Show success after authoritative state confirms activation; a recoverable checkout can resume after reload. For needs_review, direct the customer to your support route before another collection attempt.

Use individual controls

Import CancellationControl and PlanChangeControl for your own billing layout. The cancellation control needs both the current subscription and a persisted-state refresh; the plan control loads its state through the adapter.
CancellationSubscription contains the Core id, external externalId, name, status, endingAt, and optional pendingPlanName. Use the external ID for management requests; retain the internal UUID for portal reads. Refresh both controls and change refreshKey when another control updates the subscription. BillingPortal handles this coordination when they are embedded in it. Both controls also accept locale, timezone, onError, and nonce.

Runnable backend integrations

Build the React package from the linked source revision and install that build for evaluation; installing the current registry version does not provide these exports. Follow the examples’ READMEs for TLS and environment setup. Their demo login and single-process file replay store must be replaced with your application’s authentication and transactional shared storage before serving production customers. Browser sessions use Secure, HttpOnly cookies over HTTPS. The portal changes billing state. Enforce feature access on your application’s server using Nozle entitlement checks. The hosted Engine observes cancellation at the saved ending boundary and Keep after commit, without waiting for its old subscription-cache refresh.