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
- Refresh the page and resubmit — the form then carries the current perceived prices.
- Sellers: avoid editing a product's price or discounts while buyers are mid-upgrade during launches.
- 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
- Send perceived_price_cents and perceived_upgrade_price_cents from the same server render that displayed the price — never from a cached client value.
- Show the buyer the final amount on the confirm step sourced from the same payload they will submit.
- Schedule seller price edits outside launch windows when buyers are mid-upgrade.
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
- net_negative_seller_revenue
- Offer code cannot be expired
- This commission's deposit is no longer in a completable stat
- Sorry, the price entered is too large.
- Please provide suggested payment options.
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/87a18069af7fb333.
Report an issue: GitHub.