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
- 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.
- Edit the offer code on the Offer Codes page to add this bundle to its products (or remove the currency restriction / exclusion), then retry.
- As an API caller, run `offer_code.applicable?(@bundle)` before sending `default_offer_code_id`.
- 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
- Only offer codes attached to the bundle (or currency-free universal codes) in the default-code picker.
- When changing a product's currency, clear/re-pick its default offer code in the same edit.
- Run offer_code.applicable?(product) in client-side or service pre-validation before save.
- Keep offer-code product lists and exclusions current when products are merged/renamed.
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
- Offer code cannot be expired
- Invalid offer code
- Offer code must apply to this product
- Offer code cannot be expired
- Invalid offer code
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/46ca1424d2e7b05d.
Report an issue: GitHub.