Stripe Installments (MSI) Not Showing in Mexico? The Automatic Payment Methods Trap and the Fix — Cesar Ayala
← All posts

Stripe Installments (MSI) Not Showing in Mexico? The Automatic Payment Methods Trap and the Fix

If your Stripe meses sin intereses are enabled in the Dashboard but never render in the Payment Element, it's almost always the Automatic Payment Methods trap (stripe-js #454): dynamic PMs don't surface installments. Set payment_method_options[card][installments][enabled]=true explicitly on the PaymentIntent. Then confirm MXN, a Mexican consumer credit card, and the per-plan minimum amount.

The short answer: enable installments explicitly on the PaymentIntent

If your Stripe meses sin intereses (MSI) are enabled in the Dashboard but never render, the fix is one line: set payment_method_options[card][installments][enabled]=true explicitly on the PaymentIntent. Automatic Payment Methods alone will not surface installments (stripe-js #454). Then confirm you are charging in MXN, with a Mexican consumer credit card, and that the amount clears the per-plan minimum.

The symptom: MSI enabled in the Dashboard, nothing in the Payment Element

You did everything the onboarding told you to. You turned on meses sin intereses in the Stripe Dashboard under your payment method settings. You wired up the Payment Element with automatic_payment_methods: { enabled: true } because that is what every quickstart shows. You test with a real MXN charge over MXN 1,000, and… the card form renders fine, but there is no “pay in installments” dropdown. No 3, 6, 12 months. Nothing.

The frustrating part is that the Dashboard swears MSI is active. Your account is Mexican, your currency is right, and support articles say installments “appear automatically.” So you start doubting the card, the amount, the issuer — when the real problem is one boolean you never set.

The root cause: Automatic Payment Methods vs explicit installments

Here is the contradiction. Stripe’s docs strongly imply that with dynamic (automatic) payment methods, eligible options — including installments — surface on their own based on currency, amount, and your Dashboard settings. In practice, they do not. This is stripe-js issue #454, opened on 2023-06-02 and still biting integrators years later.

When you create a PaymentIntent with Automatic Payment Methods, Stripe decides which payment methods to show, but it does not turn on the card installments sub-feature. The Payment Element only renders the MSI selector when the PaymentIntent carries an explicit payment_method_options[card][installments][enabled]=true. The Dashboard toggle governs eligibility and which plans are allowed; it does not inject the per-PaymentIntent flag for you. So you end up in a state where MSI is “enabled” account-wide but disabled on the exact object that drives the UI.

This is the number-one “my MSI don’t show up” bug in Mexican Stripe integrations, and it is invisible unless you know to look for it.

Automatic Payment Methods only

  • automatic_payment_methods.enabled = true
  • No explicit installments flag on the PaymentIntent
  • Dashboard MSI toggle is ON
  • Payment Element renders — but NO installments selector

Explicit installments config

  • payment_method_options.card.installments.enabled = true
  • Currency is MXN, amount clears the per-plan minimum
  • Mexican consumer credit card entered
  • MSI selector renders with eligible plans (3/6/9/12/18/24)

The fix: set installments enabled on the PaymentIntent

You set the flag when you create (or update) the PaymentIntent, server-side. You can keep Automatic Payment Methods on — you just need to add the explicit installments block so Stripe knows to offer MSI for card.

Before — the version that silently swallows MSI:

// Node — installments NEVER render, even with MSI on in the Dashboard
const paymentIntent = await stripe.paymentIntents.create({
  amount: 150000,            // MXN 1,500.00, in centavos
  currency: "mxn",
  automatic_payment_methods: { enabled: true },
});

After — the version that actually offers meses sin intereses:

// Node — the explicit flag that makes the MSI selector appear
const paymentIntent = await stripe.paymentIntents.create({
  amount: 150000,            // MXN 1,500.00, in centavos
  currency: "mxn",
  automatic_payment_methods: { enabled: true },
  payment_method_options: {
    card: {
      installments: { enabled: true },
    },
  },
});

That is the whole fix for the trap. Ship the PaymentIntent’s client_secret to your Payment Element as usual; the installments dropdown now renders whenever the card and amount are eligible. If you build the plan list yourself, Stripe returns the available_plans on the PaymentIntent once a qualifying card is entered — but you do not need to enumerate them to make the selector appear.

Still not showing? Amount below the per-plan minimum

The second most common cause: the charge is too small for the plan the buyer wants. Stripe’s MSI minimums are per plan, not a flat MXN 300. Longer terms require larger amounts, and a plan whose minimum you have not cleared simply will not be offered.

3 monthsMXN 300
6 monthsMXN 600
9 monthsMXN 900
12 monthsMXN 1,200
18 monthsMXN 1,800
24 monthsMXN 2,400

So a MXN 1,500 charge is eligible for 3, 6, 9, and 12 months, but not 18 (needs MXN 1,800) or 24 (needs MXN 2,400). If your test cart is MXN 500 and you are wondering why only “3 months” shows, that is expected: MXN 500 clears the 3-month minimum but nothing longer. You can override these minimums in the Dashboard, but until you do, treat the defaults above as the gate.

Remember amounts are in centavos: amount: 150000 is MXN 1,500.00, not MXN 150,000.

Currency, card type, and issuer traps

Even with the flag set and the amount high enough, MSI is narrowly scoped. It is a Mexico-only product with hard eligibility rules, and violating any one of them means no installments:

  • Currency must be MXN. MSI does not exist for USD or any other currency. A PaymentIntent in usd will never offer installments, no matter what.
  • Card must be a Mexican consumer credit card. Debit cards do not qualify. Corporate/commercial cards do not qualify. This is the single most common “it works for me but not for my QA tester” difference — someone is testing with a debit card.
  • Issuer must support MSI. Supported issuers include BBVA, Santander, Amex, Banamex, and others. An unsupported or foreign issuer will not present plans.
  • Banamex does not offer 18 or 24 months. Banamex supports only 3, 6, 9, and 12. If your UI hard-codes an 18- or 24-month option and the buyer pays with a Banamex card, that plan will be missing — this is correct behavior, not a bug.

The debit-vs-credit and MXN-only rules are why MSI “disappears” between environments. It is not flaky; it is enforcing card eligibility.

Debug checklist: the 7 things to verify, in order

When MSI still will not show, walk this list top to bottom. The first failing check is almost always your answer.

  1. 1. Explicit installments flag setpayment_method_options.card.installments.enabled = true on THIS PaymentIntent — not just the Dashboard toggle
  2. 2. Currency is mxnAny non-MXN currency disqualifies MSI entirely
  3. 3. Amount clears the per-plan minimum300/600/900/1,200/1,800/2,400 for 3/6/9/12/18/24 — and it is in centavos
  4. 4. Card is a Mexican CREDIT cardNot debit, not corporate/commercial
  5. 5. Issuer supports MSIBBVA, Santander, Amex, Banamex, etc.; Banamex maxes out at 12 months
  6. 6. Dashboard MSI is enabledPayment method settings — and the desired plans are allowed, not disabled
  7. 7. Payment Element uses the right client_secretConfirm the Element is mounted with the PaymentIntent that carries the installments flag

If checks 1–6 pass and MSI still will not render, you are almost certainly on check 7: the front end mounted a stale or different client_secret than the PaymentIntent you just fixed. Log the PaymentIntent id on both sides and confirm they match.

How to confirm it works in test mode

You do not need a real Banamex card to prove the wiring. Stripe ships test cards that exercise the MSI flow. Create the PaymentIntent exactly as in the “after” snippet above, then inspect the object to confirm the installments block is present before you even touch the front end:

curl https://api.stripe.com/v1/payment_intents \
  -u "sk_test_...:" \
  -d amount=150000 \
  -d currency=mxn \
  -d "automatic_payment_methods[enabled]=true" \
  -d "payment_method_options[card][installments][enabled]=true"

In the JSON response, verify that payment_method_options.card.installments.enabled is true. If it is, the server side is correct and any remaining problem is currency, amount, card, or the front-end client_secret. Then load your Payment Element in test mode, enter a Mexican test credit card, and confirm the installments selector renders with the plans your amount qualifies for.

Create PaymentIntent with installments.enabled = true
Inspect JSON: installments.enabled is true
Mount Payment Element with that client_secret
Enter Mexican test credit card in MXN
MSI selector renders eligible plans

Stripe’s MSI acceptance guide lists the current test cards and expected behavior.

// Optional server-side assertion in a test
if (!paymentIntent.payment_method_options?.card?.installments?.enabled) {
  throw new Error("Installments not enabled on PaymentIntent — the #454 trap");
}

Wire that assertion into your integration tests so a future refactor that drops the installments block fails loudly instead of silently regressing to the “no MSI selector” state. It catches the trap on the server, before any buyer ever loads the Payment Element.

Where MSI fits in your stack: API, webhooks, and reconciliation

MSI changes what the buyer is offered and who eats the financing fee — the merchant absorbs it — but it does not change your webhook or settlement plumbing. The PaymentIntent still succeeds through the same payment_intent.succeeded event, and you still reconcile the same way. Do not build a parallel code path for installment payments; treat them as ordinary card charges that happen to carry an installment count.

That said, the usual gateway engineering still applies: verify webhook signatures, make your handlers idempotent, and reconcile against Stripe rather than trusting the client. If you are wiring this from scratch, start with how to integrate Stripe into your SaaS and lock down your event handling with Stripe webhook signature verification. If you are deciding whether Stripe is even the right rail for MSI in Mexico versus Conekta or Mercado Pago, see payment gateways in Mexico and the broader LATAM payments hub.

For the full API-first walkthrough of enabling meses sin intereses across Stripe, Conekta, and Mercado Pago — including the per-plan minimums and the vendor-specific fields — read the sibling post: meses sin intereses by API.

One last engineering note: once the charge settles, you will likely need to issue a CFDI for it. MSI does not change the invoice — you are still billing the full amount — so the auto-invoice CFDI from Stripe flow works unchanged.

Sources: Stripe — MX installments, Stripe — accept a meses sin intereses payment, stripe-js issue #454.