{"record":{"id":"884fc09e2623cbf2","repo":"antiwork/gumroad","slug":"the-product-s-free-trial-has-changed-please-refre","errorCode":null,"errorMessage":"The product's free trial has changed, please refresh the page!","messagePattern":"The product's free trial has changed, please refresh the page!","errorType":"exception","errorClass":"Purchase::PurchaseInvalid","httpStatus":null,"severity":"error","filePath":"app/services/purchase/create_service.rb","lineNumber":378,"sourceCode":"    def perceived_price_matches_accepted_offer?(offer_code)\n      return false unless offer_code\n\n      original_offer_code = purchase.offer_code\n      purchase.offer_code = offer_code\n      purchase.minimum_paid_price_cents + params[:tip_cents].to_i == purchase_params[:perceived_price_cents].to_i\n    ensure\n      purchase.offer_code = original_offer_code if purchase\n    end\n\n    def validate_perceived_free_trial_params\n      return if is_gift?\n\n      free_trial_params = params[:perceived_free_trial_duration]\n      if product.free_trial_enabled?\n        if !free_trial_params.present? || !free_trial_params[:amount].present? || !free_trial_params[:unit].present?\n          raise Purchase::PurchaseInvalid, \"Invalid free trial information provided. Please try again.\"\n        elsif free_trial_params[:amount].to_i != product.free_trial_duration_amount || free_trial_params[:unit] != product.free_trial_duration_unit\n          raise Purchase::PurchaseInvalid, \"The product's free trial has changed, please refresh the page!\"\n        end\n      elsif free_trial_params.present?\n        raise Purchase::PurchaseInvalid, \"Invalid free trial information provided. Please try again.\"\n      end\n    end\n\n    def validate_bundle_products\n      return unless product.is_bundle?\n\n      product.bundle_products.alive.each do |bundle_product|\n        if params[:bundle_products].none? { _1[:product_id] == bundle_product.product.external_id && _1[:variant_id] == bundle_product.variant&.external_id && _1[:quantity].to_i == bundle_product.quantity }\n          raise Purchase::PurchaseInvalid, \"The bundle's contents have changed. Please refresh the page!\"\n        end\n\n        validate_bundle_component_inventory(bundle_product)\n      end\n    end\n","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/purchase/create_service.rb#L360-L396","documentation":"The submitted trial terms no longer match the product: free_trial_params[:amount].to_i differs from product.free_trial_duration_amount, or the unit differs from product.free_trial_duration_unit. This is a stale-checkout guard - the seller changed the trial after the buyer loaded the page, and purchasing against outdated terms would misrepresent what the buyer agreed to.","triggerScenarios":"Product has a trial, the request echoes a complete { amount:, unit: } hash, but amount.to_i != product.free_trial_duration_amount OR unit != product.free_trial_duration_unit.","commonSituations":"Seller edits trial duration/unit while buyers are mid-checkout; cached product JSON in custom checkout integrations; long-lived checkout tabs.","solutions":["Refresh the checkout page and resubmit with the updated terms.","Integrators: re-fetch free_trial_duration_amount/unit at submit time and rebuild the params from it.","Treat this raise as a signal to restart checkout, not to retry identical params."],"exampleFix":"# before: cached trial terms submitted after the seller changed them\nparams[:perceived_free_trial_duration] = cached_trial # { amount: 7, unit: 'day' }, product now 14 days\n\n# after: rebuild from current product state\nparams[:perceived_free_trial_duration] = { amount: product.free_trial_duration_amount, unit: product.free_trial_duration_unit }","handlingStrategy":"validation","validationCode":"if product.free_trial_enabled?\n  params[:perceived_free_trial_duration] = { amount: product.free_trial_duration_amount, unit: product.free_trial_duration_unit }\nend","typeGuard":"def trial_params_match_product?(trial_params, product)\n  trial_params[:amount].to_i == product.free_trial_duration_amount && trial_params[:unit] == product.free_trial_duration_unit\nend","tryCatchPattern":"begin\n  Purchase::CreateService.new(product:, params:, buyer:).perform\nrescue Purchase::PurchaseInvalid => e\n  # stale page: rebuild trial params from the product and restart checkout\n  redirect_to_checkout if e.message.start_with?(\"The product's free trial has changed\")\nend","preventionTips":["Rebuild trial params from freshly fetched product state at submit time instead of caching them.","Treat this raise as a refresh signal - resubmitting identical params will keep failing."],"tags":["free-trial","stale-state","checkout","gumroad"],"backgroundTag":"free-trial-param-mismatch","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}