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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/f2b3edc73a5127fe. Report an issue: GitHub.