Operations on Stripe Subscription Schedule objects.
A Subscription Schedule lets you define a phased billing timeline — each phase describes the prices, quantities, proration behavior, and trial settings for a slice of time. When one phase ends, the schedule transitions to the next and applies the new configuration to the underlying Subscription.
Creation modes
Stripe accepts two mutually-exclusive parameter shapes on create:
Mode 1: from_subscription
Convert an existing Subscription into a schedule whose first phase captures the subscription's current state.
LatticeStripe.SubscriptionSchedule.create(client, %{
"from_subscription" => "sub_1234567890"
})Mode 2: customer + phases
Build a new schedule from scratch with an explicit phase timeline.
LatticeStripe.SubscriptionSchedule.create(client, %{
"customer" => "cus_1234567890",
"start_date" => "now",
"end_behavior" => "release",
"phases" => [
%{
"items" => [%{"price" => "price_1234567890", "quantity" => 1}],
"iterations" => 12,
"proration_behavior" => "create_prorations"
}
]
})Mixing fields from both modes in a single call raises a Stripe 400 that
surfaces as {:error, %LatticeStripe.Error{type: :invalid_request_error}}.
LatticeStripe does not client-side-validate the mode — Stripe's own error
is already actionable.
cancel vs release
cancel/4(added in Plan 16-02) terminates both the schedule AND the underlying Subscription.release/4(added in Plan 16-02) detaches the schedule from its Subscription; the Subscription remains active and billable but is no longer governed by phases. This is irreversible.
Search
Stripe does not expose a search endpoint for Subscription Schedules
(unlike Subscriptions). This module therefore has no search/3 or
search_stream!/3.
Proration guard
When a client has require_explicit_proration: true, update/4 requires
proration_behavior at either the top level of params or inside any
element of params["phases"][]. Stripe does not accept
proration_behavior at phases[].items[], so the guard does not walk
that deep.
Wired into update/4 only — create/3, cancel/4, and release/4
bypass this guard because Stripe does not accept proration_behavior on
those endpoints.
Telemetry
SubscriptionSchedule operations piggyback on the general
[:lattice_stripe, :request, *] events emitted by Client.request/2. No
schedule-specific events are emitted — state transitions belong to webhook
handlers, not the SDK layer.
Stripe API Reference
See the Stripe Subscription Schedules API.
Summary
Functions
Cancels a Subscription Schedule.
Like cancel/4 but raises on failure.
Creates a new Subscription Schedule.
Like create/3 but raises on failure.
Converts a decoded Stripe API map to a %SubscriptionSchedule{} struct.
Lists Subscription Schedules with optional filters.
Like list/3 but raises on failure.
Releases a Subscription Schedule.
Like release/4 but raises on failure.
Retrieves a Subscription Schedule by ID.
Like retrieve/3 but raises on failure.
Returns a lazy stream of all Subscription Schedules matching the given params.
Updates a Subscription Schedule by ID.
Like update/4 but raises on failure.
Types
@type t() :: %LatticeStripe.SubscriptionSchedule{ application: String.t() | nil, billing_mode: String.t() | nil, canceled_at: integer() | nil, completed_at: integer() | nil, created: integer() | nil, current_phase: LatticeStripe.SubscriptionSchedule.CurrentPhase.t() | nil, customer: LatticeStripe.Customer.t() | String.t() | nil, customer_account: String.t() | nil, default_settings: LatticeStripe.SubscriptionSchedule.Phase.t() | nil, end_behavior: atom() | String.t() | nil, extra: map(), id: String.t() | nil, livemode: boolean() | nil, metadata: map() | nil, object: String.t(), phases: [LatticeStripe.SubscriptionSchedule.Phase.t()] | nil, released_at: integer() | nil, released_subscription: String.t() | nil, status: atom() | String.t() | nil, subscription: LatticeStripe.Subscription.t() | String.t() | nil, test_clock: String.t() | nil }
A Stripe Subscription Schedule.
See the Stripe SubscriptionSchedule object for field definitions.
Functions
@spec cancel(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: {:ok, t()} | {:error, LatticeStripe.Error.t()}
Cancels a Subscription Schedule.
Terminates the schedule AND the underlying Subscription. Both entities move
to canceled status. This is irreversible.
Wire shape
Dispatches POST /v1/subscription_schedules/:id/cancel — unlike
LatticeStripe.Subscription.cancel/4 which uses DELETE. Do not mix them up.
Params
"invoice_now"(boolean) — whether to generate a final invoice immediately for any prorations"prorate"(boolean) — whether to prorate the cancellation
Note: the prorate param here is unrelated to proration_behavior. It
controls whether a proration invoice is generated on cancel, not which
proration strategy applies to a future item change. LatticeStripe does NOT
run check_proration_required/2 on this call.
Contrast with release/4
cancel/4terminates both schedule and subscription.release/4detaches the schedule; the subscription stays active.
Examples
iex> LatticeStripe.SubscriptionSchedule.cancel(client, "sub_sched_123", %{"invoice_now" => true})
{:ok, %LatticeStripe.SubscriptionSchedule{status: "canceled"}}
@spec cancel!(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: t()
Like cancel/4 but raises on failure.
@spec create(LatticeStripe.Client.t(), map(), keyword()) :: {:ok, t()} | {:error, LatticeStripe.Error.t()}
Creates a new Subscription Schedule.
Sends POST /v1/subscription_schedules. See the module @moduledoc for the
two mutually-exclusive parameter shapes (from_subscription vs
customer + phases).
Stripe does not accept proration_behavior on create — schedules prorate
based on start_date mode, not an explicit field — so this function does
NOT run the proration guard.
@spec create!(LatticeStripe.Client.t(), map(), keyword()) :: t()
Like create/3 but raises on failure.
Converts a decoded Stripe API map to a %SubscriptionSchedule{} struct.
Decodes nested typed structs:
current_phase→%LatticeStripe.SubscriptionSchedule.CurrentPhase{}default_settings→%LatticeStripe.SubscriptionSchedule.Phase{}(reused)phases→[%LatticeStripe.SubscriptionSchedule.Phase{}]
Unknown top-level fields are collected into :extra.
@spec list(LatticeStripe.Client.t(), map(), keyword()) :: {:ok, LatticeStripe.Response.t()} | {:error, LatticeStripe.Error.t()}
Lists Subscription Schedules with optional filters.
Sends GET /v1/subscription_schedules.
@spec list!(LatticeStripe.Client.t(), map(), keyword()) :: LatticeStripe.Response.t()
Like list/3 but raises on failure.
@spec release(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: {:ok, t()} | {:error, LatticeStripe.Error.t()}
Releases a Subscription Schedule.
Detaches the schedule from its Subscription. The Subscription remains
active and billable but is no longer governed by phases — subsequent
configuration changes must go through LatticeStripe.Subscription.update/4
directly.
This is irreversible. Once a schedule is released, there is no API to re-attach it. If you need to regain phase-based control, you must create a new schedule from the now-detached subscription (if policy allows).
Contrast with cancel/4
release/4detaches the schedule; the subscription continues billing but is no longer phase-governed.cancel/4terminates BOTH the schedule AND the underlying subscription.
Use release/4 when you want to graduate a subscription off a phased plan
into a flat ongoing subscription. Use cancel/4 when you want to end
billing entirely.
Wire shape
Dispatches POST /v1/subscription_schedules/:id/release.
Params
"preserve_cancel_date"(boolean) — if the underlying subscription had a scheduled cancellation date, whether to preserve it on release
Examples
iex> LatticeStripe.SubscriptionSchedule.release(client, "sub_sched_123")
{:ok, %LatticeStripe.SubscriptionSchedule{status: "released"}}
@spec release!(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: t()
Like release/4 but raises on failure.
@spec retrieve(LatticeStripe.Client.t(), String.t(), keyword()) :: {:ok, t()} | {:error, LatticeStripe.Error.t()}
Retrieves a Subscription Schedule by ID.
Sends GET /v1/subscription_schedules/:id.
@spec retrieve!(LatticeStripe.Client.t(), String.t(), keyword()) :: t()
Like retrieve/3 but raises on failure.
@spec stream!(LatticeStripe.Client.t(), map(), keyword()) :: Enumerable.t()
Returns a lazy stream of all Subscription Schedules matching the given params.
Auto-paginates via LatticeStripe.List.stream!/2. Raises on fetch failure.
@spec update(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: {:ok, t()} | {:error, LatticeStripe.Error.t()}
Updates a Subscription Schedule by ID.
Sends POST /v1/subscription_schedules/:id.
Proration guard
When the client has require_explicit_proration: true, this function
runs the Billing proration guard BEFORE dispatching the request. Params
must carry "proration_behavior" at
the top level OR at phases[].proration_behavior (but NOT
phases[].items[] — Stripe does not accept it there).
@spec update!(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: t()
Like update/4 but raises on failure.