antiwork/gumroad · error · Link::LinkInvalid

Invalid offer code

Error message

Invalid offer code

What it means

Raised as `Link::LinkInvalid` from the `rescue ActiveRecord::RecordNotFound` at bundles/product_controller.rb:159-161. `@bundle.user.offer_codes.alive.find_by_external_id!` found no live offer code with that external id owned by the bundle's user — the code was deleted (filtered by the `alive` scope), never existed, or belongs to a different account. `find_by_external_id!` is the bang variant, so a miss raises RecordNotFound which is converted to this friendlier validation message.

Source

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

      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. Reload the product form / offer-code dropdown so it lists only currently alive codes, then re-select a valid default offer code.
  2. Confirm the id is the code's external id from the same seller account and the same environment you are saving in.
  3. If the code was deleted by mistake, recreate it and use the new id.
  4. Leave `default_offer_code_id` blank to clear the assignment if no default code is wanted.

Example fix

# before
patch bundle_product_path(@bundle), params: { product: { default_offer_code_id: stale_external_id } }

# after — resolve a live code owned by the same seller first
code = @bundle.user.offer_codes.alive.find_by(external_id: submitted_id)
raise Link::LinkInvalid, "Invalid offer code" unless code
params[:product][:default_offer_code_id] = code.external_id
Defensive patterns

Strategy: validation

Validate before calling

code = @bundle.user.offer_codes.alive.find_by(external_id: submitted_id)
raise Link::LinkInvalid, "Invalid offer code" if code.nil?

Type guard

def resolvable_offer_code?(user, external_id)
  user.offer_codes.alive.exists?(external_id: external_id)
end

Try / catch

begin
  offer_code = user.offer_codes.alive.find_by_external_id!(id)
rescue ActiveRecord::RecordNotFound
  # show a picker refreshed with alive codes; do not retry the same id
end

Prevention

When it happens

Trigger: Submitting `default_offer_code_id` for an offer code the seller deleted in another tab; an id copied from a different seller account or a different environment (test id in production); an id typo/truncation in an API payload; the code still exists but is outside the `alive` scope.

Common situations: Two browser tabs open on Offer Codes and the product editor — delete in one, save in the other; front-end caching an old dropdown list of codes; scripts pasting ids between environments.

Related errors


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