Skip to main content

Subscription upgrades and plan history

Userdocks stores subscription history as plan periods. A plan period describes one continuous billing assignment; it is not a payment ledger. Monthly or yearly renewal payments update the current plan period instead of creating new subscription rows.

New subscriptions snapshot inline unitAmount, currency, quantity, description, recurringInterval, recurringIntervalCount, and the provider price ID. Their legacy productPriceUuid is null. Existing legacy identifiers remain readable and usable.

Atomic Stripe upgrades​

When a tenant already has an active Stripe subscription, Userdocks compares the new and current raw unitAmount values. A higher amount in the same currency is an upgrade. Prices in different currencies cannot be compared and the request is rejected.

For an upgrade, Userdocks changes the existing Stripe subscription item. It does not cancel the subscription or create a replacement subscription. The update uses Stripe's proration_behavior: "always_invoice" to invoice the price difference immediately and payment_behavior: "pending_if_incomplete" to make the price change conditional on payment.

  • If payment succeeds, Stripe applies the new item price and Userdocks starts an upgraded plan period.
  • If payment requires authentication or a retry, nextAction.url points to the Stripe hosted invoice. The old price and Userdocks plan period remain active.
  • If payment fails or the pending update expires, Stripe discards the pending price change and Userdocks writes no upgrade history.

Userdocks leaves the billing anchor unchanged when the old and new price have matching cadences. When the recurring cadence changes, Stripe applies its normal interval-change behavior: it resets the billing date, credits unused time, and bills the new interval immediately.

An upgrade request can provide description for the new/remaining-time line and descriptionUnused for the old/unused-time line. Userdocks keeps Stripe's generated prefixes and service-period suffixes and replaces only the product name between on and after. The active plan stores description, and the invoice.created webhook applies it to each later draft renewal line.

Custom-description and discounted upgrades use a controlled invoice and therefore require the old and new prices to have the same cadence. The subscription changes only after that invoice is paid. While payment or customer authentication is pending, the checkout response links to Stripe's hosted invoice. Repeating the same request reuses that pending invoice instead of creating another charge. If no custom description is supplied, cadence-change upgrades retain Stripe's standard behavior.

Downgrades retain the existing end-of-period behavior. Discounted Mollie upgrades use a controlled payment followed by an update of the existing subscription. See Named billing discounts for both providers' proration and renewal rules.

Plan-period fields​

Each subscription plan period includes:

FieldMeaning
statusactive for current access or ended for history.
changeTypestarted, upgraded, downgraded, replaced, or promotion.
startedAt / endedAtEffective boundaries of this price assignment.
transitionInvoiceId / transitionInvoiceUrlImmutable invoice that started this plan period.
transitionAmountPaid / transitionCurrencyImmutable payment information for that transition.
invoiceId / invoiceUrlMost recent successfully reconciled billing-cycle invoice.
paidFrom / paidUntilMost recent successfully reconciled billing cycle.
unitAmount / currencyImmutable minor-unit amount and normalized currency snapshot.
quantity / descriptionImmutable billed quantity and line description.
recurringInterval / recurringIntervalCountImmutable subscription cadence.
providerPriceIdProvider-side price identifier, when one exists.
productPriceUuidNullable deprecated Userdocks legacy-price identifier.

The transition fields and startedAt never change on renewal. A successful renewal updates only the latest invoice and paid-period fields.

Lifecycle and ordering​

A row is created only for a distinct plan period:

  • first paid provider subscription: started;
  • paid upgrade: upgraded;
  • downgrade when it becomes active: downgraded;
  • same-price provider-subscription replacement: replaced;
  • manually granted free subscription: promotion.

When a paid change becomes effective, Userdocks ends the previous active row and creates one new active row. Cancellation or expiration ends the active row without creating a replacement. Webhook retries and synchronous reconciliation are idempotent, so they do not duplicate a plan period.

Tenant subscription list APIs return the active plan first, followed by ended plan periods in reverse startedAt order. External user entitlement responses remain limited to the active plan.

Provider-billed and ended plan periods are audit history and cannot be deleted. Only an active promotional subscription can be permanently deleted.