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 (code upi_recurring_authorization_required) by Purchase#fail_upi_recurring_authorization! on the renewal path (create_charge_intent for subscriptions). It fires before Stripe is called when a UPI Autopay renewal fails a pre-submit reason (e.g. mandate cap, mandate state, currency conditions in mandate cap conversion), with the same buyer-facing 'update your payment method' message. The purchase id and reason are sent to ErrorNotifier as "UPI Autopay renewal rejected before Stripe submit".

Source

Thrown at app/models/purchase.rb:4836

        purchases: [self],
        amount_cents: total_transaction_cents,
        gumroad_amount_cents: total_transaction_amount_for_gumroad_cents,
        required_currency:,
        **required_currency_errors
      ).perform
      return {} if result.blank?

      {
        processor_amount_cents: result.processor_amount_cents,
        processor_currency: result.processor_currency,
        processor_gumroad_amount_cents: result.processor_gumroad_amount_cents,
        stripe_fx_quote_id: result.stripe_fx_quote_id,
      }
    end

    def fail_upi_recurring_authorization!(reason)
      ErrorNotifier.notify("UPI Autopay renewal rejected before Stripe submit", reason:, purchase_id: id)
      raise ChargeProcessorCardError.new(
        PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED,
        StripeChargeProcessor::UPI_PAYMENT_METHOD_UPDATE_MESSAGE
      )
    end

    def defer_upi_recurring_renewal!(reason)
      ErrorNotifier.notify("UPI Autopay renewal deferred before Stripe submit", reason:, purchase_id: id)
      raise ChargeProcessorUnavailableError, "UPI Autopay renewals are temporarily paused"
    end

    # Converts the RBI e-mandate cap into the currency this charge will actually settle in. See
    # Charge::CreateService#mandate_options_in_charge_currency for the full reasoning; this is the
    # renewal-path counterpart, kept deliberately identical in behaviour.
    def mandate_options_in_charge_currency(mandate_options, presentment_args, canonical_amount_cents)
      return mandate_options if mandate_options.blank?

      presentment_currency = presentment_args[:processor_currency]
      return mandate_options if presentment_currency.blank? || presentment_currency == Currency::USD

View on GitHub (pinned to afeacbd394)

Solutions

  1. Notify the buyer to update/re-authorize their payment method — the saved UPI mandate can no longer be charged; the error is by design terminal for that method.
  2. Check the ErrorNotifier payload (reason, purchase_id) to distinguish cap-exceeded from mandate-inactive; a cap breach may instead warrant plan adjustment (lower renewal amount) before re-authorization.
  3. Verify the mandate cap conversion (see the comment on converting the RBI e-mandate cap into the settlement currency) is using the current FX quote — a stale rate can wrongly classify a renewal as over-cap.
  4. If the reason looks transient (quote/currency related), confirm the code chose fail_upi_recurring_authorization! rather than defer_upi_recurring_renewal! — the latter pauses and retries rather than failing the member.

Example fix

# before: billing job treats every renewal failure alike
begin
  purchase.create_charge_intent(chargeable)
rescue ChargeProcessorCardError
  purchase.mark_failed
end
# after: branch on the UPI code to trigger re-auth flow
begin
  purchase.create_charge_intent(chargeable)
rescue ChargeProcessorCardError => e
  if e.error_code == PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED
    SubscriptionPaymentMethodUpdateMailer.notify(purchase.subscription)
  else
    purchase.mark_failed
  end
end
Defensive patterns

Strategy: try-catch

Validate before calling

# before scheduling a UPI renewal, check the mandate's usability signal you already store
if subscription.saved_upi? && !subscription.upi_mandate_usable?
  PaymentMethodUpdateMailer.notify(subscription)
  next
end

Type guard

def upi_renewal_chargeable?(purchase)
  chargeable = purchase.stripe_chargeable
  chargeable.nil? || !chargeable.respond_to?(:recurring_authorization_verified_at) ||
    chargeable.recurring_authorization_verified_at.present?
end

Try / catch

begin
  purchase.create_charge_intent(chargeable)
rescue ChargeProcessorCardError => e
  case e.error_code
  when PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED
    subscription.notify_buyer_reauth_required # terminal: do not retry with same method
  else
    raise
  end
end

Prevention

When it happens

Trigger: A subscription renewal (Purchase#create_charge_intent) whose saved chargeable is UPI hits a rejection reason: RBI e-mandate amount cap would be exceeded, the mandate is no longer active, or the converted cap in the charge currency makes the renewal non-compliant. defer_upi_recurring_renewal! is the transient sibling (raises ChargeProcessorUnavailableError); this one is the terminal branch.

Common situations: Indian Rupee subscriptions on UPI Autopay at renewal time: buyer's mandate hit the RBI per-transaction or per-period cap; mandate revoked in the buyer's UPI app; amount or currency of the renewal drifted from what the mandate authorizes; sandbox mandates without real caps.

Related errors


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