antiwork/gumroad · error · Subscription::UpdateFailed

The price just changed! Refresh the page for the updated pri

Error message

The price just changed! Refresh the page for the updated price.

What it means

validate_perceived_prices_match compares the server-computed new recurring price (new_price_cents) and prorated amount owed (amount_owed) against the browser-submitted perceived_price_cents and perceived_upgrade_price_cents. Any mismatch aborts the update with "The price just changed! Refresh the page for the updated price." — a guard ensuring the buyer is never charged a different amount than the page displayed. The full computed values are logged with the subscription id.

Source

Thrown at app/services/subscription/updater_service.rb:277

      UpdateIntegrationsOnTierChangeWorker.perform_async(subscription.id)
    end

    subscription.send_restart_notifications! if is_resubscribing && result[:success] && !result[:requires_card_action] && terminated_or_scheduled_for_termination

    result
  end

  private
    def validate_params
      return if !tiered_membership? || (variants.present? && price.present?)

      "Please select a valid tier and payment option."
    end

    def validate_perceived_prices_match
      unless new_price_cents == params[:perceived_price_cents] && amount_owed == params[:perceived_upgrade_price_cents]
        logger.info("SubscriptionUpdater: Error updating subscription - perceived prices do not match: id: #{subscription.external_id} ; new_price_cents: #{new_price_cents} ; amount_owed: #{amount_owed}")
        raise Subscription::UpdateFailed, "The price just changed! Refresh the page for the updated price."
      end
    end

    def purchase_perceived_price_cents(original_discount)
      return params[:price_range] unless pwyw? && params[:price_range].present?

      fixed_once_per_cart = if params[:offer_code].present?
        params[:offer_code].is_cents? && params[:offer_code].once_per_cart?
      else
        original_discount.present? && !original_discount.offer_code_is_percent && original_discount.once_per_cart?
      end
      return params[:price_range] unless fixed_once_per_cart

      params[:perceived_price_cents]
    end

    def should_clear_original_discount?
      params[:offer_code].blank? && original_purchase.offer_code&.deleted?

View on GitHub (pinned to afeacbd394)

Solutions

  1. Refresh the page and resubmit — the form then carries the current perceived prices.
  2. Sellers: avoid editing a product's price or discounts while buyers are mid-upgrade during launches.
  3. API integrators: always send perceived_price_cents / perceived_upgrade_price_cents read from the same server render that displayed the price, never hardcoded values.

Example fix

# before: one-shot call that loses the buyer's context on mismatch
result = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform

# after: treat the mismatch as a signal to re-fetch and re-confirm
result = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform
if !result[:success] && result[:error_message].include?("price just changed")
  re_render_form_with_current_prices # buyer confirms the new amount, then resubmits
end
Defensive patterns

Strategy: validation

Validate before calling

# Server-side: verify perceived prices match before opening the transaction
expected_price = new_price_cents
expected_owed = amount_owed
params[:perceived_price_cents] == expected_price && params[:perceived_upgrade_price_cents] == expected_owed

Try / catch

result = Subscription::UpdaterService.new(...).perform
if !result[:success] && result[:error_message].include?("price just changed")
  # re-fetch current price/tier data, re-render the confirmation, let the buyer approve the new amount
end

Prevention

When it happens

Trigger: Plan change or overdue-charge update submitted after the seller changed the tier price, recurrence, or offer code between page render and submit; a once-per-cart or percent discount edited/deleted concurrently; amount_owed recomputed differently because overdue_for_charge, trial state, or discount allocation changed since the form was rendered.

Common situations: Buyer leaves an upgrade tab open for hours or days before confirming; seller edits price mid-sale while buyers are in checkout; double-submit of a stale form after a price change; offer code amount modified by creator between add-to-form and submit.

Related errors


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