antiwork/gumroad · error · ChargeProcessorCardError
upi_recurring_authorization_required
upi_recurring_authorization_required
Error message
Your saved UPI payment method can no longer be used. Please update your payment method to continue your membership.
What it means
Raised as ChargeProcessorCardError from Purchase::LaterChargePresentmentService#fallback when a delayed charge (subscription renewal, installment, preorder release, commission completion) that must settle in a required currency (INR for UPI rails by default — required_currency_error_code defaults to UPI_RECURRING_AUTHORIZATION_REQUIRED) cannot use its stored presentment fixing. Non-transient reasons (no_stored_presentment, required_currency_mismatch, unsupported_currency, unsupported_charge_model, settlement_currency_mismatch, stale_fixing, non_positive_total) raise this card error; transient ones (quote_unavailable, wrapped exceptions) raise ChargeProcessorUnavailableError instead. Unlike ordinary cards, a required-currency rail cannot silently fall back to USD, so the buyer must update their payment method.
Source
Thrown at app/services/purchase/later_charge_presentment_service.rb:234
end
def fallback(reason, transient: false)
@fallback_reason = reason
Rails.logger.info("Later-charge presentment fallback for #{charge.present? ? "charge #{charge.external_id}" : "purchase #{purchases.first&.id}"}: #{reason}")
if required_currency.present?
notification = transient ? "Required-currency renewal deferred before processor submit" : "Required-currency renewal rejected before processor submit"
ErrorNotifier.notify(
notification,
reason:,
required_currency:,
purchase_id: purchases.first&.id,
charge_id: charge&.id
)
if transient
raise ChargeProcessorUnavailableError, "The required-currency quote is temporarily unavailable"
end
raise ChargeProcessorCardError.new(
required_currency_error_code,
required_currency_error_message
)
end
nil
end
end
View on GitHub (pinned to afeacbd394)
Solutions
- Treat it as terminal for the saved payment method: surface the UPI 'update your payment method' message so the buyer re-authenticates on a working INR rail.
- Inspect the ErrorNotifier payload (reason, required_currency, purchase_id, charge_id) — 'stale_fixing' points at a price-change/data issue you can repair (re-create a correct LaterChargePresentment) rather than a buyer action.
- For settlement_currency_mismatch / unsupported_charge_model, fix the merchant account's charge model or settlement config so the account is again eligible for buyer-currency presentment before the next renewal wave.
- Confirm transient sibling errors (ChargeProcessorUnavailableError 'The required-currency quote is temporarily unavailable') are retried by the billing loop rather than shown to the buyer — only this non-transient class should reach members.
Example fix
# before: treating later-charge presentment failure as a generic retryable error result = Purchase::LaterChargePresentmentService.new(...).perform rescue ChargeProcessorError => e retry_later # after: split transient vs terminal begin result = Purchase::LaterChargePresentmentService.new(merchant_account:, purchases:, amount_cents:, gumroad_amount_cents:, required_currency: "inr").perform rescue ChargeProcessorUnavailableError retry_later # quote temporarily unavailable rescue ChargeProcessorCardError => e request_payment_method_update(e.error_code) # terminal for the saved UPI method end
Defensive patterns
Strategy: try-catch
Validate before calling
# pre-check the conditions the service validates, before performing it
presentment = owner.current_later_charge_presentment
ok = presentment.present? &&
(required_currency.blank? || presentment.presentment_currency == required_currency) &&
StripeChargeProcessor.charge_minor_units_compatible?(presentment.presentment_currency) &&
Checkout::BuyerCurrencyEligibility.supported_merchant_account?(merchant_account)
request_payment_method_update unless ok Type guard
def later_charge_presentment_usable?(owner, merchant_account, required_currency)
p = owner&.current_later_charge_presentment
return false if p.nil?
return false if required_currency.present? && p.presentment_currency != required_currency.to_s.downcase
Checkout::BuyerCurrencyEligibility.supported_merchant_account?(merchant_account) &&
Checkout::BuyerCurrencyEligibility.usd_settling_merchant_account?(merchant_account, presentment_currency: p.presentment_currency)
end Try / catch
begin
result = svc.perform
rescue ChargeProcessorUnavailableError
schedule_retry_with_backoff # transient quote issue
rescue ChargeProcessorCardError => e
if e.error_code == PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED
request_payment_method_update # terminal for the saved UPI method
else
raise
end
end Prevention
- Keep required-currency (INR) products on supported, USD-settling merchant accounts so renewals don't lose eligibility.
- Re-fix stale presentments when link prices change so canonical_price_cents stays matched.
- Alert separately on the fallback reason values (stale_fixing, settlement_currency_mismatch) — they indicate data/config drift, not buyer error.
When it happens
Trigger: Performing Purchase::LaterChargePresentmentService with required_currency set (saved UPI method on an INR-priced link) where: the stored later-charge presentment is missing or in a different currency, the merchant account stopped being a supported/USD-settling account, the stored fixing's canonical price no longer matches and cannot be re-fixed (stale_fixing), or the computed presentment total is non-positive.
Common situations: INR subscription originally sold via UPI but the presentment record was never created or was for a different currency after price changes; seller's merchant account changed charging model or settlement configuration after sign-up; price change on the link making the stored fixing stale and the re-fix path returning nil; amount/tax combos producing a zero total after conversion.
Related errors
- upi_recurring_authorization_required
- Something went wrong.
- india_card_mandate_missing
- upi_recurring_authorization_required
- upi_recurring_authorization_required
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/f2b3edc73a5127fe.
Report an issue: GitHub.