import { type BillingInterval, type PlanCatalog, type Quantities } from "./plans.js"; import type { BillingAdapter, ResolvedConfig } from "./types.js"; export type PlanChangeTiming = "auto" | "now" | "period_end"; export type ProrationPolicy = "next_invoice" | "invoice_now" | "none"; export type PlanChangeKind = /** No live subscription: a Checkout Session was opened instead. */ "checkout" /** Applied immediately. */ | "updated" /** * Sent, but NOT in force: the invoice it raised has not been paid, so Stripe is holding * the change as a pending update (~23h, then it is dropped). The customer is still on * `plan`. Only `proration: "invoice_now"` can produce this. */ | "pending" /** Takes effect at the end of the paid period. */ | "scheduled" /** Ends at the end of the paid period (a downgrade to a free plan is this). */ | "canceling" /** A pending cancellation was called off. */ | "resumed" /** Already where it was asked to be. */ | "noop"; export interface PlanChangeResult { kind: PlanChangeKind; customerId: string | null; subscriptionId: string | null; /** The plan in force NOW (unchanged for `scheduled` / `canceling`). */ plan: string | null; status: string | null; /** ISO, for `scheduled` / `canceling` — when it takes effect. */ effectiveAt: string | null; /** `checkout` only: mount these with BillingCheckoutSessionProvider. */ sessionId?: string; clientSecret?: string | null; /** `checkout` under `uiMode: "hosted"` — the page to send a customer to, and the * only usable answer for a caller that has no browser. */ checkoutUrl?: string | null; /** `pending` only: when Stripe drops the held change if the invoice is still unpaid. */ pendingUntil?: string | null; /** `pending` only: the unpaid invoice. Settling it is what applies the change. */ invoiceId?: string | null; } export type PlanChangeErrorCode = "multiple_subscriptions" | "unknown_plan" | "not_purchasable" | "invalid_basket" | "needs_return_url" | "no_customer" /** The plan's seat count is fixed at purchase (`sells.seatsFixed`), and this * change only moves quantities on the plan already in force. */ | "seats_fixed"; export declare class PlanChangeError extends Error { readonly code: PlanChangeErrorCode; readonly detail?: unknown | undefined; constructor(code: PlanChangeErrorCode, message: string, detail?: unknown | undefined); } export { planActions, planRank, type PlanActions } from "./ladder.js"; export declare function changePlan(adapter: BillingAdapter, orgId: string, opts: { plans: PlanCatalog; to: { plan: string; interval?: BillingInterval; seats?: Quantities; }; config?: ResolvedConfig; currency?: string; timing?: PlanChangeTiming; proration?: ProrationPolicy; /** * Manual TaxRate ids. Defaults to the rates already on the subscription's * items — which is what stops a newly ADDED seat line being invoiced at 0% * tax on an account that computes tax itself rather than with Stripe Tax. */ taxRates?: string[]; /** Required for the no-subscription case, which opens a Checkout Session. */ returnUrl?: string; /** `"hosted"` makes that session a URL rather than a client secret — what a * caller with no browser needs. See `createCheckoutSession`. */ uiMode?: "elements" | "hosted"; email?: string; metadata?: Record; /** Disambiguates when a customer somehow has more than one live subscription. * Without it that case throws rather than guessing which one to change. */ subscriptionId?: string; /** Write the outcome through the adapter as well as letting the sync engine * mirror it, so the next render is already correct. Default true. */ record?: boolean; }): Promise; /** What a plan change would cost, before committing to it. */ export interface PlanChangePreview { /** What `changePlan` would do with these same arguments. */ kind: "immediate" | "scheduled" | "checkout" | "canceling" | "noop"; currency: string; /** * Charged NOW, in minor units. * * Only `proration: "invoice_now"` charges anything today; the default * (`next_invoice`) defers the prorated difference to the next invoice, and a * period-end change charges nothing at all. Getting this wrong is not a * rounding error — quoting the upcoming invoice as if it were due immediately * told a customer €197.64 for an upgrade that charged €87.84. */ dueNow: number; /** What the NEXT invoice comes to — including any deferred proration. */ nextInvoiceTotal: number; /** The steady-state amount per period once the change has settled. */ recurringTotal: number; /** The credit for the unused remainder of the current plan, positive. */ credit: number; /** When the change takes effect — now, or the period end for a scheduled one. */ effectiveAt: string | null; /** * When `nextInvoiceTotal` will actually be charged. * * Deferring the proration is the kinder default (no payment today, so no SCA * challenge and no upgrade that silently fails to apply), but it produces the * classic surprise: measured on a mid-month €18 → €90 upgrade, the next * invoice is €127.16, not €90. A surface that shows the figure AND the date * turns that from a surprise into a quote, which is the whole reason this * field exists. */ nextInvoiceAt: string | null; /** The proration lines Stripe would write, for an itemised summary. */ lines: Array<{ description: string; amount: number; proration: boolean; }>; } /** * Quote a plan change without making it. * * The number here is the number `changePlan` charges, because both build the * basket with `desiredPrices` and the mutation with `diffItems` — a preview that * recomputed the diff its own way would agree until the day it didn't, and the * day it didn't would be a customer disputing a charge. * * A scheduled (downgrade) change quotes `dueNow: 0` and the new recurring total, * which is what actually happens: the customer keeps what they paid for until it * runs out, and Stripe issues no refund. */ export declare function previewPlanChange(adapter: BillingAdapter, orgId: string, opts: { plans: PlanCatalog; to: { plan: string; interval?: BillingInterval; seats?: Quantities; }; currency?: string; timing?: PlanChangeTiming; /** Must match what you will pass to `changePlan` — it decides whether the * prorated difference is billed today or deferred to the next invoice. */ proration?: ProrationPolicy; taxRates?: string[]; subscriptionId?: string; }): Promise; /** * End the paid subscription at the end of the period it has been paid for. * * A named alias over `changePlan` to the free plan, because "cancel" is what the * copy says and what the customer means. Reversible: calling `changePlan` back to * the current plan resumes it. */ export declare function cancelPlan(adapter: BillingAdapter, orgId: string, opts: { plans: PlanCatalog; currency?: string; record?: boolean; }): Promise; //# sourceMappingURL=subscription.d.ts.map