antiwork/gumroad · error · Purchase::PurchaseInvalid

error_message (preorder.errors.full_messages[0])

Error message

error_message (preorder.errors.full_messages[0])

What it means

For a standalone pre-order (not part of a combined charge), preorder.authorize! reserves the charge; when it returns with errors and the purchase is not a test purchase, the first error message is re-raised as PurchaseInvalid. The text comes from Preorder validations - most often payment-authorization failures from the charge processor. SCA-required purchases take a different branch (scheduled FailAbandonedPurchaseWorker) instead of raising.

Source

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

    end

    def giftee_email
      giftee_purchaser&.email || gift_params[:giftee_email]
    end

    def build_preorder(locked_rate: nil)
      raise Purchase::PurchaseInvalid, "The product was just released. Refresh the page to purchase it." unless product.is_in_preorder_state?

      self.preorder = product.preorder_link.build_preorder(purchase)
      if purchase.is_part_of_combined_charge?
        purchase.prepare_for_charge!(locked_rate:)
      else
        preorder.authorize!(locked_rate:)
        error_message = preorder.errors.full_messages[0]
        if purchase.is_test_purchase?
          preorder.mark_test_authorization_successful!
        elsif error_message.present?
          raise Purchase::PurchaseInvalid, error_message
        elsif purchase.requires_sca?
          # Leave the preorder in `in_progress` state until the the required UI action is completed.
          # Check back later to see if it has been completed. If not, transition to a failed state.
          FailAbandonedPurchaseWorker.perform_in(ChargeProcessor::TIME_TO_COMPLETE_SCA, purchase.id)
        else
          preorder.mark_authorization_successful!
        end
      end
    end

    def set_purchaser_for(purchase, purchase_email)
      if buyer.present?
        purchase.purchaser = buyer unless purchase.is_gift_receiver_purchase
      else
        user_from_email = User.find_by(email: purchase_email)
        # This limits test purchase to be done in logged out mode
        if purchase.link.user != user_from_email
          purchase.purchaser = user_from_email

View on GitHub (pinned to afeacbd394)

Solutions

  1. Read the raised message - it surfaces the underlying authorization failure reason from the preorder/charge processor.
  2. Retry with a valid payment method after the failure is addressed.
  3. Integrators: log preorder.errors.full_messages (all entries) when this raise fires to capture the complete processor response.
Defensive patterns

Strategy: try-catch

Try / catch

begin
  purchase, error = Purchase::CreateService.new(product:, params:, buyer:).perform
rescue Purchase::PurchaseInvalid => e
  # authorization failed: message carries the processor reason; prompt for a new payment method
  Rails.logger.warn("preorder authorization failed: #{e.message}")
  render_checkout_error(e.message)
end

Prevention

When it happens

Trigger: preorder.authorize!(locked_rate:) populates preorder.errors; purchase.is_test_purchase? is false; purchase.requires_sca? is false - so the error branch fires and preorder.errors.full_messages[0] is raised verbatim.

Common situations: Card declined at pre-order authorization; charge-processor errors (amount, currency, processing limits); authorizing against a payment method that cannot be reserved.

Related errors


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