antiwork/gumroad · error · ChargeProcessorCardError

india_card_mandate_missing

india_card_mandate_missing

Error message

Your card's recurring payment authorization is missing. Please re-enter your payment method to complete this payment.

What it means

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.

Source

Thrown at app/business/payments/charging/implementations/stripe/stripe_charge_processor.rb:401

        # decline mandate-less recurring charges (as "transaction_not_allowed"), so submitting
        # this charge would just burn a guaranteed decline. Report it so we can see how often
        # registration silently produces no mandate, and — when the flag is on — fail fast with
        # our own error code so the buyer is asked to re-authorize their card (which registers
        # a fresh mandate) instead of receiving an issuer decline they can't act on.
        #
        # This is gated on `mandate_expected` (subscription renewals and preorder release
        # charges) because `off_session` alone does not mean "rebill of a saved card":
        # multi-seller cart checkouts also charge off-session, and those first-time charges
        # can legitimately have no mandate to reference — failing them here would block
        # valid checkouts, and reporting them would pollute the renewal-prevalence data.
        fail_fast = Feature.active?(:fail_india_recurring_charge_without_mandate)
        ErrorNotifier.notify(
          "Off-session charge on an Indian card has no e-mandate to reference",
          reference:,
          fail_fast:
        )
        if fail_fast
          raise ChargeProcessorCardError.new(
            PurchaseErrorCode::INDIA_CARD_MANDATE_MISSING,
            "Your card's recurring payment authorization is missing. Please re-enter your payment method to complete this payment."
          )
        end
      end
    end

    # Request 3DS manually when preparing future charges for all Indian cards. Ref: https://github.com/gumroad/web/issues/20783
    params.deep_merge!(REQUEST_MANUAL_3DS_PARAMS) if should_setup_future_usage && !upi_autopay && chargeable.country == Compliance::Countries::IND.alpha2

    if statement_description
      statement_description = statement_description.gsub(%r{[^A-Z0-9./\s]}i, "").to_s.strip[0...22]
      params[:statement_descriptor_suffix] = statement_description if statement_description.present?
    end

    with_stripe_error_handler do
      stripe_options = {}
      stripe_options[:stripe_version] = StripeFxQuote::API_VERSION if stripe_fx_quote_id.present?

View on GitHub (pinned to afeacbd394)

Solutions

  1. Direct the buyer to re-enter their payment method (the message says so): a fresh authenticated setup registers a new mandate.
  2. 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.
  3. 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.
  4. Operators: use the ErrorNotifier report to size prevalence; the feature flag decides between fail-fast and submit-and-absorb-the-issuer-decline.
  5. Long term: ensure Indian checkouts always build SetupIntents with mandate data (the REQUEST_MANUAL_3DS_PARAMS path directly below this raise).

Example fix

# before: submitting the renewal and hoping the issuer allows a mandate-less charge
intent = create_stripe_charge(...)
# after: route to re-authorization when the mandate is missing
mandate_id = get_mandate_id_from_chargeable(chargeable, merchant_account)
if mandate_expected && mandate_id.nil?
  require_buyer_reauthorization!(purchase)
  return
end
intent = create_stripe_charge(...)
Defensive patterns

Strategy: validation

Validate before calling

# Ruby, before charging: verify an e-mandate exists for expected recurring charges
mandate_id = get_mandate_id_from_chargeable(chargeable, merchant_account)
if mandate_expected && mandate_id.nil?
  require_buyer_reauthorization!(purchase) # fresh authenticated setup registers a new mandate
  return
end
process_charge(...)

Try / catch

begin
  process_charge(...)
rescue ChargeProcessorCardError => e
  if e.code == PurchaseErrorCode::INDIA_CARD_MANDATE_MISSING
    send_buyer_to_reenter_payment_method(purchase) # re-auth registers a fresh e-mandate
  else
    raise
  end
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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