{"record":{"id":"f2a9cb2cd31df683","repo":"antiwork/gumroad","slug":"error-message-from-get-chargeable-charge-process","errorCode":null,"errorMessage":"error_message (from get_chargeable, charge-processor card errors)","messagePattern":"error_message \\(from get_chargeable, charge-processor card errors\\)","errorType":"exception","errorClass":"Subscription::UpdateFailed","httpStatus":null,"severity":"error","filePath":"app/services/subscription/updater_service.rb","lineNumber":117,"sourceCode":"\n    begin\n      ActiveRecord::Base.transaction do\n        # Update subscription contact info\n        if params[:contact_info].present?\n          params[:contact_info][:country] = ISO3166::Country[params[:contact_info][:country]]&.common_name\n          original_purchase.is_updated_original_subscription_purchase = true\n          original_purchase.update!(params[:contact_info])\n        end\n\n        # Update card if necessary\n        unless use_existing_card?\n          had_saved_card = subscription.credit_card.present?\n\n          # (a) Get chargeable. Return if error\n          error_message = get_chargeable\n          if error_message.present?\n            logger.info(\"SubscriptionUpdater: Error fetching chargeable for subscription #{subscription.external_id}: #{error_message}\")\n            raise Subscription::UpdateFailed, error_message\n          end\n\n          # (b) Create new credit card. Return if error.\n          replacement_card = CreditCard.create(chargeable, card_data_handling_mode, logged_in_user)\n\n          unless replacement_card.errors.empty?\n            logger.info(\"SubscriptionUpdater: Error creating new credit card for subscription #{subscription.external_id}: #{replacement_card.errors.full_messages}\")\n            raise Subscription::UpdateFailed, replacement_card.errors.messages[:base].first\n          end\n\n          if indian_card_mandate_validation_required?(replacement_card)\n            # A plan update builds its replacement purchase before mandate validation. Keep the\n            # new card available for that build, but persist it only after validation succeeds.\n            subscription.credit_card = replacement_card\n          else\n            associate_replacement_card!(replacement_card, had_saved_card:, **validate_indian_card_mandate!(replacement_card))\n            replacement_card = nil\n          end","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/subscription/updater_service.rb#L99-L135","documentation":"Raised as Subscription::UpdateFailed when Subscription::UpdaterService.perform cannot build a chargeable from the submitted card data: get_chargeable returns an error string instead of a chargeable object. The string comes from CardParamsHelper.check_for_errors (mapped through PurchaseErrorCode.customer_error_message for buyer-card errors, otherwise the generic temporary-problem message) or from build_chargeable returning nil (\"We couldn't charge your card. Try again or use a different card.\"). perform rescues it and returns { success: false, error_message: }, so callers see a failed result, not an exception.","triggerScenarios":"Updating or restarting a membership with params[:use_existing_card] false while the request lacks valid card data: a missing or already-consumed Stripe single-use token, stale encrypted card fields, a PayPal billing agreement id the helper rejects, or any card_data_handling_error carrying a card_error_code. Also when build_chargeable(params.merge(product_permalink: ...)) returns nil because no usable payment token is present in params.","commonSituations":"Checkout form submitted long after the card token was minted (expired/consumed), stale page after a Stripe key rotation, integration code forgetting to forward the encrypted card params, or test environments without valid charge processor credentials producing the temporary-problem branch.","solutions":["Have the buyer re-enter card details and resubmit so a fresh, unconsumed card token is produced.","Grep the Rails log for \"SubscriptionUpdater: Error building chargeable\" with the subscription external_id — it includes the raw error_message and card_error_code identifying the failing param.","Verify the request actually carries the card fields the checkout flow captured and that params[:use_existing_card] matches intent (false means new card data is required).","If the non-card-error branch fired (\"There is a temporary problem...\"), check charge processor configuration/credentials and retry once healthy."],"exampleFix":"# before: params from a stale form, token already consumed\nresult = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform\n# => { success: false, error_message: \"We couldn't charge your card. Try again or use a different card.\" }\n\n# after: always send the card fields the form just captured\nparams.merge!(use_existing_card: false) # plus fresh encrypted card / stripe token fields\nresult = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform\nreturn render_error(result[:error_message]) unless result[:success]","handlingStrategy":"validation","validationCode":"# Fail fast before invoking the updater when new-card data is requested but absent\nuse_existing = ActiveModel::Type::Boolean.new.cast(params[:use_existing_card])\nif !use_existing && CardParamsHelper.build_chargeable(params.merge(product_permalink: subscription.link.unique_permalink)).nil?\n  return { success: false, error_message: \"We couldn't charge your card. Try again or use a different card.\" }\nend","typeGuard":null,"tryCatchPattern":"# perform() already rescues Subscription::UpdateFailed into the result hash\nresult = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform\nunless result[:success]\n  render json: { success: false, error: result[:error_message] }, status: :unprocessable_entity\nend","preventionTips":["Mint the card token immediately before submit — Stripe single-use tokens are consumed by the first request and expire after ~24h.","Keep params[:use_existing_card] in sync with whether the checkout form actually collected new card data.","Log subscription.external_id alongside failures so support can correlate the card_error_code from the updater's log lines."],"tags":["subscription","payment","credit-card","chargeable","stripe"],"backgroundTag":"card-tokenization-failed","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}