antiwork/gumroad · error · Purchase::PurchaseInvalid
The cart does not have any products to which the upsell appl
Error message
The cart does not have any products to which the upsell applies.
What it means
Raised by Gumroad's Purchase::CreateService when a checkout submits params[:accepted_offer] for a cross-sell upsell that keeps (rather than replaces) the buyer's selected products, but no product remaining in the cart - excluding the product being purchased - falls inside the upsell's scope. The scope is the seller's entire catalog when the upsell is universal, otherwise its explicitly selected_products. It prevents a stale or tampered accepted offer from applying a cross-sell discount to a cart that no longer contains anything the upsell covers.
Source
Thrown at app/services/purchase/create_service.rb:118
valid_items = if purchase.offer_code.universal
excluded_permalinks = purchase.offer_code.excluded_products.pluck(:unique_permalink)
valid_items.reject { excluded_permalinks.include?(_1[:permalink]) }
else
valid_items.filter { purchase.offer_code.products.find_by(unique_permalink: _1[:permalink]).present? }
end
if valid_items.map { _1[:price_cents].to_i }.sum < purchase.offer_code.minimum_amount_cents
raise Purchase::PurchaseInvalid, "Sorry, you have not met the offer code's minimum amount."
end
end
if params[:accepted_offer].present?
upsell = Upsell.available_to_customers.find_by_external_id(params[:accepted_offer][:id])
raise Purchase::PurchaseInvalid, "Sorry, this offer is no longer available." unless upsell.present?
if upsell.cross_sell?
if upsell.not_replace_selected_products?
cart_product_permalinks = params[:cart_items].reject { _1[:permalink] == product.unique_permalink }.map { _1[:permalink] }
if upsell.not_is_content_upsell? && (upsell.universal ? product.user.products : upsell.selected_products).where(unique_permalink: cart_product_permalinks).empty?
raise Purchase::PurchaseInvalid, "The cart does not have any products to which the upsell applies."
end
end
# The original discount is retained if it is better than the upsell
# discount. The client can't automatically set the upsell discount
# because it doesn't have a "code". Thus, upsell discount should only
# be applied when the purchase does not already have a discount code.
if purchase.offer_code.blank? && upsell.offer_code&.evaluate_for_buyer(buyer, product: purchase.link).present? &&
(!params[:is_purchasing_power_parity_discounted] || perceived_price_matches_accepted_offer?(upsell.offer_code))
purchase.offer_code = upsell.offer_code
end
end
purchase.build_upsell_purchase(
upsell:,
selected_product: Link.find_by_external_id(params[:accepted_offer][:original_product_id]),
upsell_variant: params[:accepted_offer][:original_variant_id].present? ?
upsell.upsell_variants.alive.find_by(
selected_variant: BaseVariant.find_by_external_id(params[:accepted_offer][:original_variant_id])View on GitHub (pinned to afeacbd394)
Solutions
- Reload the checkout page and re-accept the upsell so the accepted offer matches the current cart.
- Before submitting, confirm the cart contains at least one other product in the upsell's scope (its selected_products, or any product by the same seller when the upsell is universal).
- If you administer the seller's account, verify the cross-sell upsell's 'applies to' product selection still matches what buyers actually have in their carts.
Example fix
# before: accepted_offer submitted even after the eligible item was removed from the cart
params[:accepted_offer] = { id: upsell.external_id } # cart no longer has any product the upsell applies to
# after: submit the accepted offer only while it still applies to the cart
cart_permalinks = cart_items.reject { _1[:permalink] == product.unique_permalink }.map { _1[:permalink] }
scope = upsell.universal ? product.user.products : upsell.selected_products
params[:accepted_offer] = scope.where(unique_permalink: cart_permalinks).exists? ? { id: upsell.external_id } : nil Defensive patterns
Strategy: validation
Validate before calling
if params[:accepted_offer].present?
upsell = Upsell.available_to_customers.find_by_external_id(params[:accepted_offer][:id])
if upsell&.cross_sell? && upsell.not_replace_selected_products? && upsell.not_is_content_upsell?
cart_permalinks = params[:cart_items].reject { _1[:permalink] == product.unique_permalink }.map { _1[:permalink] }
scope = upsell.universal ? product.user.products : upsell.selected_products
params[:accepted_offer] = nil if scope.where(unique_permalink: cart_permalinks).empty?
end
end Type guard
def upsell_applies_to_cart?(upsell, cart_items, product)
return true unless upsell.cross_sell? && upsell.not_replace_selected_products? && upsell.not_is_content_upsell?
permalinks = cart_items.reject { _1[:permalink] == product.unique_permalink }.map { _1[:permalink] }
scope = upsell.universal ? product.user.products : upsell.selected_products
scope.where(unique_permalink: permalinks).exists?
end Try / catch
begin purchase, error = Purchase::CreateService.new(product:, params:, buyer:).perform rescue Purchase::PurchaseInvalid => e # user-facing message: revalidate upsell applicability against the current cart, then prompt refresh render_checkout_error(e.message) if e.message == 'The cart does not have any products to which the upsell applies.' end
Prevention
- Recompute upsell applicability whenever cart contents change instead of persisting an accepted offer across cart edits.
- When sellers edit upsells, expect in-flight checkouts to fail validation; design the client to revalidate on 422 and refresh.
- Treat Purchase::PurchaseInvalid messages as user-facing text and surface them on the checkout page.
When it happens
Trigger: POST checkout with params[:accepted_offer][:id] naming an Upsell that is cross_sell?, has not_replace_selected_products?, and is not a content upsell, where params[:cart_items] (minus the current product's unique_permalink) contains no permalink matching (upsell.universal ? product.user.products : upsell.selected_products).
Common situations: Buyer removes the only eligible product from a multi-item cart but the accepted offer captured earlier is still submitted; seller edits an upsell's selected products or universal flag while buyers sit on stale checkout pages; API integrations that replay a stored accepted_offer against a rebuilt cart.
Related errors
- 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's variants 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/390d76c827ce231e.
Report an issue: GitHub.