antiwork/gumroad · error · Subscription::UpdateFailed

We could not verify this card for recurring payments. Please

Error message

We could not verify this card for recurring payments. Please try the card again or use a different payment method.

What it means

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.

Source

Thrown at app/services/subscription/updater_service.rb:346

      merchant_account = subscription.renewal_merchant_account
      setup_intent_id = credit_card.stripe_setup_intent_id
      setup_intent = ChargeProcessor.get_setup_intent(merchant_account, setup_intent_id) if setup_intent_id.present?
      mandate_id = setup_intent&.mandate if setup_intent&.succeeded?
      mandate = ChargeProcessor.get_mandate(merchant_account, mandate_id) if mandate_id.present?
      status = mandate&.status || "missing"
      payment_method_id = credit_card.processor_payment_method_id
      customer_matches = setup_intent&.customer_id == credit_card.stripe_customer_id
      binding_matches = setup_intent&.payment_method_id == payment_method_id &&
        StripeChargeProcessor.mandate_matches_payment_method?(mandate, payment_method_id)
      terms_match = indian_card_setup_intent_terms_match?(setup_intent)

      unless status == "active" && customer_matches && binding_matches && terms_match
        ErrorNotifier.notify(
          "Indian card update rejected without an active e-mandate",
          subscription: subscription.external_id,
          mandate_status: status
        )
        raise Subscription::UpdateFailed, "We could not verify this card for recurring payments. Please try the card again or use a different payment method."
      end

      stripe_chargeable = chargeable&.get_chargeable_for(StripeChargeProcessor.charge_processor_id)
      stripe_chargeable.validated_stripe_mandate_id = mandate.id if stripe_chargeable.respond_to?(:validated_stripe_mandate_id=)
      { clear_mandate_stop: true, stripe_mandate_id: mandate.id }
    rescue ChargeProcessorError => e
      ErrorNotifier.notify(e, subscription: subscription.external_id)
      raise Subscription::UpdateFailed, "We could not verify this card for recurring payments. Please try the card again or use a different payment method."
    end

    def indian_card_mandate_validation_required?(credit_card)
      subscription.india_card_mandate_reliability_enabled? &&
        credit_card.stripe_charge_processor? &&
        credit_card.requires_mandate?
    end

    def indian_card_setup_intent_terms_match?(setup_intent)
      return false unless setup_intent&.usage == "off_session"

View on GitHub (pinned to afeacbd394)

Solutions

  1. Restart the card-update flow end to end so a fresh SetupIntent with full SCA/mandate confirmation is created for this subscription.
  2. Verify via the Stripe API that the mandate status is "active" and setup_intent.metadata.gumroad_subscription_id matches this subscription.
  3. 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.
  4. Check the ErrorNotifier event (subscription external_id + mandate_status) for the exact failing condition.
Defensive patterns

Strategy: validation

Validate before calling

# Pre-flight: require an active mandate bound to this card before persisting the update
mandate_id = ChargeProcessor.get_setup_intent(merchant_account, card.stripe_setup_intent_id)&.mandate
status = ChargeProcessor.get_mandate(merchant_account, mandate_id)&.status
proceed = status == "active"

Try / catch

begin
  result = Subscription::UpdaterService.new(...).perform
rescue Subscription::UpdateFailed => e
  result = { success: false, error_message: e.message } # buyer redoes the mandate flow with a fresh SetupIntent
end

Prevention

When it happens

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

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

Related errors


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