antiwork/gumroad · error · Link::LinkInvalid

Offer code must apply to this product

Error message

Offer code must apply to this product

What it means

Raised as `Link::LinkInvalid` when the chosen default offer code fails `OfferCode#applicable?` for the bundle (app/models/offer_code.rb:305). A non-universal code must include the product in its `products` list; a universal code must not exclude the product and, if the code declares a `currency_type`, the product's price currency must match. The controller renders the message on the form and aborts the save.

Source

Thrown at app/controllers/bundles/product_controller.rb:156

      # Only touch the default offer code when the request includes the field.
      # Requests from flows that don't render the toggle (like converting a
      # product to a bundle) shouldn't silently clear an existing default code.
      return unless params.key?(:default_offer_code_id)

      default_offer_code_id = product_permitted_params[:default_offer_code_id]

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

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

      # The form re-sends the current id on every save, so a currency change
      # would otherwise be rejected outright by applicable? below. Leave an
      # unchanged id to Link#clear_detached_default_offer_code, which drops it
      # when it stops applying.
      return if offer_code == @bundle.default_offer_code

      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?(@bundle)

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

View on GitHub (pinned to afeacbd394)

Solutions

  1. Choose an offer code that is attached to this bundle (or a universal code with no currency restriction and no exclusion for it) — reload the offer codes list and re-select.
  2. Edit the offer code on the Offer Codes page to add this bundle to its products (or remove the currency restriction / exclusion), then retry.
  3. As an API caller, run `offer_code.applicable?(@bundle)` before sending `default_offer_code_id`.
  4. Clear the default offer code field if no applicable code is intended.

Example fix

# before
@bundle.default_offer_code = user.offer_codes.find_by(name: "Summer sale") # scoped to other products

# after — only assign when the code applies
offer_code = user.offer_codes.alive.find_by_external_id(id)
@bundle.default_offer_code = offer_code if offer_code&.applicable?(@bundle)
Defensive patterns

Strategy: validation

Validate before calling

return unless offer_code.applicable?(@bundle)
@bundle.default_offer_code = offer_code

Type guard

def code_applies?(code, bundle)
  return false if code.universal? && code.excluded_products.include?(bundle)
  code.universal? ? (code.currency_type.blank? || bundle.price_currency_type == code.currency_type) : code.products.include?(bundle)
end

Try / catch

begin
  @bundle.update!(permitted)
rescue Link::LinkInvalid => e
  # e.message is form-ready: "Offer code must apply to this product"
  render :edit, alert: e.message
end

Prevention

When it happens

Trigger: Selecting an offer code that was created for other products (non-universal, bundle not attached), a universal code that explicitly excludes this bundle, or a code with `currency_type: "usd"` while the bundle's price is in EUR. Note this bundle controller deliberately early-returns when the id is unchanged, so a currency change alone does not trip it — only switching to a non-applicable code does.

Common situations: Seller has many per-product codes and picks the wrong one from the dropdown; a universal code was later scoped to a currency that no longer matches the product; product was removed from the code's product list while it stayed selected in a stale form.

Related errors


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