antiwork/gumroad · error · Purchase::PurchaseInvalid

The product was just released. Refresh the page to purchase

Error message

The product was just released. Refresh the page to purchase it.

What it means

build_preorder runs when the purchase is flagged is_preorder_authorization, but it first asserts product.is_in_preorder_state?. If the product was just released, the assertion fails and the buyer is told to refresh - the pre-order authorization path no longer applies and a normal purchase must be made instead.

Source

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

        is_gift_receiver_purchase: true
      )
      giftee_purchase = build_purchase(giftee_purchase_params)
      giftee_purchase.purchaser = giftee_purchaser
      giftee_purchase.gift_received = gift
      giftee_purchase.process!
      raise Purchase::PurchaseInvalid, giftee_purchase.errors.full_messages[0] if giftee_purchase.errors.present?
    end

    def giftee_purchaser
      @_giftee_purchaser ||= gift_params[:giftee_id].present? ? User.alive.find_by_external_id(gift_params[:giftee_id]) : User.alive.by_email(gift_params[:giftee_email]).last
    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

View on GitHub (pinned to afeacbd394)

Solutions

  1. Refresh the page and complete a normal purchase - the release flow takes over.
  2. Integrators: re-check product.is_in_preorder_state? immediately before submit and switch to the normal purchase flow on mismatch.
  3. Handle this raise by restarting checkout, never by resubmitting identical preorder params.

Example fix

# before: preorder params submitted after release
params[:purchase][:is_preorder_authorization] = true # product no longer in preorder state

# after: reconcile the flag with live product state
params[:purchase][:is_preorder_authorization] = product.is_in_preorder_state?
Defensive patterns

Strategy: validation

Validate before calling

params[:purchase][:is_preorder_authorization] = product.is_in_preorder_state? if params[:purchase][:is_preorder_authorization]

Type guard

def preorder_flow_current?(params, product)
  !params[:purchase][:is_preorder_authorization] || product.is_in_preorder_state?
end

Try / catch

begin
  Purchase::CreateService.new(product:, params:, buyer:).perform
rescue Purchase::PurchaseInvalid => e
  # product was released mid-checkout: restart checkout in normal purchase mode
  redirect_to_checkout if e.message == 'The product was just released. Refresh the page to purchase it.'
end

Prevention

When it happens

Trigger: Checkout was loaded while the product was a pre-order (is_preorder_authorization true in the submitted params), but by submit time product.is_in_preorder_state? is false - the classic race between release and submit. A separate guard (line 75) covers the inverse tampered case.

Common situations: Stale tabs from before a product release; integrations caching preorder state; seller releasing the product while buyers are mid-checkout.

Related errors


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