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 --> <form id="payment-form"> <div id="card-element"></div> <button id="card-button">Next</button> </form> </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 form = document.getElementById('payment-form'); form.addEventListener('submit', function(ev) { ev.preventDefault(); stripe.createPaymentMethod({ type: 'card', 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 form = document.getElementById('payment-form'); form.addEventListener('submit', async (ev) => { ev.preventDefault(); 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=pm_card_mx \ -d amount=3099 \ -d currency=usd \ -d "payment_method_options[card][installments][enabled]"=true
# Set your secret key. Remember to switch 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 # "e" contains a message explaining why the request failed 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 switch 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(): try: 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 }) except stripe.error.CardError as e: return 'Card Error: ' + str(e.error.message), 500 pass except stripe.error.InvalidRequestError as e: return 'Request Error: ' + str(e.error.message), 500 pass except stripe.error.StripeError as e: return 'Error: ' + str(e.error.message), 500 pass
<?php try { # vendor using composer require_once('vendor/autoload.php'); StripeStripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); 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 ]); } catch (\Stripe\Exception\CardException $e){ # "e" contains a message explaining why the request failed echo 'Card Error Message is:' . $e->getError()->message . ' '; echo json_encode([ 'error_message' => $e->getError()->message ]); } catch (\Stripe\Exception\InvalidRequestException $e) { // Invalid parameters were supplied to Stripe's API echo 'Invalid Parameters Message is:' . $e->getError()->message . ' '; echo json_encode([ 'error_message' => $e->getError()->message ]); } ?>
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 = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; 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) { // "e" contains a message explaining why the request failed 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) { // "e" contains a message explaining why the request failed 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 switch 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) { // "err" contains a message explaining why the request failed 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/v71" paymentintent "github.com/stripe/stripe-go/v71/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) // stripErr contains a message explaining why the request failed 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 stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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) }
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Stripe; using Newtonsoft.Json; namespace Controllers { public class CollectDetailsRequest { [JsonProperty("payment_method_id")] public string PaymentMethodId { get; set; } } public class ConfirmPaymentRequest { [JsonProperty("payment_intent_id")] public string PaymentIntentId { get; set; } [JsonProperty("selected_plan")] public PaymentIntentPaymentMethodOptionsCardInstallmentsPlan SelectedPlan { get; set; } } [Route("/")] public class PaymentsController : Controller { static PaymentsController() { StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; } [HttpPost("collect_details")] public async Task<IActionResult> CollectDetails([FromBody] CollectDetailsRequest request) { var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 30_99L, Currency = "mxn", PaymentMethodTypes = new List<string> { "card" }, PaymentMethod = request.PaymentMethodId, PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions { Card = new PaymentIntentPaymentMethodOptionsCardOptions { Installments = new PaymentIntentPaymentMethodOptionsCardInstallmentsOptions { Enabled = true, }, }, }, }; try { var intent = await service.CreateAsync(options); return Json(new { intent_id = intent.Id, available_plans = intent.PaymentMethodOptions.Card.Installments.AvailablePlans, }); } catch (StripeException e) { // "e" contains a message explaining why the request failed return StatusCode((int)e.HttpStatusCode, new { error = e.StripeError.Message }); } } [HttpPost("confirm_payment")] public async Task<IActionResult> ConfirmPayment([FromBody] ConfirmPaymentRequest request) { var service = new PaymentIntentService(); try { var options = new PaymentIntentConfirmOptions(); if (request.SelectedPlan != null) { options.PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions { Card = new PaymentIntentPaymentMethodOptionsCardOptions { Installments = new PaymentIntentPaymentMethodOptionsCardInstallmentsOptions { Enabled = true, Plan = new PaymentIntentPaymentMethodOptionsCardInstallmentsPlanOptions { Count = request.SelectedPlan.Count, Type = request.SelectedPlan.Type, Interval = request.SelectedPlan.Interval, }, }, }, }; } var intent = await service.ConfirmAsync(request.PaymentIntentId, options); return Json(new { status = intent.Status, }); } catch (StripeException e) { // "e" contains a message explaining why the request failed return StatusCode((int)e.HttpStatusCode, new { error = e.StripeError.Message }); } } } }

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 47 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" }, { "count": 24, "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; var message; if (responseJson.status === "succeeded" && selectedPlan !== undefined) { message = `Success! You made a charge with this plan:${ selectedPlan.count } ${selectedPlan.interval}`; } else if (responseJson.status === "succeeded") { message = "Success! You paid immediately!"; } else { message = "Uh oh! Something went wrong"; } document.getElementById("status-message").innerText = message; });

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('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); 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 = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; 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) { // "e" contains a message explaining why the request failed 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) { // "e" contains a message explaining why the request failed 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/v71" paymentintent "github.com/stripe/stripe-go/v71/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) // stripErr contains a message explaining why the request failed 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 stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" 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) }
using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Stripe; using Newtonsoft.Json; namespace Controllers { public class CollectDetailsRequest { [JsonProperty("payment_method_id")] public string PaymentMethodId { get; set; } } public class ConfirmPaymentRequest { [JsonProperty("payment_intent_id")] public string PaymentIntentId { get; set; } [JsonProperty("selected_plan")] public PaymentIntentPaymentMethodOptionsCardInstallmentsPlan SelectedPlan { get; set; } } [Route("/")] public class PaymentsController : Controller { static PaymentsController() { StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; } [HttpPost("collect_details")] public async Task<IActionResult> CollectDetails([FromBody] CollectDetailsRequest request) { var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 30_99L, Currency = "mxn", PaymentMethodTypes = new List<string> { "card" }, PaymentMethod = request.PaymentMethodId, PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions { Card = new PaymentIntentPaymentMethodOptionsCardOptions { Installments = new PaymentIntentPaymentMethodOptionsCardInstallmentsOptions { Enabled = true, }, }, }, }; try { var intent = await service.CreateAsync(options); return Json(new { intent_id = intent.Id, available_plans = intent.PaymentMethodOptions.Card.Installments.AvailablePlans, }); } catch (StripeException e) { // "e" contains a message explaining why the request failed return StatusCode((int)e.HttpStatusCode, new { error = e.StripeError.Message }); } } [HttpPost("confirm_payment")] public async Task<IActionResult> ConfirmPayment([FromBody] ConfirmPaymentRequest request) { var service = new PaymentIntentService(); try { var options = new PaymentIntentConfirmOptions(); if (request.SelectedPlan != null) { options.PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions { Card = new PaymentIntentPaymentMethodOptionsCardOptions { Installments = new PaymentIntentPaymentMethodOptionsCardInstallmentsOptions { Enabled = true, Plan = new PaymentIntentPaymentMethodOptionsCardInstallmentsPlanOptions { Count = request.SelectedPlan.Count, Type = request.SelectedPlan.Type, Interval = request.SelectedPlan.Interval, }, }, }, }; } var intent = await service.ConfirmAsync(request.PaymentIntentId, options); return Json(new { status = intent.Status, }); } catch (StripeException e) { // "e" contains a message explaining why the request failed return StatusCode((int)e.HttpStatusCode, new { error = e.StripeError.Message }); } } } }

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, 18, and 24 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 minimum 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 $1,200.00 MXN
18 months $1,800.00 MXN
24 months $2,400.00 MXN
Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.