antiwork/gumroad · error · Link::LinkInvalid
Offer code cannot be expired
Error message
Offer code cannot be expired
What it means
Same rule as the bundle variant, on the main product editor: links_controller.rb:1932 raises `Link::LinkInvalid` when the submitted `default_offer_code_id` resolves to an offer code where `OfferCode#inactive?` is true (`valid_at` future or `expires_at` past). Unlike the bundles controller, this path has NO unchanged-id early return, so the product form re-sending the current default offer code id after that code expired will trip this on an otherwise unrelated save. The action rescues LinkInvalid (links_controller.rb:663) and re-renders the form with the message.
Source
Thrown at app/controllers/links_controller.rb:1932
return unless @product.installment_plan.changed?
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
elseView on GitHub (pinned to afeacbd394)
Solutions
- Reload the product edit page so the form carries fresh state, then either re-select an active code or clear the default offer code field, and save again.
- Extend the offer code's expiry on the Offer Codes page if it should remain the default.
- Clear the default offer code (submit blank `default_offer_code_id`) when the expired code is no longer wanted — blank unassigns it.
- As an API caller, pre-check `!offer_code.inactive?` before including `default_offer_code_id`, and omit… note that omitting/blank clears the assignment, so send the id only when it is currently valid.
Example fix
# before — saving an unrelated change with a stale default_offer_code_id hidden field
put product_path(@product), params: { product: { name: "New name", default_offer_code_id: expired_code.external_id } }
# after — drop or refresh the assignment before saving
put product_path(@product), params: { product: { name: "New name", default_offer_code_id: nil } }
# or: default_offer_code_id: active_code.external_id Defensive patterns
Strategy: validation
Validate before calling
code = @product.user.offer_codes.alive.find_by(external_id: default_offer_code_id) # omit the key entirely on unrelated saves; never re-send a possibly-expired id blindly @product.default_offer_code = code if code && !code.inactive? && code.applicable?(@product)
Type guard
def active_default_offer?(code) !code.inactive? # valid_at blank/past AND expires_at blank/future end
Try / catch
begin update_product_with_default_offer_code rescue Link::LinkInvalid => e flash.now[:alert] = e.message render :edit # nothing persisted; user re-picks the code end
Prevention
- Unlike bundles, the product editor raises even for the unchanged id — after an offer code expires, always clear or re-pick the default before the next save.
- Avoid short expiries on codes used as product defaults.
- Reload the edit page before saving a tab that has been open for days.
- API clients: send default_offer_code_id only with a code verified active right now; remember blank clears the assignment.
When it happens
Trigger: Saving any product change (price, name, files) while the form still carries the id of a default offer code that has since expired; explicitly selecting a not-yet-valid or expired code as the new default; offer code expired while the product editor tab sat open for days.
Common situations: Long-lived product-edit tabs with stale hidden-field values; codes with short expiry windows used as defaults; admin expires a code while a seller is mid-edit.
Related errors
- Offer code cannot be expired
- Offer code must apply to this product
- Invalid offer code
- Offer code must apply to this product
- Invalid offer code
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/3ebbbab5bf872277.
Report an issue: GitHub.