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.AbortSignal into a request body.
Issue the portal session
On the authenticated merchant backend, call Core’sGET /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:
- Load policy-authorized targets for the selected subscription. The public catalog alone does not establish eligibility.
- Show the backend quote, including amount due now, currency, effective date and renewal date.
- Apply an upgrade through quoted checkout. Apply a downgrade through the existing end-of-period transition with
keep_anchor. - Reload persisted state after submission, payment, uncertainty or a page reload. Keep the original idempotency key when retrying the same action.
needs_review, direct the customer to your support route before another collection attempt.
Use individual controls
ImportCancellationControl 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
- Node merchant server and React app
- Python merchant server
- Node method reference and Python method reference