antiwork/gumroad · error · Purchase::PurchaseInvalid

Sorry, you have not met the offer code's minimum amount.

Error message

Sorry, you have not met the offer code's minimum amount.

What it means

In Purchase::CreateService, when the applied offer code has minimum_amount_cents set, the eligible cart items' price_cents sum must reach it or Purchase::PurchaseInvalid is raised. Eligible items are all cart items minus the code's excluded_products for universal codes, or only items whose permalink is in offer_code.products otherwise — so items outside the code's scope do not count toward the minimum.

Source

Thrown at app/services/purchase/create_service.rb:107

        # 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?
          return existing_purchase, error if existing_purchase.present? || error.present?
        end
      end

      if purchase.offer_code&.minimum_amount_cents.present?
        valid_items = params[:cart_items]
        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.

View on GitHub (pinned to afeacbd394)

Solutions

  1. Add eligible items (per the code's product scope/exclusions) until their total meets the minimum
  2. Use a different code without a minimum, or applicable to the products actually in the cart
  3. Compute the eligible-items total the same way — excluded products and non-code products do not count — before submitting payment
Defensive patterns

Strategy: validation

Validate before calling

code = purchase.offer_code
if code&.minimum_amount_cents.present?
  eligible = params[:cart_items].select do |item|
    code.universal ? !code.excluded_products.pluck(:unique_permalink).include?(item[:permalink]) : code.products.exists?(unique_permalink: item[:permalink])
  end
  raise 'below minimum' if eligible.sum { _1[:price_cents].to_i } < code.minimum_amount_cents
end

Type guard

def cart_meets_offer_minimum?(cart_items, offer_code)
  return true unless offer_code&.minimum_amount_cents.present?
  eligible = cart_items.select do |i|
    offer_code.universal ? !offer_code.excluded_products.pluck(:unique_permalink).include?(i[:permalink]) : offer_code.products.exists?(unique_permalink: i[:permalink])
  end
  eligible.sum { _1[:price_cents].to_i } >= offer_code.minimum_amount_cents
end

Try / catch

begin
  Purchase::CreateService.new(user: buyer, params: purchase_params).perform
rescue Purchase::PurchaseInvalid => e
  render json: { error: e.message }, status: :unprocessable_entity # message is buyer-safe
end

Prevention

When it happens

Trigger: Applying a '$10 off orders over $50' code to a $30 cart; removing an item after applying the code so the eligible total drops below the minimum; a universal code whose exclusions leave zero eligible items (sum 0 < minimum).

Common situations: Buyers trim the cart at the last step; codes scoped to specific products get applied to carts containing none of them; the minimum is per eligible items only, surprising users who included excluded products.

Related errors


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