LatticeStripe.SubscriptionSchedule (LatticeStripe v1.7.5)

Copy Markdown View Source

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.

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

Types

t()

A Stripe Subscription Schedule.

Functions

Cancels a Subscription Schedule.

Creates a new Subscription Schedule.

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.

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.

Types

t()

@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

cancel(client, id, params \\ %{}, opts \\ [])

@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/4 terminates both schedule and subscription.
  • release/4 detaches the schedule; the subscription stays active.

Examples

iex> LatticeStripe.SubscriptionSchedule.cancel(client, "sub_sched_123", %{"invoice_now" => true})
{:ok, %LatticeStripe.SubscriptionSchedule{status: "canceled"}}

cancel!(client, id, params \\ %{}, opts \\ [])

@spec cancel!(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: t()

Like cancel/4 but raises on failure.

create(client, params \\ %{}, opts \\ [])

@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.

create!(client, params \\ %{}, opts \\ [])

@spec create!(LatticeStripe.Client.t(), map(), keyword()) :: t()

Like create/3 but raises on failure.

from_map(map)

@spec from_map(map() | nil) :: t() | nil

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.

list(client, params \\ %{}, opts \\ [])

@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.

list!(client, params \\ %{}, opts \\ [])

Like list/3 but raises on failure.

release(client, id, params \\ %{}, opts \\ [])

@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/4 detaches the schedule; the subscription continues billing but is no longer phase-governed.
  • cancel/4 terminates 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"}}

release!(client, id, params \\ %{}, opts \\ [])

@spec release!(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: t()

Like release/4 but raises on failure.

retrieve(client, id, opts \\ [])

@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.

retrieve!(client, id, opts \\ [])

@spec retrieve!(LatticeStripe.Client.t(), String.t(), keyword()) :: t()

Like retrieve/3 but raises on failure.

stream!(client, params \\ %{}, opts \\ [])

@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.

update(client, id, params \\ %{}, opts \\ [])

@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).

update!(client, id, params \\ %{}, opts \\ [])

@spec update!(LatticeStripe.Client.t(), String.t(), map(), keyword()) :: t()

Like update/4 but raises on failure.