antiwork/gumroad · error · Purchase::PurchaseInvalid

Gift purchases cannot be on installment plans.

Error message

Gift purchases cannot be on installment plans.

What it means

Raised when a gift purchase also requests installment payments (params[:pay_in_installments] truthy). Installment plans charge the buyer over time while gifts settle immediately against a recipient-side purchase, so the two options are mutually exclusive in create_gift.

Source

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

        purchase = result[:purchase] || restartable_subscription.original_purchase
        self.purchase = purchase
        Rails.logger.info("Subscription #{restartable_subscription.external_id} restarted during checkout for product #{product.id}")
        return purchase, nil
      else
        return nil, result[:error_message]
      end
    end

    def create_gift
      # A seller buying their own product can only ever be a test purchase, and test purchases were
      # never built for gifts, so this case has to be rejected. The message names what the seller
      # actually did and points at the supported way to give a product away, because the old wording
      # ("Test gift purchases have not been enabled yet.") described an internal capability and read
      # as a flag we could switch on for them, which generated support tickets asking us to do that.
      raise Purchase::PurchaseInvalid, "You can't gift your own product. To give it away for free, create a 100% off discount code under Checkout > Discounts and share the checkout link." if buyer == product.user
      raise Purchase::PurchaseInvalid, "You cannot gift a product to yourself. Please try gifting to another email." if giftee_email == purchase_params[:email]
      raise Purchase::PurchaseInvalid, "Gift purchases cannot be on installment plans." if params[:pay_in_installments]

      if product.can_gift?
        gift = product.gifts.build(giftee_email:, gift_note: gift_params[:gift_note], gifter_email: params[:purchase][:email], is_recipient_hidden: gift_params[:giftee_email].blank?)
        error_message = gift.save ? nil : gift.errors.full_messages[0]
        raise Purchase::PurchaseInvalid, error_message if error_message.present?

        gift
      else
        error_message = product.user.gifting_disabled? ? "The creator has disabled gifting for their products." : "Gifting is not yet enabled for pre-orders."
        raise Purchase::PurchaseInvalid, error_message
      end
    end

    def validate_perceived_price
      if purchase_params[:perceived_price_cents] && !Purchase::MAX_PRICE_RANGE.cover?(purchase_params[:perceived_price_cents])
        raise Purchase::PurchaseInvalid, "Purchase price is invalid. Please check the price."
      end
    end

View on GitHub (pinned to afeacbd394)

Solutions

  1. Turn off installment payments (pay in full) to gift the product.
  2. Keep installments and remove the gift params to buy for yourself.
  3. Integrators: make the gift and installment options mutually exclusive in the checkout UI.

Example fix

# before: both flags sent together
params[:gift] = { giftee_email: 'friend@example.com' }
params[:pay_in_installments] = true

# after: gift purchases pay in full
params[:pay_in_installments] = false if params[:gift].present?
Defensive patterns

Strategy: validation

Validate before calling

params[:pay_in_installments] = false if params[:gift].present?

Try / catch

begin
  Purchase::CreateService.new(product:, params:, buyer:).perform
rescue Purchase::PurchaseInvalid => e
  # ask the buyer to pick one: full-price gift or non-gift installment purchase
  render_checkout_error(e.message) if e.message == 'Gift purchases cannot be on installment plans.'
end

Prevention

When it happens

Trigger: Gift params present (is_gift?) and params[:pay_in_installments] truthy in the same checkout request.

Common situations: Buyer toggled installment payments before switching the checkout to a gift and the client kept the flag; integrations that send pay_in_installments unconditionally.

Related errors


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