{"record":{"id":"6b08d2183c019d69","repo":"antiwork/gumroad","slug":"component-name-is-no-longer-available-in-the-qu","errorCode":null,"errorMessage":"#{component.name} is no longer available in the quantity this bundle includes. Please refresh the page!","messagePattern":"#(.+?) is no longer available in the quantity this bundle includes\\. Please refresh the page!","errorType":"exception","errorClass":"Purchase::PurchaseInvalid","httpStatus":null,"severity":"error","filePath":"app/services/purchase/create_service.rb","lineNumber":416,"sourceCode":"    # costs the buyer nothing. Only closes the non-concurrent hole (verdict computed then discarded);\n    # the multi-lock direct-vs-bundle/bundle-vs-bundle race is gumroad-private#1786 items 2-4.\n    def validate_bundle_component_inventory(bundle_product)\n      requested_quantity = bundle_product.quantity * purchase.quantity\n      component = bundle_product.product.reload\n\n      # A variant can have stock left while its product-wide cap is exhausted (or vice versa),\n      # so both must be checked — checking only one lets the other's cap be silently oversold.\n      remaining = if bundle_product.variant.present?\n        [bundle_product.variant.reload.quantity_left, component.remaining_for_sale_count].compact.min\n      else\n        component.remaining_for_sale_count\n      end\n\n      # nil means uncapped, so there is nothing to enforce.\n      return if remaining.nil?\n      return if remaining >= requested_quantity\n\n      raise Purchase::PurchaseInvalid,\n            \"#{component.name} is no longer available in the quantity this bundle includes. Please refresh the page!\"\n    end\n\n    def build_purchase(params_for_purchase)\n      params_for_purchase[:country] = ISO3166::Country[params_for_purchase[:country]]&.common_name\n\n      purchase = product.sales.build(params_for_purchase)\n      purchase.authenticated_offer_code_buyer = buyer\n      purchase.affiliate = product.collaborator if product.collaborator.present?\n      should_ship = product.is_physical || product.require_shipping\n      purchase.country = nil unless should_ship\n      purchase.country ||= ISO3166::Country[params_for_purchase[:sales_tax_country_code_election]]&.common_name\n      set_purchaser_for(purchase, params_for_purchase[:email])\n      purchase.is_installment_payment = params[:pay_in_installments] && product.allow_installment_plan?\n      purchase.installment_plan = product.installment_plan if purchase.is_installment_payment\n      purchase.save_card = !!params_for_purchase[:save_card] || (product.is_recurring_billing && !is_gift?) || purchase.is_preorder_authorization || purchase.is_installment_payment\n      purchase.seller = product.user\n      purchase.is_gift_sender_purchase = is_gift? unless params_for_purchase.has_key?(:is_gift_receiver_purchase)","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/purchase/create_service.rb#L398-L434","documentation":"Under the bundle's product_inventory lock, validate_bundle_component_inventory compares the requested quantity (bundle_product.quantity * purchase.quantity) with remaining stock: the minimum of variant.quantity_left and component.remaining_for_sale_count when the component is variant-scoped, otherwise just remaining_for_sale_count; nil means uncapped and passes. If remaining < requested, the bundle cannot be fulfilled complete and checkout aborts before any charge, naming the component.","triggerScenarios":"A capped component (or its variant) has remaining stock below bundle_product.quantity * purchase.quantity - e.g. a bundle includes 2 of an item, purchase quantity is 2 (4 needed), and only 3 remain; note both caps are checked because a variant can have stock while the product-wide cap is exhausted.","commonSituations":"Stock drops between page load and submit; bundles that include more copies of an item than its cap allows; concurrent purchases consuming the last units.","solutions":["Refresh the page and retry with a lower purchase quantity if the product allows quantity selection.","Seller: restock the component or reduce the per-bundle quantity so bundles stay fulfillable.","Integrators: before enabling submit, check that each component's min(variant.quantity_left, remaining_for_sale_count) >= bundle quantity x cart quantity, treating nil as uncapped."],"exampleFix":"# before: submit proceeds without a stock check\nparams[:quantity] = 2 # bundle needs 2x component per unit, only 3 left\n\n# after: gate submission on fulfillable stock\nshort = product.bundle_products.alive.any? do |bp|\n  remaining = bp.variant.present? ? [bp.variant.quantity_left, bp.product.remaining_for_sale_count].compact.min : bp.product.remaining_for_sale_count\n  remaining.present? && remaining < bp.quantity * params[:quantity].to_i\nend\nparams[:quantity] = 1 if short","handlingStrategy":"validation","validationCode":"if product.is_bundle?\n  understocked = product.bundle_products.alive.any? do |bp|\n    remaining = bp.variant.present? ? [bp.variant.quantity_left, bp.product.remaining_for_sale_count].compact.min : bp.product.remaining_for_sale_count\n    remaining.present? && remaining < bp.quantity * params[:quantity].to_i\n  end\n  params[:quantity] = 1 if understocked\nend","typeGuard":"def bundle_fulfillable?(bundle_product, purchase_quantity)\n  remaining = bundle_product.variant.present? ?\n    [bundle_product.variant.quantity_left, bundle_product.product.remaining_for_sale_count].compact.min :\n    bundle_product.product.remaining_for_sale_count\n  remaining.nil? || remaining >= bundle_product.quantity * purchase_quantity\nend","tryCatchPattern":"begin\n  Purchase::CreateService.new(product:, params:, buyer:).perform\nrescue Purchase::PurchaseInvalid => e\n  # stock moved under us: reload, recheck, offer lower quantity - never blind-retry\n  render_checkout_error(e.message) if e.message.include?('no longer available in the quantity')\nend","preventionTips":["Check both caps before submit: variant quantity_left AND product remaining_for_sale_count (nil means uncapped).","Remember the requirement multiplies: bundle quantity x purchase quantity.","Stock is checked under an inventory lock at submit; treat a pass at page-load time as advisory only."],"tags":["bundle","inventory","stock","checkout","gumroad"],"backgroundTag":"insufficient-inventory","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}