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
- 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).
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
- 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.
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
- upi_recurring_authorization_required
- upi_recurring_authorization_required
- We could not verify this card for recurring payments. Please
- upi_recurring_authorization_required
- india_card_mandate_missing
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/27fd304b7f60a338.
Report an issue: GitHub.