antiwork/gumroad · error · Purchase::PurchaseInvalid
The product's variants have changed, please refresh the page
Error message
The product's variants have changed, please refresh the page!
What it means
Each external_id in params[:variants] is looked up in product.current_base_variants during build_purchase; an id that no longer resolves means the variant was deleted, archived, or replaced since checkout loaded. The message is added to purchase.errors and raised so bundle-checkout error rendering has a purchase object to attach it to.
Source
Thrown at app/services/purchase/create_service.rb:453
purchase.offer_code = product.find_offer_code(code: purchase.discount_code.downcase.strip) if purchase.discount_code.present?
if purchase.offer_code.present? && product.default_offer_code.present? && purchase.offer_code.id == product.default_offer_code.id
purchase.default_offer_code_id = purchase.offer_code.id
end
purchase.business_vat_id = (params_for_purchase[:business_vat_id] && params_for_purchase[:business_vat_id].size > 0 ? params_for_purchase[:business_vat_id] : nil)
purchase.is_original_subscription_purchase = (product.is_recurring_billing && !params_for_purchase[:is_gift_receiver_purchase]) || purchase.is_installment_payment
purchase.is_free_trial_purchase = product.free_trial_enabled? && !is_gift?
purchase.should_exclude_product_review = product.free_trial_enabled? && !is_gift?
Shipment.create(purchase:) if should_ship
if params[:variants].present?
params[:variants].each do |external_id|
variant = product.current_base_variants.find_by_external_id(external_id)
if variant.present?
purchase.variant_attributes << variant
else
purchase.errors.add(:base, "The product's variants have changed, please refresh the page!")
raise Purchase::PurchaseInvalid, "The product's variants have changed, please refresh the page!"
end
end
elsif product.is_tiered_membership
purchase.variant_attributes << product.tiers.first
elsif product.is_physical && product.skus.is_default_sku.present?
purchase.variant_attributes << product.skus.is_default_sku.first
end
if product.native_type == Link::NATIVE_TYPE_CALL
start_time = Time.zone.parse(params[:call_start_time] || "")
duration_in_minutes = purchase.variant_attributes.first&.duration_in_minutes
if start_time.blank? || duration_in_minutes.blank?
raise Purchase::PurchaseInvalid, "Please select a start time."
end
end_time = start_time + duration_in_minutes.minutes
purchase.build_call(start_time:, end_time:)View on GitHub (pinned to afeacbd394)
Solutions
- Refresh the checkout page and re-select a current variant.
- Integrators: re-fetch the product's current_base_variants at submit time and drop ids that no longer resolve.
- On this raise, reload the variant list and restart checkout rather than retrying the same params.
Example fix
# before: stale variant id persisted from an earlier page state params[:variants] = ['var_stale123'] # after: keep only ids that still resolve against the product params[:variants] = params[:variants] & product.current_base_variants.pluck(:external_id)
Defensive patterns
Strategy: validation
Validate before calling
current_ids = product.current_base_variants.pluck(:external_id)
params[:variants] = params[:variants]&.select { current_ids.include?(_1) } Type guard
def variants_current?(variant_ids, product) (variant_ids.to_a - product.current_base_variants.pluck(:external_id)).empty? end
Try / catch
begin Purchase::CreateService.new(product:, params:, buyer:).perform rescue Purchase::PurchaseInvalid => e # variant set changed: reload variants, force re-selection, restart checkout redirect_to_checkout if e.message == "The product's variants have changed, please refresh the page!" end
Prevention
- Re-fetch current_base_variants at submit time and drop ids that no longer resolve.
- Never cache variant ids across product edits in integrations.
When it happens
Trigger: params[:variants] contains at least one external_id for which product.current_base_variants.find_by_external_id returns nil.
Common situations: Seller edits or removes variants mid-checkout; client persisting a stale variant selection; integrations caching variant ids across product edits.
Related errors
- The cart does not have any products to which the upsell appl
- Invalid free trial information provided. Please try again.
- The product's free trial has changed, please refresh the pag
- The bundle's contents have changed. Please refresh the page!
- The product was just released. Refresh the page to purchase
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/d277170a6e308e28.
Report an issue: GitHub.