antiwork/gumroad · error · Subscription::UpdateFailed

Changing plans for fixed-length subscriptions is not current

Error message

Changing plans for fixed-length subscriptions is not currently supported.

What it means

Raised as Subscription::UpdateFailed by Subscription#update_current_plan! when the subscription has_fixed_length? — fixed-length subscriptions (e.g. 6-month memberships) do not support plan migration. Like the installment-plan check, it fires before the transaction that would build a new original purchase and archive the existing one, so no partial state is written.

Source

Thrown at app/models/subscription.rb:1033

  end

  def end_subscription!
    with_lock do
      return if ended_at.present?

      self.ended_at = Time.current
      self.deactivate!

      CustomerLowPriorityMailer.subscription_ended(id).deliver_later(queue: "low")
      ContactingCreatorMailer.subscription_ended(id).deliver_later(queue: "critical") if seller.enable_payment_email?
    end
  end

  # creates a new original subscription purchase & archives the existing one.
  # Any changes to the subscription made here must be reverted in `Subscription::UpdaterService#restore_original_purchase`
  def update_current_plan!(new_variants:, new_price:, new_quantity: nil, perceived_price_cents: nil, is_applying_plan_change: false, skip_preparing_for_charge: false, offer_code: nil, clear_discount: false, clear_deleted_discount: false, authenticated_offer_code_buyer: AUTHENTICATED_OFFER_CODE_BUYER_NOT_PROVIDED, submitted_pre_discount_price_cents: nil, once_per_cart_discount_allocation: nil)
    raise Subscription::UpdateFailed, "Installment plans cannot be updated." if is_installment_plan?
    raise Subscription::UpdateFailed, "Changing plans for fixed-length subscriptions is not currently supported." if has_fixed_length?

    ActiveRecord::Base.transaction do
      payment_option = last_payment_option

      # build new original subscription purchase
      new_purchase = build_purchase(override_params: { is_original_subscription_purchase: true,
                                                       email: original_purchase.email,
                                                       is_free_trial_purchase: original_purchase.is_free_trial_purchase },
                                    authenticated_offer_code_buyer:)
      # avoid failing `Purchase#variants_available` validation if reverting back to the original set of variants & those variants are unavailable
      new_purchase.original_variant_attributes = original_purchase.variant_attributes
      # avoid failing `Purchase#price_not_too_low` validation if reverting back to the original subscription price & price has been deleted
      new_purchase.original_price = price
      # avoid `Purchase#not_double_charged` and sold out validations
      new_purchase.is_updated_original_subscription_purchase = true
      # avoid price validation failures when applying a pre-existing plan change (i.e. a downgrade)
      new_purchase.is_applying_plan_change = is_applying_plan_change
      # avoid preparing chargeable, in cases where we simply want to calculate the new price

View on GitHub (pinned to afeacbd394)

Solutions

  1. Exclude fixed-length subscriptions from plan-change flows: check subscription.has_fixed_length? first and skip or offer cancel-and-repurchase.
  2. If the product must support plan changes, recreate it as an open-ended recurring product.
  3. Rescue Subscription::UpdateFailed in jobs (as UpdaterService/RecurringChargeWorker already do) and log it for follow-up.

Example fix

# before
subscription.update_current_plan!(new_variants:, new_price:) # Subscription::UpdateFailed

# after
if subscription.has_fixed_length?
  return { error: "Changing plans for fixed-length subscriptions is not currently supported." }
end
subscription.update_current_plan!(new_variants:, new_price:)
Defensive patterns

Strategy: try-catch

Validate before calling

return { unsupported: true } if subscription.has_fixed_length?

Try / catch

begin
  subscription.update_current_plan!(new_variants:, new_price:)
rescue Subscription::UpdateFailed => e
  log_and_report(e) # fixed-length subscriptions don't support plan migration
end

Prevention

When it happens

Trigger: Calling update_current_plan! (plan change, tier change, or price-change flows including ScheduleMembershipPriceUpdatesJob sweeps) on a subscription created with a fixed length/term.

Common situations: Sellers with mixed recurring + fixed-length products running blanket price updates; customers on fixed-length memberships attempting to switch tiers mid-term; automated plan-migration tooling not filtering on has_fixed_length?.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/5849b33d3dec323f. Report an issue: GitHub.