antiwork/gumroad · error · Purchase::PurchaseInvalid

Tip is too large for this purchase

Error message

Tip is too large for this purchase

What it means

Raised when the tip would push the payment below the product's floor: perceived_price_cents.ceil minus tip_cents.floor is less than purchase.minimum_paid_price_cents. On pay-what-you-want products minimum_paid_price_cents is the seller's minimum price, so the amount entered must cover both the product minimum and the tip.

Source

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

            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])
            ) :
            nil
        )
        raise Purchase::PurchaseInvalid, purchase.upsell_purchase.errors.first.message unless purchase.upsell_purchase.valid?
      end

      if params[:tip_cents].present? && params[:tip_cents] > 0
        raise Purchase::PurchaseInvalid, "Tip is not allowed for this product" unless purchase.seller.tipping_enabled? && product.not_is_tiered_membership?

        raise Purchase::PurchaseInvalid, "Tip is too large for this purchase" if (purchase_params[:perceived_price_cents].ceil - params[:tip_cents].floor) < purchase.minimum_paid_price_cents

        purchase.build_tip(value_cents: params[:tip_cents], value_usd_cents: get_usd_cents(product.price_currency_type, params[:tip_cents]))
      end

      validate_bundle_products

      purchase.prepare_for_charge!(locked_rate: buyer_currency_quote_rate_hint(purchase))

      purchase.build_purchase_wallet_type(wallet_type: params[:wallet_type]) if params[:wallet_type].present?

      payment_flow_attributes = PurchasePaymentFlow.attributes_for_checkout_params(params)
      # The purchase may still be unsaved here: `prepare_for_charge!` leaves it
      # unpersisted when a validation fails (for example, an invalid email).
      # Creating the dependent analytics row on an unsaved parent raises
      # ActiveRecord::RecordNotSaved, and a purchase that never saved has no
      # payment flow worth recording anyway.
      if payment_flow_attributes && purchase.persisted? && !purchase.free_purchase?
        begin

View on GitHub (pinned to afeacbd394)

Solutions

  1. Lower the tip so that price minus tip stays at or above the product minimum.
  2. Increase the total amount entered so it absorbs both the minimum and the tip.
  3. Integrators: enforce tip_cents <= perceived_price_cents - minimum_paid_price_cents before enabling submit.

Example fix

# before: tip accepted without bound (total 500c, minimum 100c, tip 450c -> 50 < 100)
params[:tip_cents] = 450

# after: clamp the tip to what the entered price can cover
max_tip = purchase_params[:perceived_price_cents].ceil - minimum_paid_price_cents
params[:tip_cents] = [params[:tip_cents].to_i, max_tip].min
Defensive patterns

Strategy: validation

Validate before calling

tip = params[:tip_cents].to_i.floor
price = purchase_params[:perceived_price_cents].to_i.ceil
params[:tip_cents] = 0 if tip > 0 && (price - tip) < minimum_paid_price_cents

Type guard

def tip_within_bounds?(perceived_price_cents, tip_cents, minimum_paid_price_cents)
  (perceived_price_cents.to_i.ceil - tip_cents.to_i.floor) >= minimum_paid_price_cents
end

Try / catch

begin
  Purchase::CreateService.new(product:, params:, buyer:).perform
rescue Purchase::PurchaseInvalid => e
  # ask the buyer to lower the tip or raise the entered amount; do not auto-retry
  render_checkout_error(e.message) if e.message == 'Tip is too large for this purchase'
end

Prevention

When it happens

Trigger: Any submission where (purchase_params[:perceived_price_cents].ceil - params[:tip_cents].floor) < purchase.minimum_paid_price_cents - e.g. a PWYW product with a $1 minimum, buyer enters $1.00 total and adds a $0.50 tip: 100 - 50 = 50 < 100.

Common situations: Pay-what-you-want checkouts with generous tips; clients computing perceived_price_cents in dollars instead of cents; rounding drift between client and server (the server rounds the price up and the tip down).

Related errors


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