{"record":{"id":"d7e4cff7a52fbd84","repo":"antiwork/gumroad","slug":"we-could-not-verify-this-card-for-recurring-paymen","errorCode":null,"errorMessage":"We could not verify this card for recurring payments. Please try the card again or use a different payment method.","messagePattern":"We could not verify this card for recurring payments\\. Please try the card again or use a different payment method\\.","errorType":"exception","errorClass":"Subscription::UpdateFailed","httpStatus":null,"severity":"error","filePath":"app/services/subscription/updater_service.rb","lineNumber":346,"sourceCode":"      merchant_account = subscription.renewal_merchant_account\n      setup_intent_id = credit_card.stripe_setup_intent_id\n      setup_intent = ChargeProcessor.get_setup_intent(merchant_account, setup_intent_id) if setup_intent_id.present?\n      mandate_id = setup_intent&.mandate if setup_intent&.succeeded?\n      mandate = ChargeProcessor.get_mandate(merchant_account, mandate_id) if mandate_id.present?\n      status = mandate&.status || \"missing\"\n      payment_method_id = credit_card.processor_payment_method_id\n      customer_matches = setup_intent&.customer_id == credit_card.stripe_customer_id\n      binding_matches = setup_intent&.payment_method_id == payment_method_id &&\n        StripeChargeProcessor.mandate_matches_payment_method?(mandate, payment_method_id)\n      terms_match = indian_card_setup_intent_terms_match?(setup_intent)\n\n      unless status == \"active\" && customer_matches && binding_matches && terms_match\n        ErrorNotifier.notify(\n          \"Indian card update rejected without an active e-mandate\",\n          subscription: subscription.external_id,\n          mandate_status: status\n        )\n        raise Subscription::UpdateFailed, \"We could not verify this card for recurring payments. Please try the card again or use a different payment method.\"\n      end\n\n      stripe_chargeable = chargeable&.get_chargeable_for(StripeChargeProcessor.charge_processor_id)\n      stripe_chargeable.validated_stripe_mandate_id = mandate.id if stripe_chargeable.respond_to?(:validated_stripe_mandate_id=)\n      { clear_mandate_stop: true, stripe_mandate_id: mandate.id }\n    rescue ChargeProcessorError => e\n      ErrorNotifier.notify(e, subscription: subscription.external_id)\n      raise Subscription::UpdateFailed, \"We could not verify this card for recurring payments. Please try the card again or use a different payment method.\"\n    end\n\n    def indian_card_mandate_validation_required?(credit_card)\n      subscription.india_card_mandate_reliability_enabled? &&\n        credit_card.stripe_charge_processor? &&\n        credit_card.requires_mandate?\n    end\n\n    def indian_card_setup_intent_terms_match?(setup_intent)\n      return false unless setup_intent&.usage == \"off_session\"","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/subscription/updater_service.rb#L328-L364","documentation":"For Indian cards under the india_card_mandate_reliability feature, validate_indian_card_mandate! requires an active Stripe mandate whose SetupIntent binds to the same Stripe customer and payment method, carries this subscription's external_id in metadata, and whose card_mandate_options exactly match the recomputed terms (amount_type maximum, amount, currency, reference encoding the subscription id, interval, interval_count, supported_types including \"india\"). Any miss — including mandate status \"missing\" — raises UpdateFailed and fires an ErrorNotifier event with the subscription id and mandate_status.","triggerScenarios":"Buyer supplies a requires_mandate? Stripe card while the setup intent never succeeded or was canceled, references a different customer/payment method, its metadata gumroad_subscription_id belongs to another subscription, or the mandate terms don't match the server-recomputed terms (amount/currency/interval drift between setup intent creation and update submission, e.g. price changed or a mandate-rate stamp is absent on old intents).","commonSituations":"Buyer abandons the 3DS/mandate confirmation step or hits browser-back after SCA; mandate revoked in the Stripe dashboard; setup intent reused from a different membership; discount or billing-info change altering expected terms between steps.","solutions":["Restart the card-update flow end to end so a fresh SetupIntent with full SCA/mandate confirmation is created for this subscription.","Verify via the Stripe API that the mandate status is \"active\" and setup_intent.metadata.gumroad_subscription_id matches this subscription.","If terms mismatch: confirm the price/discount submitted is the one the mandate was authorized for; redo the flow after any price or contact-info change.","Check the ErrorNotifier event (subscription external_id + mandate_status) for the exact failing condition."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"# Pre-flight: require an active mandate bound to this card before persisting the update\nmandate_id = ChargeProcessor.get_setup_intent(merchant_account, card.stripe_setup_intent_id)&.mandate\nstatus = ChargeProcessor.get_mandate(merchant_account, mandate_id)&.status\nproceed = status == \"active\"","typeGuard":null,"tryCatchPattern":"begin\n  result = Subscription::UpdaterService.new(...).perform\nrescue Subscription::UpdateFailed => e\n  result = { success: false, error_message: e.message } # buyer redoes the mandate flow with a fresh SetupIntent\nend","preventionTips":["Complete the full SCA/mandate confirmation before submitting the membership update — never submit with a half-finished setup intent.","Never reuse a SetupIntent across subscriptions; metadata gumroad_subscription_id is verified per subscription.","If price or billing info changes, redo the card update so the mandate terms match the recomputed amounts."],"tags":["subscription","stripe","india","mandate","rbi","e-mandate","setup-intent"],"backgroundTag":"rbi-emandate-validation-failed","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}