{"record":{"id":"16f918bfc503a206","repo":"antiwork/gumroad","slug":"upi-recurring-authorization-required","errorCode":"upi_recurring_authorization_required","errorMessage":"Your saved UPI payment method can no longer be used. Please update your payment method to continue your membership.","messagePattern":"Your saved UPI payment method can no longer be used\\. Please update your payment method to continue your membership\\.","errorType":"exception","errorClass":"ChargeProcessorCardError","httpStatus":null,"severity":"error","filePath":"app/business/payments/charging/implementations/stripe/stripe_charge_processor.rb","lineNumber":1434,"sourceCode":"        if !off_session\n          \"charge was not off-session\"\n        elsif currency.to_s.downcase != Currency::INR\n          \"charge currency was #{currency.inspect}\"\n        elsif !chargeable.recurring_authorization_verified?\n          \"authorization was not verified\"\n        elsif chargeable.recurring_authorization_currency.to_s.downcase != Currency::INR\n          \"stored authorization currency was #{chargeable.recurring_authorization_currency.inspect}\"\n        elsif chargeable.recurring_authorization_max_amount_cents.to_i <= 0\n          \"stored authorization maximum was missing\"\n        elsif amount_cents > chargeable.recurring_authorization_max_amount_cents.to_i\n          \"charge amount #{amount_cents} exceeded stored maximum #{chargeable.recurring_authorization_max_amount_cents}\"\n        elsif amount_cents > Checkout::PaymentMethodResolver::UPI_RECURRING_MAX_INR_CENTS\n          \"charge amount #{amount_cents} exceeded Stripe's UPI recurring limit\"\n        end\n      return if reason.nil?\n\n      ErrorNotifier.notify(\"UPI Autopay renewal rejected before Stripe submit\", reason:)\n      raise ChargeProcessorCardError.new(PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED, UPI_PAYMENT_METHOD_UPDATE_MESSAGE)\n    end\n\n    def get_mandate_id_from_chargeable(chargeable, merchant_account)\n      if chargeable.stripe_setup_intent_id\n        setup_intent = if merchant_migrated?(merchant_account)\n          Stripe::SetupIntent.retrieve(chargeable.stripe_setup_intent_id, { stripe_account: merchant_account.charge_processor_merchant_id })\n        else\n          Stripe::SetupIntent.retrieve(chargeable.stripe_setup_intent_id)\n        end\n        setup_intent.mandate\n      elsif chargeable.stripe_payment_intent_id\n        original_payment_intent = if merchant_migrated?(merchant_account)\n          Stripe::PaymentIntent.retrieve(chargeable.stripe_payment_intent_id, { stripe_account: merchant_account.charge_processor_merchant_id })\n        else\n          Stripe::PaymentIntent.retrieve(chargeable.stripe_payment_intent_id)\n        end\n        original_charge = if merchant_migrated?(merchant_account)\n          Stripe::Charge.retrieve(original_payment_intent.latest_charge, { stripe_account: merchant_account.charge_processor_merchant_id })","sourceCodeStart":1416,"sourceCodeEnd":1452,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/business/payments/charging/implementations/stripe/stripe_charge_processor.rb#L1416-L1452","documentation":"ChargeProcessorCardError with PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED, raised by validate_upi_autopay_charge! before any Stripe call for UPI Autopay renewals. UPI exposes no reusable Mandate id (Stripe selects it from Customer + PaymentMethod), so the stored authorization is validated instead: the charge must be off-session, in INR, on a verified authorization whose stored currency is INR and whose max amount is positive, for an amount within both the stored cap and Stripe's UPI recurring limit (Checkout::PaymentMethodResolver::UPI_RECURRING_MAX_INR_CENTS). Any violation reports the precise reason via ErrorNotifier and raises with the buyer-facing 'update your payment method' message (UPI_PAYMENT_METHOD_UPDATE_MESSAGE).","triggerScenarios":"A membership renewal on a saved UPI method where: the stored authorization was never verified, recurring_authorization_max_amount_cents is 0/missing, the renewal amount exceeds the mandate's stored cap (e.g. a price increase), the amount exceeds Stripe's UPI recurring limit, the charge is not off-session, or the currency isn't INR.","commonSituations":"Seller raises a subscription price above the buyer's original UPI mandate cap; buyer cancelled the autopay mandate in their UPI app; legacy UPI subscriptions saved before authorization fields were persisted; currency drift away from INR.","solutions":["Ask the buyer to update their payment method — re-authorization creates a fresh mandate with a current cap.","Identify which invariant fired: the ErrorNotifier report's reason string names it exactly (not verified / cap missing / over stored max / over Stripe limit).","For price increases, run a re-authorization flow instead of charging above the stored cap.","Verify recurring_authorization_* fields are persisted at UPI setup time and backfill where possible.","Confirm the renewal amount is within Stripe's UPI recurring limit before scheduling the charge."],"exampleFix":"# before: schedule the renewal at the new (higher) price\nSubscriptionChargeJob.perform_later(subscription)\n# after: only schedule when the stored UPI authorization covers the amount\nif payment_method_upi?\n  max_cents = chargeable.recurring_authorization_max_amount_cents.to_i\n  if max_cents <= 0 || renewal_amount_cents > max_cents\n    require_upi_reauthorization!(subscription)\n  else\n    SubscriptionChargeJob.perform_later(subscription)\n  end\nend","handlingStrategy":"validation","validationCode":"# Ruby, before charging a UPI renewal\nverified = chargeable.recurring_authorization_verified?\nwithin_stored_cap = renewal_amount_cents <= chargeable.recurring_authorization_max_amount_cents.to_i\nwithin_stripe_limit = renewal_amount_cents <= Checkout::PaymentMethodResolver::UPI_RECURRING_MAX_INR_CENTS\ninr = currency.to_s.downcase == \"inr\"\n\nif verified && within_stored_cap && within_stripe_limit && inr && off_session\n  process_charge(...)\nelse\n  require_upi_reauthorization!(subscription)\nend","typeGuard":null,"tryCatchPattern":"begin\n  process_charge(...)\nrescue ChargeProcessorCardError => e\n  if e.code == PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED\n    notify_buyer_to_update_payment_method(purchase) # re-auth creates a fresh mandate with a current cap\n  else\n    raise\n  end\nend","preventionTips":["Persist authorization currency and max amount at UPI setup and re-verify before every renewal.","Trigger re-authorization on price increases instead of charging past the mandate cap.","Treat 'UPI Autopay renewal rejected' ErrorNotifier reports as a queue of members needing re-auth, not noise.","Keep the pre-submit guard — submitting anyway just converts a clear message into an issuer decline."],"tags":["stripe","upi","autopay","india","inr","recurring-payments","mandate-limit"],"backgroundTag":"upi-recurring-limit-exceeded","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}