{"record":{"id":"58a19b21b8d236a3","repo":"antiwork/gumroad","slug":"upi-recurring-authorization-required-58a19b","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/models/purchase.rb","lineNumber":4836,"sourceCode":"        purchases: [self],\n        amount_cents: total_transaction_cents,\n        gumroad_amount_cents: total_transaction_amount_for_gumroad_cents,\n        required_currency:,\n        **required_currency_errors\n      ).perform\n      return {} if result.blank?\n\n      {\n        processor_amount_cents: result.processor_amount_cents,\n        processor_currency: result.processor_currency,\n        processor_gumroad_amount_cents: result.processor_gumroad_amount_cents,\n        stripe_fx_quote_id: result.stripe_fx_quote_id,\n      }\n    end\n\n    def fail_upi_recurring_authorization!(reason)\n      ErrorNotifier.notify(\"UPI Autopay renewal rejected before Stripe submit\", reason:, purchase_id: id)\n      raise ChargeProcessorCardError.new(\n        PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED,\n        StripeChargeProcessor::UPI_PAYMENT_METHOD_UPDATE_MESSAGE\n      )\n    end\n\n    def defer_upi_recurring_renewal!(reason)\n      ErrorNotifier.notify(\"UPI Autopay renewal deferred before Stripe submit\", reason:, purchase_id: id)\n      raise ChargeProcessorUnavailableError, \"UPI Autopay renewals are temporarily paused\"\n    end\n\n    # Converts the RBI e-mandate cap into the currency this charge will actually settle in. See\n    # Charge::CreateService#mandate_options_in_charge_currency for the full reasoning; this is the\n    # renewal-path counterpart, kept deliberately identical in behaviour.\n    def mandate_options_in_charge_currency(mandate_options, presentment_args, canonical_amount_cents)\n      return mandate_options if mandate_options.blank?\n\n      presentment_currency = presentment_args[:processor_currency]\n      return mandate_options if presentment_currency.blank? || presentment_currency == Currency::USD","sourceCodeStart":4818,"sourceCodeEnd":4854,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/models/purchase.rb#L4818-L4854","documentation":"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\".","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","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.","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."],"exampleFix":"# before: billing job treats every renewal failure alike\nbegin\n  purchase.create_charge_intent(chargeable)\nrescue ChargeProcessorCardError\n  purchase.mark_failed\nend\n# after: branch on the UPI code to trigger re-auth flow\nbegin\n  purchase.create_charge_intent(chargeable)\nrescue ChargeProcessorCardError => e\n  if e.error_code == PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED\n    SubscriptionPaymentMethodUpdateMailer.notify(purchase.subscription)\n  else\n    purchase.mark_failed\n  end\nend","handlingStrategy":"try-catch","validationCode":"# before scheduling a UPI renewal, check the mandate's usability signal you already store\nif subscription.saved_upi? && !subscription.upi_mandate_usable?\n  PaymentMethodUpdateMailer.notify(subscription)\n  next\nend","typeGuard":"def upi_renewal_chargeable?(purchase)\n  chargeable = purchase.stripe_chargeable\n  chargeable.nil? || !chargeable.respond_to?(:recurring_authorization_verified_at) ||\n    chargeable.recurring_authorization_verified_at.present?\nend","tryCatchPattern":"begin\n  purchase.create_charge_intent(chargeable)\nrescue ChargeProcessorCardError => e\n  case e.error_code\n  when PurchaseErrorCode::UPI_RECURRING_AUTHORIZATION_REQUIRED\n    subscription.notify_buyer_reauth_required # terminal: do not retry with same method\n  else\n    raise\n  end\nend","preventionTips":["Track mandate state changes (revocation notifications) and proactively email buyers before the renewal date.","Keep renewal amounts within the registered RBI mandate cap; convert the cap in the settlement currency with a fresh quote.","Distinguish terminal fail_upi_recurring_authorization! from transient defer_upi_recurring_renewal! in alerting — only the former should page."],"tags":["upi","subscriptions","renewal","india","rbi-emandate","stripe"],"backgroundTag":"upi-recurring-mandate-invalid","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}