antiwork/gumroad · error · Purchase::PurchaseInvalid
Something went wrong. Please refresh the page to pre-order t
Error message
Something went wrong. Please refresh the page to pre-order the product.
What it means
In Purchase::CreateService, a purchase for a product still in preorder state must be created as a preorder authorization (purchase.is_preorder_authorization, set by the preorder checkout form/token). If the flag is absent while product.is_in_preorder_state? is true, Purchase::PurchaseInvalid is raised — per the code comment, 'This should never happen unless the request is tampered with': the preorder params were altered, dropped, or built by a client that skipped the preorder flow.
Source
Thrown at app/services/purchase/create_service.rb:75
)
elsif @product.product_refund_policy_enabled? && @product.product_refund_policy.present?
# The enabled flag can be out of sync with the underlying record (the
# ProductRefundPolicy row may have been deleted or never created), so we only
# attach a purchase refund policy when the record actually exists.
purchase.build_purchase_refund_policy(
max_refund_period_in_days: @product.product_refund_policy.max_refund_period_in_days,
title: @product.product_refund_policy.title,
fine_print: @product.product_refund_policy.fine_print
)
end
# build pre-order if purchase is for pre-order product & return
if purchase.is_preorder_authorization
build_preorder(locked_rate: buyer_currency_quote_rate_hint(purchase))
return purchase, nil
elsif product.is_in_preorder_state?
# This should never happen unless the request is tampered with:
raise Purchase::PurchaseInvalid, "Something went wrong. Please refresh the page to pre-order the product."
end
purchase.is_commission_deposit_purchase = product.native_type == Link::NATIVE_TYPE_COMMISSION
# associate correct price for membership product
if product.is_recurring_billing || purchase.is_installment_payment
# For membership products, params[:price_id] should be provided but if
# not, or if a price_id is invalid, associate the default price.
price = params[:price_id].present? ?
product.prices.alive.find_by_external_id(params[:price_id]) :
product.default_price
purchase.price = price || product.default_price
# Check for existing subscriptions (active or restartable)
if should_check_for_restartable_subscription?
existing_purchase, error, sca_response = handle_existing_subscription
return nil, nil, sca_response if sca_response.present?View on GitHub (pinned to afeacbd394)
Solutions
- Reload the product page and start checkout fresh so the preorder form/token is regenerated
- If scripting, GET the checkout page/props first and forward the preorder params untouched
- Never modify or drop the is_preorder_authorization / preorder form fields
- Sellers: verify the product's preorder state matches what the checkout page shows before debugging further
Defensive patterns
Strategy: try-catch
Validate before calling
# Client-side: verify the product's preorder state and fetch a fresh form def fresh_checkout_state?(product) loaded = Product.find_by(unique_permalink: product.unique_permalink) loaded.is_in_preorder_state? == form_preorder_fields_present? end
Try / catch
begin
Purchase::CreateService.new(user: buyer, params: purchase_params).perform
rescue Purchase::PurchaseInvalid => e
if e.message.include?('pre-order')
redirect_to product_path(product) # restart checkout from a fresh page
else
render_error(e.message)
end
end Prevention
- Always start checkout by fetching the live product/checkout payload — never replay a cached or reconstructed form
- Forward preorder form fields (including the authorization flag) untouched; this raise is an anti-tamper tripwire, not a bug to code around
- In automation, detect a product entering preorder state and re-GET the page before the purchase POST
When it happens
Trigger: Modifying the checkout form/hidden fields (e.g. clearing the preorder authorization flag via devtools), a stale cached checkout page from before the product entered/left preorder, or a scripted POST to the purchase endpoint that omits the preorder authorization params.
Common situations: Browser extensions or scripts that rebuild forms; buyers with a long-open tab on a product that just went into preorder; custom automation that doesn't first fetch the live checkout payload.
Related errors
- Sorry, you have not met the offer code's minimum amount.
- Sorry, this offer is no longer available.
- Message is required
- That file is too large. Images can be up to #{MAX_IMAGE_BYTE
- We couldn't download that file, please check the URL and try
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/b10da19735b0f3bb.
Report an issue: GitHub.