antiwork/gumroad · error · Subscription::UpdateFailed
error_message (from get_chargeable, charge-processor card er
Error message
error_message (from get_chargeable, charge-processor card errors)
What it means
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.
Source
Thrown at app/services/subscription/updater_service.rb:117
begin
ActiveRecord::Base.transaction do
# Update subscription contact info
if params[:contact_info].present?
params[:contact_info][:country] = ISO3166::Country[params[:contact_info][:country]]&.common_name
original_purchase.is_updated_original_subscription_purchase = true
original_purchase.update!(params[:contact_info])
end
# Update card if necessary
unless use_existing_card?
had_saved_card = subscription.credit_card.present?
# (a) Get chargeable. Return if error
error_message = get_chargeable
if error_message.present?
logger.info("SubscriptionUpdater: Error fetching chargeable for subscription #{subscription.external_id}: #{error_message}")
raise Subscription::UpdateFailed, error_message
end
# (b) Create new credit card. Return if error.
replacement_card = CreditCard.create(chargeable, card_data_handling_mode, logged_in_user)
unless replacement_card.errors.empty?
logger.info("SubscriptionUpdater: Error creating new credit card for subscription #{subscription.external_id}: #{replacement_card.errors.full_messages}")
raise Subscription::UpdateFailed, replacement_card.errors.messages[:base].first
end
if indian_card_mandate_validation_required?(replacement_card)
# A plan update builds its replacement purchase before mandate validation. Keep the
# new card available for that build, but persist it only after validation succeeds.
subscription.credit_card = replacement_card
else
associate_replacement_card!(replacement_card, had_saved_card:, **validate_indian_card_mandate!(replacement_card))
replacement_card = nil
endView on GitHub (pinned to afeacbd394)
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.
Example fix
# before: params from a stale form, token already consumed
result = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform
# => { success: false, error_message: "We couldn't charge your card. Try again or use a different card." }
# after: always send the card fields the form just captured
params.merge!(use_existing_card: false) # plus fresh encrypted card / stripe token fields
result = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform
return render_error(result[:error_message]) unless result[:success] Defensive patterns
Strategy: validation
Validate before calling
# Fail fast before invoking the updater when new-card data is requested but absent
use_existing = ActiveModel::Type::Boolean.new.cast(params[:use_existing_card])
if !use_existing && CardParamsHelper.build_chargeable(params.merge(product_permalink: subscription.link.unique_permalink)).nil?
return { success: false, error_message: "We couldn't charge your card. Try again or use a different card." }
end Try / catch
# perform() already rescues Subscription::UpdateFailed into the result hash
result = Subscription::UpdaterService.new(subscription:, params:, logged_in_user:, gumroad_guid:, remote_ip:).perform
unless result[:success]
render json: { success: false, error: result[:error_message] }, status: :unprocessable_entity
end Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- replacement_card.errors.messages[:base].first
- We could not verify this card for recurring payments. Please
- error_message (purchase charge error)
- Something went wrong.
- india_card_mandate_missing
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/f2a9cb2cd31df683.
Report an issue: GitHub.