{"record":{"id":"27fd304b7f60a338","repo":"antiwork/gumroad","slug":"india-card-mandate-missing","errorCode":"india_card_mandate_missing","errorMessage":"Your card's recurring payment authorization is missing. Please re-enter your payment method to complete this payment.","messagePattern":"Your card's recurring payment authorization is missing\\. Please re-enter your payment method to complete this payment\\.","errorType":"exception","errorClass":"ChargeProcessorCardError","httpStatus":null,"severity":"error","filePath":"app/business/payments/charging/implementations/stripe/stripe_charge_processor.rb","lineNumber":401,"sourceCode":"        # decline mandate-less recurring charges (as \"transaction_not_allowed\"), so submitting\n        # this charge would just burn a guaranteed decline. Report it so we can see how often\n        # registration silently produces no mandate, and — when the flag is on — fail fast with\n        # our own error code so the buyer is asked to re-authorize their card (which registers\n        # a fresh mandate) instead of receiving an issuer decline they can't act on.\n        #\n        # This is gated on `mandate_expected` (subscription renewals and preorder release\n        # charges) because `off_session` alone does not mean \"rebill of a saved card\":\n        # multi-seller cart checkouts also charge off-session, and those first-time charges\n        # can legitimately have no mandate to reference — failing them here would block\n        # valid checkouts, and reporting them would pollute the renewal-prevalence data.\n        fail_fast = Feature.active?(:fail_india_recurring_charge_without_mandate)\n        ErrorNotifier.notify(\n          \"Off-session charge on an Indian card has no e-mandate to reference\",\n          reference:,\n          fail_fast:\n        )\n        if fail_fast\n          raise ChargeProcessorCardError.new(\n            PurchaseErrorCode::INDIA_CARD_MANDATE_MISSING,\n            \"Your card's recurring payment authorization is missing. Please re-enter your payment method to complete this payment.\"\n          )\n        end\n      end\n    end\n\n    # Request 3DS manually when preparing future charges for all Indian cards. Ref: https://github.com/gumroad/web/issues/20783\n    params.deep_merge!(REQUEST_MANUAL_3DS_PARAMS) if should_setup_future_usage && !upi_autopay && chargeable.country == Compliance::Countries::IND.alpha2\n\n    if statement_description\n      statement_description = statement_description.gsub(%r{[^A-Z0-9./\\s]}i, \"\").to_s.strip[0...22]\n      params[:statement_descriptor_suffix] = statement_description if statement_description.present?\n    end\n\n    with_stripe_error_handler do\n      stripe_options = {}\n      stripe_options[:stripe_version] = StripeFxQuote::API_VERSION if stripe_fx_quote_id.present?","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/business/payments/charging/implementations/stripe/stripe_charge_processor.rb#L383-L419","documentation":"ChargeProcessorCardError with PurchaseErrorCode::INDIA_CARD_MANDATE_MISSING, raised while preparing an off-session Stripe charge on an Indian card when `mandate_expected` (subscription renewals, preorder release charges) but no e-mandate id can be resolved from the chargeable's SetupIntent/PaymentIntent. Indian issuers decline mandate-less recurring charges (as 'transaction_not_allowed'), so under the fail_india_recurring_charge_without_mandate feature flag the charge fails fast with an actionable message instead of burning a guaranteed issuer decline. Without the flag it only reports via ErrorNotifier and submits anyway.","triggerScenarios":"Renewing a membership or releasing a preorder charged to an Indian card where the saved SetupIntent/PaymentIntent has mandate nil — the original purchase predates mandate enforcement, Stripe never created the Mandate, or the mandate data was lost during merchant-account migration — while off_session and mandate_expected are both true.","commonSituations":"Subscriptions created before the RBI e-mandate requirement shipped; checkout paths that skipped the India-specific SetupIntent/manual-3DS flow; Stripe merchant migrations dropping mandates; note that first-time multi-seller cart charges are deliberately excluded via the mandate_expected gate.","solutions":["Direct the buyer to re-enter their payment method (the message says so): a fresh authenticated setup registers a new mandate.","Verify the charge really is a rebill — mandate_expected must be true only for renewals and preorder releases; first-time off-session carts must not hit this path.","Inspect the stored stripe_setup_intent_id / stripe_payment_intent_id and retrieve the intent in Stripe to confirm mandate is nil rather than a retrieval bug.","Operators: use the ErrorNotifier report to size prevalence; the feature flag decides between fail-fast and submit-and-absorb-the-issuer-decline.","Long term: ensure Indian checkouts always build SetupIntents with mandate data (the REQUEST_MANUAL_3DS_PARAMS path directly below this raise)."],"exampleFix":"# before: submitting the renewal and hoping the issuer allows a mandate-less charge\nintent = create_stripe_charge(...)\n# after: route to re-authorization when the mandate is missing\nmandate_id = get_mandate_id_from_chargeable(chargeable, merchant_account)\nif mandate_expected && mandate_id.nil?\n  require_buyer_reauthorization!(purchase)\n  return\nend\nintent = create_stripe_charge(...)","handlingStrategy":"validation","validationCode":"# Ruby, before charging: verify an e-mandate exists for expected recurring charges\nmandate_id = get_mandate_id_from_chargeable(chargeable, merchant_account)\nif mandate_expected && mandate_id.nil?\n  require_buyer_reauthorization!(purchase) # fresh authenticated setup registers a new mandate\n  return\nend\nprocess_charge(...)","typeGuard":null,"tryCatchPattern":"begin\n  process_charge(...)\nrescue ChargeProcessorCardError => e\n  if e.code == PurchaseErrorCode::INDIA_CARD_MANDATE_MISSING\n    send_buyer_to_reenter_payment_method(purchase) # re-auth registers a fresh e-mandate\n  else\n    raise\n  end\nend","preventionTips":["Create Indian recurring charges only after an authenticated setup that yields a Mandate.","Store and validate stripe_setup_intent_id at checkout time, not at renewal time.","Never gate on off_session alone — multi-seller cart first charges legitimately lack mandates.","Monitor the ErrorNotifier report to find registration paths silently producing no mandate."],"tags":["stripe","india","e-mandate","recurring-payments","subscriptions","rbi-compliance"],"backgroundTag":"recurring-payment-mandate-missing","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}