Accepting Card Payments in Installments

    Learn how to accept credit card payments using an installment plan with Elements and the Payment Intents API.

    Installments is a feature of consumer credit cards in some markets (known as meses sin intereses in Mexico) that allows customers to split purchases over multiple billing statements. You receive the full amount (minus a fee) as if it were a normal charge, and the customer’s bank handles collecting the money over time.

    Installments Quickstart

    Accepting Installments uses the Payment Intents API. It requires collecting payment details and Installment plan information on the client and completing the payment on the server.

    A diagram showing the collection of card details, creation of the PaymentIntent, selection of installment plan, and confirmation of the payment

    1. Collect payment method details on the client
    2. Retrieve Installment plans on the server
    3. Select an Installment plan on the client
    4. Confirm the PaymentIntent on the server

    Step 1: Collect payment method details on the client

    The Payment Intents API works with Stripe.js & Elements to securely collect payment information (e.g., credit card details) on the client side. To get started with Elements, include the following script on your pages. This script must always load directly from js.stripe.com in order to remain PCI compliant—you can’t include it in a bundle or host a copy of it yourself.

    <script src="https://js.stripe.com/v3/"></script>
    

    To securely collect card details from your customers, Elements creates UI components for you that are hosted by Stripe. They are then placed into your payment form, rather than you creating them directly. To determine where to insert these components, create empty DOM elements (containers) with unique IDs within your payment form.

    <div id="details">
      <input id="cardholder-name" type="text" placeholder="Cardholder name">
      <!-- placeholder for Elements -->
      <div id="card-element"></div>
      <button id="card-button">Next</button>
    </div>
    

    Next, create an instance of the Stripe object, providing your publishable API key as the first parameter, and then create an instance of the Elements object. Use the newly created object to mount a Card element in the relevant placeholder in the page.

    var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    var elements = stripe.elements();
    var cardElement = elements.create('card');
    cardElement.mount('#card-element');
    const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx');
    
    const elements = stripe.elements();
    const cardElement = elements.create('card');
    cardElement.mount('#card-element');

    Finally, use stripe.createPaymentMethod on your client to collect the card details and create a PaymentMethod when the user clicks the submit button.

    var cardholderName = document.getElementById('cardholder-name');
    var cardButton = document.getElementById('card-button');
    
    cardButton.addEventListener('click', function(ev) {
      stripe.createPaymentMethod('card', cardElement, {
        billing_details: {name: cardholderName.value}
      }).then(function(result) {
        if (result.error) {
          // Show error in payment form
        } else {
          // Otherwise send paymentMethod.id to your server (see Step 2)
          fetch('/collect_details', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ payment_method_id: result.paymentMethod.id })
          }).then(function(result) {
            // Handle server response (see Step 3)
            result.json().then(function(json) {
              handleInstallmentPlans(json);
            })
          });
        }
      });
    });
    
    
    const cardholderName = document.getElementById('cardholder-name');
    const cardButton = document.getElementById('card-button');
    
    cardButton.addEventListener('click', async (ev) => {
      const {paymentMethod, error} = await stripe.createPaymentMethod(
        'card',
        cardElement,
        {billing_details: {name: cardholderName.value}},
      );
      if (error) {
        // Show error in payment form
      } else {
        // Send paymentMethod.id to your server (see Step 2)
        const response = await fetch('/collect_details', {
          method: 'POST',
          headers: {'Content-Type': 'application/json'},
          body: JSON.stringify({payment_method_id: paymentMethod.id}),
        });
    
        const json = await response.json();
    
        // Handle server response (see Step 3)
        handleInstallmentPlans(json);
      }
    });
    
    

    Step 2: Retrieve Installment plans on the server

    To retrieve available installment plans, set up an endpoint on your server to receive the request.

    Create a new PaymentIntent with the ID of the PaymentMethod created on your client. Set payment_method_options.card.installments.enabled=true to allow this payment to use Installments. Send the available plans to the client so the customer can select which plan to pay with.

    curl https://api.stripe.com/v1/payment_intents \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d payment_method="{{PAYMENT_METHOD_ID}}" \
      -d amount=3099 \
      -d currency=usd \
      -d "payment_method_options[card][installments][enabled]"=true
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    require 'sinatra'
    require 'sinatra/json'
    
    post '/collect_details' do
      data = JSON.parse(request.body.read.to_s)
    
      begin
        # Create the PaymentIntent
        intent = Stripe::PaymentIntent.create(
          payment_method: data['payment_method_id'],
          amount: 3099,
          currency: 'mxn',
          payment_method_options: {
            card: {
              installments: {
                enabled: true,
              },
            },
          },
        )
      rescue Stripe::CardError => e
        # Display error on client
        return [500, {error: e.message}.to_json]
      end
    
      return [200, {
        intent_id: intent.id,
        available_plans: intent.payment_method_options.card.installments.available_plans,
      }.to_json,]
    end
    
    # Set your secret key: remember to change this to your live secret key in production
    # See your keys here: https://dashboard.stripe.com/account/apikeys
    stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
    
    
    # Using Flask
    from flask import request, jsonify
    
    @app.route('/collect_details', methods=['POST'])
    def collect_details():
    
        content = request.get_json()
        intent = stripe.PaymentIntent.create(
            amount=3099,
            currency='mxn',
            payment_method_types=['card'],
            payment_method=content['payment_method_id'],
            payment_method_options={
                'card': {
                    'installments': {
                        'enabled': True
                    }
                }
            }
        )
    
        available_plans = intent \
            .get('payment_method_options', {}) \
            .get('card', {}) \
            .get('installments', {}) \
            .get('available_plans', [])
    
        return jsonify({
            'intent_id': intent.id,
            'available_plans': available_plans
        })
    
    <?php
    
    # vendor using composer
    require_once('vendor/autoload.php');
    
    StripeStripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
    
    header('Content-Type: application/json');
    
    # retrieve json from POST body
    $json_str = file_get_contents('php://input');
    $json_obj = json_decode($json_str);
    
    $intent = StripePaymentIntent::create([
        'payment_method' => $json_obj->payment_method_id,
        'amount' => 3099,
        'currency' => 'mxn',
        'payment_method_options' => [
            'card' => [
                'installments' => [
                    'enabled' => true
                ]
            ]
        ],
    ]);
    
    echo json_encode([
        'intent_id' => $intent->id,
        'available_plans' => $intent->payment_method_options->card->installments->available_plans
    ]);
    
    ?>
    
    package com.stripe.generator;
    
    import static spark.Spark.post;
    
    import com.google.gson.Gson;
    import com.google.gson.annotations.SerializedName;
    import com.stripe.Stripe;
    import com.stripe.model.PaymentIntent;
    import com.stripe.param.PaymentIntentCreateParams;
    import com.stripe.param.PaymentIntentConfirmParams;
    
    import java.util.HashMap;
    import java.util.Map;
    import spark.Request;
    import spark.Response;
    import spark.Route;
    
    public class MyApp {
    
      private static final Gson gson = new Gson();
    
      static class CollectDetailsRequest {
        @SerializedName("payment_method_id")
        private String paymentMethodId;
    
        public String getPaymentMethodId() {
          return paymentMethodId;
        }
      }
    
      static class ConfirmPaymentRequest {
        @SerializedName("payment_intent_id")
        private String paymentIntentId;
    
        @SerializedName("selected_plan")
        private PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments.Plan selectedPlan;
    
        public String getPaymentIntentId() {
          return paymentIntentId;
        }
    
        public PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments.Plan getSelectedPlan() {
          return selectedPlan;
        }
      }
    
      /**
       * Your application.
       */
      public static void main(String[] args) {
        Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");
    
        post("/collect_details", new Route() {
          @Override
          public Object handle(Request request, Response response) {
            CollectDetailsRequest collectDetailsRequest =
                gson.fromJson(request.body(), CollectDetailsRequest.class);
            try {
              PaymentIntentCreateParams.PaymentMethodOptions.Card.Installments i =
                  PaymentIntentCreateParams.PaymentMethodOptions.Card.Installments.builder()
                    .setEnabled(true)
                    .build();
    
              PaymentIntentCreateParams.PaymentMethodOptions.Card card =
                  PaymentIntentCreateParams.PaymentMethodOptions.Card.builder()
                    .setInstallments(i)
                    .build();
    
              PaymentIntentCreateParams.PaymentMethodOptions pmo =
                  PaymentIntentCreateParams.PaymentMethodOptions.builder()
                    .setCard(card)
                    .build();
    
              PaymentIntentCreateParams createParams =
                  PaymentIntentCreateParams.builder()
                    .setAmount(3099L)
                    .setCurrency("mxn")
                    .setPaymentMethod(collectDetailsRequest.paymentMethodId)
                    .setPaymentMethodOptions(pmo)
                    .build();
    
              PaymentIntent intent = PaymentIntent.create(createParams);
    
              Map<String, Object> responseData = new HashMap<>();
    
              responseData.put("intent_id", intent.getId());
              responseData.put("available_plans",
                  intent.getPaymentMethodOptions()
                        .getCard()
                        .getInstallments()
                        .getAvailablePlans());
    
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              return makeErrorResponse(e);
            }
          }
        });
    
        post("/confirm_payment", new Route() {
          @Override
          public Object handle(Request request, Response response) {
            ConfirmPaymentRequest confirmPaymentRequest =
                gson.fromJson(request.body(), ConfirmPaymentRequest.class);
    
            try {
              PaymentIntentConfirmParams confirmParams;
    
              if (confirmPaymentRequest.getSelectedPlan() != null) {
                PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments i =
                    PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments.builder()
                      .setEnabled(true)
                      .setPlan(confirmPaymentRequest.getSelectedPlan())
                      .build();
    
    
                PaymentIntentConfirmParams.PaymentMethodOptions.Card card =
                    PaymentIntentConfirmParams.PaymentMethodOptions.Card.builder()
                      .setInstallments(i)
                      .build();
    
                PaymentIntentConfirmParams.PaymentMethodOptions pmo =
                    PaymentIntentConfirmParams.PaymentMethodOptions.builder()
                      .setCard(card)
                      .build();
    
    
                confirmParams = PaymentIntentConfirmParams.builder()
                    .setPaymentMethodOptions(pmo)
                    .build();
              } else {
                confirmParams = PaymentIntentConfirmParams.builder().build();
              }
    
              PaymentIntent intent = PaymentIntent.retrieve(confirmPaymentRequest.getPaymentIntentId());
              intent = intent.confirm(confirmParams);
    
              Map<String, Object> responseData = new HashMap<>();
    
              responseData.put("status", intent.getStatus());
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              return makeErrorResponse(e);
            }
          }
        });
      }
    
      private static Object makeErrorResponse(Exception e) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("message", e.getMessage());
        return gson.toJson(errorResponse);
      }
    }
    
    // Set your secret key: remember to change this to your live secret key in production
    // See your keys here: https://dashboard.stripe.com/account/apikeys
    const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
    
    
    // Using Express
    const express = require('express');
    const app = express();
    
    app.use(express.json());
    
    app.post('/collect_details', async (request, response) => {
      try {
        const intent = await stripe.paymentIntents.create({
          amount: 3099,
          currency: 'mxn',
          payment_method: request.body.payment_method_id,
          payment_method_options: {
            card: {
              installments: {
                enabled: true,
              },
            },
          },
        });
    
        // We could confirm without installments if no plans are available,
        // but let's give the user the chance to pick a different card
        // if installments are not available.
        return response.send({
          intent_id: intent.id,
          available_plans: intent.payment_method_options.card.installments.available_plans,
        });
      } catch (err) {
        return response.status(500).send({error: err.message});
      }
    });
    
    app.listen(3000, () => {
      console.log('Running on port 3000');
    });
    
    package main
    
    import (
      "encoding/json"
      "fmt"
      "net/http"
      "os"
    
      stripe "github.com/stripe/stripe-go"
      paymentintent "github.com/stripe/stripe-go/paymentintent"
    )
    
    // Structs to handle request and response JSON serializations
    type CollectDetailsRequest struct {
      PaymentMethodId *string `json:"payment_method_id"`
    }
    
    type ConfirmPaymentRequest struct {
      PaymentIntentId *string                                                             `json:"payment_intent_id"`
      SelectedPlan    *stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsPlanParams `json:"selected_plan"`
    }
    
    type CollectDetailsResponse struct {
      IntentId       *string                                                          `json:"intent_id"`
      AvailablePlans *[]*stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsPlan `json:"available_plans"`
    }
    
    type ConfirmPaymentResponse struct {
      Status *stripe.PaymentIntentStatus `json:"status"`
    }
    
    type ErrorResponse struct {
      Message string `json:"error"`
    }
    
    func writeError(err error, w http.ResponseWriter) {
      if stripeErr, ok := err.(*stripe.Error); ok {
        // Display error on client
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(ErrorResponse{Message: stripeErr.Msg})
      } else {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Other error occurred, %v\n", err.Error())
      }
    }
    
    func main() {
      // Initialize stripe-go with Stripe secret key from environment
      stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
    
      fs := http.FileServer(http.Dir("public"))
      http.Handle("/", fs)
    
      http.HandleFunc("/collect_details",
        func(w http.ResponseWriter, req *http.Request) {
          var collectDetailsRequest CollectDetailsRequest
          json.NewDecoder(req.Body).Decode(&collectDetailsRequest)
    
          params := &stripe.PaymentIntentParams{
            PaymentMethod: stripe.String(*collectDetailsRequest.PaymentMethodId),
            Amount:        stripe.Int64(3099),
            Currency:      stripe.String(string(stripe.CurrencyMXN)),
            PaymentMethodOptions: &stripe.PaymentIntentPaymentMethodOptionsParams{
              Card: &stripe.PaymentIntentPaymentMethodOptionsCardParams{
                Installments: &stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsParams{
                  Enabled: stripe.Bool(true),
                },
              },
            },
          }
    
          intent, err := paymentintent.New(params)
    
          if err != nil {
            writeError(err, w)
            return
          }
    
          w.WriteHeader(http.StatusOK)
          json.NewEncoder(w).Encode(CollectDetailsResponse{
            IntentId:       &intent.ID,
            AvailablePlans: &intent.PaymentMethodOptions.Card.Installments.AvailablePlans,
          })
        })
    
      http.HandleFunc("/confirm_payment",
        func(w http.ResponseWriter, req *http.Request) {
          var confirmPaymentRequest ConfirmPaymentRequest
          json.NewDecoder(req.Body).Decode(&confirmPaymentRequest)
    
          var intent *stripe.PaymentIntent
          var err error
    
          confirmParams := &stripe.PaymentIntentConfirmParams{}
    
          if confirmPaymentRequest.SelectedPlan != nil {
            confirmParams = &stripe.PaymentIntentConfirmParams{
              PaymentMethodOptions: &stripe.PaymentIntentPaymentMethodOptionsParams{
                Card: &stripe.PaymentIntentPaymentMethodOptionsCardParams{
                  Installments: &stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsParams{
                    Plan: confirmPaymentRequest.SelectedPlan,
                  },
                },
              },
            }
          }
    
          intent, err = paymentintent.Confirm(*confirmPaymentRequest.PaymentIntentId, confirmParams)
    
          if err != nil {
            writeError(err, w)
            return
          }
    
          w.WriteHeader(http.StatusOK)
          json.NewEncoder(w).Encode(ConfirmPaymentResponse{
            Status: &intent.Status,
          })
        })
    
      http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    }
    

    The PaymentIntent object lists the available installment plans for the PaymentMethod under payment_method_options.card.installments.available_plans.

    {
      "id": "pi_1FKDPTJXdud1yP2PpUXNgq0V",
      "object": "payment_intent",
      "amount": 3099,
    ...
      "payment_method_options": {
        "card": {
          "installments": {
            "enabled": true,
            "plan": null,
            "available_plans": [
              {
                "count": 3,
                "interval": "month",
                "type": "fixed_count"
              },
    See all 43 lines { "count": 6, "interval": "month", "type": "fixed_count" }, { "count": 9, "interval": "month", "type": "fixed_count" }, { "count": 12, "interval": "month", "type": "fixed_count" }, { "count": 18, "interval": "month", "type": "fixed_count" } ] }, "request_three_d_secure": "automatic" } }, ... }

    Step 3: Select an Installment plan on the client

    Allow the customer to select the Installments plan they want to use.

    
    <div id="plans" hidden>
      <form id="installment-plan-form" >
        <label><input id="immediate-plan" type="radio" name="installment_plan" value="-1" />Immediate</label>
        <input id="payment-intent-id" type="hidden" />
      </form>
      <button id="confirm-button">Confirm Payment</button>
    </div>
    
    <div id="result" hidden>
      <p id="status-message"></p>
    </div>
    
    
    const selectPlanForm = document.getElementById('installment-plan-form');
    let availablePlans = [];
    
    const handleInstallmentPlans = async (response) => {
      if (response.error) {
        // Show error from server on payment form
      } else {
        // Store the payment intent ID.
        document.getElementById('payment-intent-id').value = response.intent_id;
        availablePlans = response.available_plans;
    
        // Show available installment options
        availablePlans.forEach((plan, idx) => {
          const newInput = document.getElementById('immediate-plan').cloneNode();
          newInput.setAttribute('value', idx);
          newInput.setAttribute('id', '');
          const label = document.createElement('label');
          label.appendChild(newInput);
          label.appendChild(
            document.createTextNode(`${plan.count} ${plan.interval}s`),
          );
    
          selectPlanForm.appendChild(label);
        });
    
        document.getElementById('details').hidden = true;
        document.getElementById('plans').hidden = false;
      }
    };
    

    Send the selected plan to your server.

    
    const confirmButton = document.getElementById('confirm-button');
    
    confirmButton.addEventListener('click', async (ev) => {
      const selectedPlanIdx = selectPlanForm.installment_plan.value;
      const selectedPlan = availablePlans[selectedPlanIdx];
      const intentId = document.getElementById('payment-intent-id').value;
      const response = await fetch('/confirm_payment', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
          payment_intent_id: intentId,
          selected_plan: selectedPlan,
        }),
      });
    
      const responseJson = await response.json();
    
      // Show success / error response.
      document.getElementById('plans').hidden = true;
      document.getElementById('result').hidden = false;
    
      if (responseJson.status === 'succeeded' && selectedPlan !== undefined) {
        document.getElementById(
          'status_message',
        ).innerText = `Success! You made a charge with this plan:${
          selectedPlan.count
        } ${selectedPlan.interval}`;
      } else if (responseJson.status === 'succeeded') {
        document.getElementById('status_message').innerText =
          'Success! You payed immediately!';
      } else {
        document.getElementById('status_message').innerText =
          'Uh oh! Something went wrong';
      }
    });
    
    

    Step 4: Confirm the PaymentIntent on the server

    Using another server endpoint, confirm the PaymentIntent to finalize the payment and fulfill the order.

    curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d "payment_method_options[card][installments][plan][type]"=fixed_count \
      -d "payment_method_options[card][installments][plan][interval]"=month \
      -d "payment_method_options[card][installments][plan][count]"=3
    
    post '/confirm_payment' do
      data = JSON.parse(request.body.read.to_s)
    
      confirm_data = {}
      if data.key?('selected_plan')
        confirm_data = {
          payment_method_options: {
            card: {
              installments: {
                plan: data['selected_plan'],
              },
            },
          },
        }
      end
    
      begin
        # Create the PaymentIntent
        intent = Stripe::PaymentIntent.confirm(
          data['payment_intent_id'],
          confirm_data
        )
      rescue Stripe::CardError => e
        # Display error on client
        return [500, {error: e.message}.to_json]
      end
    
      return [200, {
        success: true,
        status: intent.status,
      }.to_json]
    end
    
    @app.route('/confirm_payment', methods=['POST'])
    def confirm_payment():
        confirm_data = {}
    
        data = request.get_json()
        selected_plan = data.get('selected_plan')
        if selected_plan is not None:
            confirm_data = {
                'card': {
                    'installments': {
                        'plan': selected_plan
                    }
                }
            }
    
        try:
            intent = stripe.PaymentIntent.confirm(
                data['payment_intent_id'],
                payment_method_options=confirm_data
            )
        except stripe.error.CardError as e:
            return jsonify({'error': e.user_message})
    
        return jsonify({'success': True, 'status': intent['status']})
    
    <?php
    
    # vendor using composer
    require_once('vendor/autoload.php');
    
    \Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
    
    header('Content-Type: application/json');
    
    # retrieve json from POST body
    $json_str = file_get_contents('php://input');
    $json_obj = json_decode($json_str, true);
    
    if (isset($json_obj['selected_plan'])) {
        $confirm_data = ['payment_method_options' =>
            [
                'card' => [
                    'installments' => [
                        'plan' => $json_obj['selected_plan']
                    ]
                ]
            ]
        ];
    }
    
    $intent = \Stripe\PaymentIntent::retrieve(
        $json_obj['payment_intent_id']
    );
    
    $intent->confirm($params = $confirm_data);
    
    echo json_encode([
        'status' => $intent->status,
    ]);
    
    ?>
    
    package com.stripe.generator;
    
    import static spark.Spark.post;
    
    import com.google.gson.Gson;
    import com.google.gson.annotations.SerializedName;
    import com.stripe.Stripe;
    import com.stripe.model.PaymentIntent;
    import com.stripe.param.PaymentIntentCreateParams;
    import com.stripe.param.PaymentIntentConfirmParams;
    
    import java.util.HashMap;
    import java.util.Map;
    import spark.Request;
    import spark.Response;
    import spark.Route;
    
    public class MyApp {
    
      private static final Gson gson = new Gson();
    
      static class CollectDetailsRequest {
        @SerializedName("payment_method_id")
        private String paymentMethodId;
    
        public String getPaymentMethodId() {
          return paymentMethodId;
        }
      }
    
      static class ConfirmPaymentRequest {
        @SerializedName("payment_intent_id")
        private String paymentIntentId;
    
        @SerializedName("selected_plan")
        private PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments.Plan selectedPlan;
    
        public String getPaymentIntentId() {
          return paymentIntentId;
        }
    
        public PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments.Plan getSelectedPlan() {
          return selectedPlan;
        }
      }
    
      /**
       * Your application.
       */
      public static void main(String[] args) {
        Stripe.apiKey = System.getenv("STRIPE_SECRET_KEY");
    
        post("/collect_details", new Route() {
          @Override
          public Object handle(Request request, Response response) {
            CollectDetailsRequest collectDetailsRequest =
                gson.fromJson(request.body(), CollectDetailsRequest.class);
            try {
              PaymentIntentCreateParams.PaymentMethodOptions.Card.Installments i =
                  PaymentIntentCreateParams.PaymentMethodOptions.Card.Installments.builder()
                    .setEnabled(true)
                    .build();
    
              PaymentIntentCreateParams.PaymentMethodOptions.Card card =
                  PaymentIntentCreateParams.PaymentMethodOptions.Card.builder()
                    .setInstallments(i)
                    .build();
    
              PaymentIntentCreateParams.PaymentMethodOptions pmo =
                  PaymentIntentCreateParams.PaymentMethodOptions.builder()
                    .setCard(card)
                    .build();
    
              PaymentIntentCreateParams createParams =
                  PaymentIntentCreateParams.builder()
                    .setAmount(3099L)
                    .setCurrency("mxn")
                    .setPaymentMethod(collectDetailsRequest.paymentMethodId)
                    .setPaymentMethodOptions(pmo)
                    .build();
    
              PaymentIntent intent = PaymentIntent.create(createParams);
    
              Map<String, Object> responseData = new HashMap<>();
    
              responseData.put("intent_id", intent.getId());
              responseData.put("available_plans",
                  intent.getPaymentMethodOptions()
                        .getCard()
                        .getInstallments()
                        .getAvailablePlans());
    
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              return makeErrorResponse(e);
            }
          }
        });
    
        post("/confirm_payment", new Route() {
          @Override
          public Object handle(Request request, Response response) {
            ConfirmPaymentRequest confirmPaymentRequest =
                gson.fromJson(request.body(), ConfirmPaymentRequest.class);
    
            try {
              PaymentIntentConfirmParams confirmParams;
    
              if (confirmPaymentRequest.getSelectedPlan() != null) {
                PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments i =
                    PaymentIntentConfirmParams.PaymentMethodOptions.Card.Installments.builder()
                      .setEnabled(true)
                      .setPlan(confirmPaymentRequest.getSelectedPlan())
                      .build();
    
    
                PaymentIntentConfirmParams.PaymentMethodOptions.Card card =
                    PaymentIntentConfirmParams.PaymentMethodOptions.Card.builder()
                      .setInstallments(i)
                      .build();
    
                PaymentIntentConfirmParams.PaymentMethodOptions pmo =
                    PaymentIntentConfirmParams.PaymentMethodOptions.builder()
                      .setCard(card)
                      .build();
    
    
                confirmParams = PaymentIntentConfirmParams.builder()
                    .setPaymentMethodOptions(pmo)
                    .build();
              } else {
                confirmParams = PaymentIntentConfirmParams.builder().build();
              }
    
              PaymentIntent intent = PaymentIntent.retrieve(confirmPaymentRequest.getPaymentIntentId());
              intent = intent.confirm(confirmParams);
    
              Map<String, Object> responseData = new HashMap<>();
    
              responseData.put("status", intent.getStatus());
              return gson.toJson(responseData);
            } catch (Exception e) {
              response.status(500);
              return makeErrorResponse(e);
            }
          }
        });
      }
    
      private static Object makeErrorResponse(Exception e) {
        Map<String, Object> errorResponse = new HashMap<>();
        errorResponse.put("message", e.getMessage());
        return gson.toJson(errorResponse);
      }
    }
    
    app.post('/confirm_payment', async (request, response) => {
      try {
        let confirmData = {};
        if (request.body.selected_plan !== undefined) {
          confirmData = {
            payment_method_options: {
              card: {
                installments: {
                  plan: request.body.selected_plan,
                },
              },
            },
          };
        }
    
        const intent = await stripe.paymentIntents.confirm(
          request.body.payment_intent_id,
          confirmData,
        );
    
        return response.send({success: true, status: intent.status});
      } catch (err) {
        return response.status(500).send({error: err.message});
      }
    });
    
    package main
    
    import (
      "encoding/json"
      "fmt"
      "net/http"
      "os"
    
      stripe "github.com/stripe/stripe-go"
      paymentintent "github.com/stripe/stripe-go/paymentintent"
    )
    
    // Structs to handle request and response JSON serializations
    type CollectDetailsRequest struct {
      PaymentMethodId *string `json:"payment_method_id"`
    }
    
    type ConfirmPaymentRequest struct {
      PaymentIntentId *string                                                             `json:"payment_intent_id"`
      SelectedPlan    *stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsPlanParams `json:"selected_plan"`
    }
    
    type CollectDetailsResponse struct {
      IntentId       *string                                                          `json:"intent_id"`
      AvailablePlans *[]*stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsPlan `json:"available_plans"`
    }
    
    type ConfirmPaymentResponse struct {
      Status *stripe.PaymentIntentStatus `json:"status"`
    }
    
    type ErrorResponse struct {
      Message string `json:"error"`
    }
    
    func writeError(err error, w http.ResponseWriter) {
      if stripeErr, ok := err.(*stripe.Error); ok {
        // Display error on client
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(ErrorResponse{Message: stripeErr.Msg})
      } else {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, "Other error occurred, %v\n", err.Error())
      }
    }
    
    func main() {
      // Initialize stripe-go with Stripe secret key from environment
      stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
    
      fs := http.FileServer(http.Dir("public"))
      http.Handle("/", fs)
    
      http.HandleFunc("/collect_details",
        func(w http.ResponseWriter, req *http.Request) {
          var collectDetailsRequest CollectDetailsRequest
          json.NewDecoder(req.Body).Decode(&collectDetailsRequest)
    
          params := &stripe.PaymentIntentParams{
            PaymentMethod: stripe.String(*collectDetailsRequest.PaymentMethodId),
            Amount:        stripe.Int64(3099),
            Currency:      stripe.String(string(stripe.CurrencyMXN)),
            PaymentMethodOptions: &stripe.PaymentIntentPaymentMethodOptionsParams{
              Card: &stripe.PaymentIntentPaymentMethodOptionsCardParams{
                Installments: &stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsParams{
                  Enabled: stripe.Bool(true),
                },
              },
            },
          }
    
          intent, err := paymentintent.New(params)
    
          if err != nil {
            writeError(err, w)
            return
          }
    
          w.WriteHeader(http.StatusOK)
          json.NewEncoder(w).Encode(CollectDetailsResponse{
            IntentId:       &intent.ID,
            AvailablePlans: &intent.PaymentMethodOptions.Card.Installments.AvailablePlans,
          })
        })
    
      http.HandleFunc("/confirm_payment",
        func(w http.ResponseWriter, req *http.Request) {
          var confirmPaymentRequest ConfirmPaymentRequest
          json.NewDecoder(req.Body).Decode(&confirmPaymentRequest)
    
          var intent *stripe.PaymentIntent
          var err error
    
          confirmParams := &stripe.PaymentIntentConfirmParams{}
    
          if confirmPaymentRequest.SelectedPlan != nil {
            confirmParams = &stripe.PaymentIntentConfirmParams{
              PaymentMethodOptions: &stripe.PaymentIntentPaymentMethodOptionsParams{
                Card: &stripe.PaymentIntentPaymentMethodOptionsCardParams{
                  Installments: &stripe.PaymentIntentPaymentMethodOptionsCardInstallmentsParams{
                    Plan: confirmPaymentRequest.SelectedPlan,
                  },
                },
              },
            }
          }
    
          intent, err = paymentintent.Confirm(*confirmPaymentRequest.PaymentIntentId, confirmParams)
    
          if err != nil {
            writeError(err, w)
            return
          }
    
          w.WriteHeader(http.StatusOK)
          json.NewEncoder(w).Encode(ConfirmPaymentResponse{
            Status: &intent.Status,
          })
        })
    
      http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    }
    

    The response from the server will indicate that you selected the plan on the PaymentIntent and also on the resulting charge.

    {
      "id": "pi_1FKDPFJXdud1yP2PMSXLlPbg",
      "object": "payment_intent",
      "amount": 3099,
    ...
      "charges": {
        "data": [
          {
            "id": "ch_1FKDPHJXdud1yP2P2u79mcIX",
            "object": "charge",
            "amount": 3099,
            "payment_method_details": {
              "card": {
                "installments": {
                  "plan": {
                    "count": 3,
                    "interval": "month",
                    "type": "fixed_count"
                  }
                },
    See all 42 lines }, ... }, ... } ], }, ... "payment_method_options": { "card": { "installments": { "enabled": true, "plan": { "count": 3, "interval": "month", "type": "fixed_count" }, "available_plans": [ ... ] } } } }

    Test the integration

    You can use the following cards to test your integration:

    Number Description
    4000004840000008 3, 6, 9, 12, and 18 month installment plans available
    4242424242424242 No installment plans available.

    Requirements

    There are restrictions on which transactions and cards can use installments. You don’t need to implement these rules yourself. The Payment Intents API automatically determines Installments eligibility after a payment method has been attached.

    • Stripe only supports installments in Mexico.
    • The payment method must be a credit card issued in Mexico.
    • The card must be a consumer card (corporate cards are not supported).
    • The card must be issued by one of our supported issuers (listed on the right).
    • The currency value must be MXN.
    • The total payment amount must be above a minimium amount, based on the number of months in the plan selected:
    Installment Plan Minimum Amount
    3 months $300.00 MXN
    6 months $600.00 MXN
    9 months $900.00 MXN
    12 months $1200.00 MXN
    18 months $1800.00 MXN

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page