Subscriptions

Checkout and self-service

How your end users choose a plan and pay, change plan, cancel, resume and change their card, in the customer portal or in your own pages.

Your end users manage their organization's subscription themselves, in the customer portal on your brand domain or in your own pages. The portal and the React components cover part of it; the public API covers all of it, and the portal and the components call the same endpoints, so the rules below apply to each.

ActionCustomer portalReact componentsPublic API
Choose a plan and pay at checkoutYesPricingTablePOST /public/v1/checkout/sessions
Subscribe to a plan without a cardNoNoPOST /public/v1/subscriptions/free
Change planYesPricingTablePOST /public/v1/subscriptions/change
CancelNoSubscriptionPanelPOST /public/v1/subscriptions/cancel
Resume, change the card, see Stripe invoicesNoNoPOST /public/v1/customer-portal/sessions opens Stripe's billing portal
Top up the walletYesNoSee Wallet and top-ups

The customer portal lets end users subscribe and change plan, but not cancel. If your end users subscribe in the portal, give them a way to cancel in your own pages, with SubscriptionPanel or the public API.

Who may do what

  • A buyer organization has at most one subscription in progress at a time: pending payment, active or past due.
  • Starting, changing and cancelling a subscription, opening the billing portal and topping up the wallet need an owner or admin of the organization. Other members are refused with 403 INSUFFICIENT_ROLE; any member can read the subscription.
  • An end user whose email address is not verified cannot check out, subscribe or change plan, and is refused with 403.
  • The organization always comes from the session token, never from the request body.

Subscribe through checkout

POST /public/v1/checkout/sessions takes the plan_version_id of a published version and the success_url and cancel_url to return to, and answers with checkout_url, a Stripe Checkout page on your Stripe account. The subscription is created as PENDING_CONFIRMATION and becomes ACTIVE when Stripe's checkout.session.completed event arrives, usually moments after payment. An end user who returns to success_url first may see the subscription still pending; read GET /public/v1/subscriptions again until it is active.

RefusalMeaning
409 ACTIVE_SUBSCRIPTION_EXISTSThe organization already has an active or past-due subscription. Change plan instead
409 CHECKOUT_PENDING_CONFIRMATIONAn earlier checkout was paid and is waiting for Stripe's event. Wait, then read the subscription
409 CHECKOUT_IN_PROGRESSAnother checkout is being created right now. Retry shortly
400 PLAN_NOT_PUBLISHEDThe version is not published, or has no Stripe price yet
503 PAYMENT_PROVIDER_NOT_CONFIGUREDThe application has no payment connection; see Payments

An abandoned checkout does not block the next one: starting a new checkout replaces a pending one that was not paid, and a checkout left open expires after about a day.

Subscribe without a card

A plan that you set to need no card, as described in Plans, is subscribed with POST /public/v1/subscriptions/free and the plan_version_id. The subscription is ACTIVE at once, Stripe is not involved, and the application needs no payment connection. A plan without the setting is refused with 400 PLAN_NOT_NO_CARD.

PricingTable in version 0.1.0-beta.0 of @shiprev/react subscribes through checkout only. To offer a no-card plan from your own page, call the endpoint yourself, or pass onSelectPlan to PricingTable and call it there.

Change plan

POST /public/v1/subscriptions/change with the target plan_version_id moves an active subscription to another published primary plan of the same product and currency, or to a newer version of its own plan. Stripe prorates the change over the rest of the period and invoices the difference at once. On a move up, the end user is charged the difference; on a move down, Stripe does not refund the difference automatically, as described in Stripe's documentation on prorations.

outcomeWhat happened
APPLIEDThe subscription is on the new version, and its entitlements follow it straight away: capabilities the new version does not grant are removed, and what was used of the others is kept
PAYMENT_REQUIREDThe difference could not be paid with the saved payment method. Send the end user to payment_url; the subscription stays on its current version until the payment succeeds

A change is refused with 400 PLAN_CHANGE_NOT_ALLOWED when the target is in another product or currency or is an add-on plan, or when the subscription has no Stripe subscription behind it, as one without a card. To move from a no-card plan to a paid one, the end user cancels and then subscribes through checkout. A past-due subscription cannot be changed (409 SUBSCRIPTION_NOT_ACTIVE).

Cancel and resume

POST /public/v1/subscriptions/cancel cancels the organization's active subscription. What happens depends on how it was paid:

SubscriptionOn cancelEnds
Paid through checkoutShipRev schedules the cancellation in Stripe. The subscription stays ACTIVE, with canceled_at setAt current_period_end, when Stripe ends it
Without a card, or activated in the operator consoleEnds at once, as CANCELEDImmediately

A subscription paid through checkout stays active until the end of the period it is paid for; until then the end user can resume it in Stripe's billing portal, where they also change their payment method. A subscription without a card ends as soon as it is cancelled. Cancelling again before the period ends changes nothing.

To open Stripe's billing portal, call POST /public/v1/customer-portal/sessions with a return_url on your verified brand domain (https only); it answers with portal_url. The organization needs a subscription that was paid through checkout; otherwise the answer is 409 BUYER_CUSTOMER_NOT_FOUND.

Use the React components

The React package @shiprev/react, version 0.1.0-beta.0, has two components for this; setup is in The React components.

ComponentWhat it does
PricingTableLists each primary plan's latest published version with its price and allowances. With no subscription, a plan's button opens checkout (successUrl and cancelUrl default to the current page). With an active subscription, it switches plan after an inline confirmation and opens payment_url when a payment is required. onSelectPlan hands the choice to your code instead. A signed-out pricing page must pass plans
SubscriptionPanelShows the current plan, its status and the date it renews, ends or ended, with a cancel action that asks for confirmation. allowCancel={false} hides cancelling; pricingUrl adds a link to your pricing page

Before a switch, PricingTable asks only whether to switch, saying that the change takes effect right away. To tell end users that the difference is charged at once, replace that text through the labels prop, with confirmSwitchPrompt.

Neither component resumes a subscription or changes the card; link to the billing portal for those. Both only display and call the public API: whether an end user may use a capability is still decided by your backend.

© 2026 ShipRev