antiwork/gumroad · error · Purchase::PurchaseInvalid

Please select a start time.

Error message

Please select a start time.

What it means

For call-type products (native_type == Link::NATIVE_TYPE_CALL), checkout requires a schedulable slot: Time.zone.parse(params[:call_start_time]) must yield a time and the first variant attribute must define duration_in_minutes so end_time can be computed. A missing, blank, or unparseable start time - or a variant without a duration - raises 'Please select a start time.'

Source

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

          if variant.present?
            purchase.variant_attributes << variant
          else
            purchase.errors.add(:base, "The product's variants have changed, please refresh the page!")
            raise Purchase::PurchaseInvalid, "The product's variants have changed, please refresh the page!"
          end
        end
      elsif product.is_tiered_membership
        purchase.variant_attributes << product.tiers.first
      elsif product.is_physical && product.skus.is_default_sku.present?
        purchase.variant_attributes << product.skus.is_default_sku.first
      end

      if product.native_type == Link::NATIVE_TYPE_CALL
        start_time = Time.zone.parse(params[:call_start_time] || "")
        duration_in_minutes = purchase.variant_attributes.first&.duration_in_minutes

        if start_time.blank? || duration_in_minutes.blank?
          raise Purchase::PurchaseInvalid, "Please select a start time."
        end

        end_time = start_time + duration_in_minutes.minutes
        purchase.build_call(start_time:, end_time:)
      end

      build_custom_fields(purchase, params[:custom_fields] || [], product:)

      product.bundle_products.alive.each do |bundle_product|
        # Temporarily create custom fields on the bundle purchase in case it can't complete yet due to SCA.
        # The custom fields will be moved to each product purchase when the receipt is generated.
        custom_fields_params = params[:bundle_products]&.find { _1[:product_id] == bundle_product.product.external_id }&.dig(:custom_fields)
        build_custom_fields(purchase, custom_fields_params || [], bundle_product:)
      end

      purchase.url_parameters = parse_url_parameters(params_for_purchase[:url_parameters])
      purchase
    end

View on GitHub (pinned to afeacbd394)

Solutions

  1. Select a start time and resubmit using an ISO 8601 string with zone, e.g. 2026-08-21T15:00:00-07:00.
  2. Ensure the selected call variant defines duration_in_minutes.
  3. Integrators: require a chosen slot client-side and validate the string parses before submit.

Example fix

# before: empty/unparseable start time
params[:call_start_time] = ''

# after: send an ISO 8601 timestamp with an explicit offset
params[:call_start_time] = '2026-08-21T15:00:00-07:00'
Defensive patterns

Strategy: validation

Validate before calling

if product.native_type == Link::NATIVE_TYPE_CALL
  raise ArgumentError, 'call_start_time required' if Time.zone.parse(params[:call_start_time].to_s).blank?
end

Type guard

def parseable_start_time?(value)
  Time.zone.parse(value.to_s).present?
end

Try / catch

begin
  Purchase::CreateService.new(product:, params:, buyer:).perform
rescue Purchase::PurchaseInvalid => e
  # require the buyer to pick a slot; no automated recovery
  render_checkout_error(e.message) if e.message == 'Please select a start time.'
end

Prevention

When it happens

Trigger: product.native_type == Link::NATIVE_TYPE_CALL with params[:call_start_time] missing, empty, or in a format Time.zone.parse rejects (returns nil), OR purchase.variant_attributes.first.duration_in_minutes blank.

Common situations: Buyer never picked a slot and the client submitted anyway; datetime string without a parseable format or zone; call product whose selected variant/tier lacks duration configuration.

Related errors


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