antiwork/gumroad · error · Link::LinkInvalid

Offer code must apply to this product

Error message

Offer code must apply to this product

What it means

links_controller.rb:1933 raises `Link::LinkInvalid` when the default offer code fails `OfferCode#applicable?(@product)` (app/models/offer_code.rb:305): a non-universal code must list the product, a universal code must not exclude it, and a currency-scoped universal code must match the product's price currency. Because this controller lacks the unchanged-id early return, even re-sending the CURRENT id fails once applicability drifted — e.g. the product's currency changed so it no longer matches the code.

Source

Thrown at app/controllers/links_controller.rb:1933

      end

      @product.installment_plan&.destroy_if_no_payment_options!
      @product.reset_installment_plan

      if product_permitted_params[:installment_plan].present?
        @product.create_installment_plan!(product_permitted_params[:installment_plan])
      end
    end

    def update_default_offer_code
      default_offer_code_id = product_permitted_params[:default_offer_code_id]

      return @product.default_offer_code = nil if default_offer_code_id.blank?

      offer_code = @product.user.offer_codes.alive.find_by_external_id!(default_offer_code_id)

      raise Link::LinkInvalid, "Offer code cannot be expired" if offer_code.inactive?
      raise Link::LinkInvalid, "Offer code must apply to this product" unless offer_code.applicable?(@product)

      @product.default_offer_code = offer_code
    rescue ActiveRecord::RecordNotFound
      raise Link::LinkInvalid, "Invalid offer code"
    end

    def toggle_community_chat!(enabled)
      return if [Link::NATIVE_TYPE_COFFEE, Link::NATIVE_TYPE_BUNDLE].include?(@product.native_type)

      @product.toggle_community_chat!(enabled)
    end

    def generate_product_details_using_ai
      if Rails.env.test?
        generate_product_cover_and_thumbnail_using_ai
        generate_product_content_using_ai
      else
        cover_thread = Thread.new { generate_product_cover_and_thumbnail_using_ai }

View on GitHub (pinned to afeacbd394)

Solutions

  1. If you just changed the product's currency, also clear or re-pick the default offer code in the same save — a currency-scoped code will keep failing otherwise.
  2. Edit the offer code to include the product / drop the currency restriction / remove the exclusion, then save the product again.
  3. Select a universal code without a currency_type, or one explicitly attached to this product.
  4. Pre-check `offer_code.applicable?(@product)` before sending `default_offer_code_id` from API clients.

Example fix

# before — currency flip + stale code id in one save
put product_path(@product), params: { product: { price_currency_type: "eur", default_offer_code_id: usd_only_code.external_id } }

# after — clear the detached default in the same request
put product_path(@product), params: { product: { price_currency_type: "eur", default_offer_code_id: nil } }
Defensive patterns

Strategy: validation

Validate before calling

code = @product.user.offer_codes.alive.find_by(external_id: default_offer_code_id)
@product.default_offer_code = code if code&.applicable?(@product)

Type guard

def code_applies_after_change?(code, product, new_currency)
  return false unless code.applicable?(product)
  code.currency_type.blank? || code.currency_type == new_currency
end

Try / catch

begin
  @product.update!(permitted_params)
rescue Link::LinkInvalid => e
  # message is user-ready; prompt to re-pick or clear the default offer code
  render :edit, alert: e.message
end

Prevention

When it happens

Trigger: Saving a product whose currency was changed while a currency-scoped universal code stays set as default; a code whose product list no longer includes the product; a universal code that now excludes the product; selecting a code belonging to other products.

Common situations: Currency change on a product with a currency-restricted default offer code (the exact case the bundles controller comments about, still live here); product removed from a multi-product code's scope; stale editor tabs.

Related errors


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