Card payments without bank authentication

    Build a simpler integration, with regional limitations.

    Overview

    This integration supports businesses accepting only U.S. and Canadian cards. It’s simpler up front, but does not scale to support a global customer base.


    How does this integration work?

    Banks in regions like Europe and India often require two-factor authentication to confirm a purchase. If you primarily do business in the U.S. and Canada, ignoring card authentication can simplify your integration, as banks rarely request it in these regions.

    When a bank requires authentication, this basic integration immediately declines the payment (like a card decline), instead of handling authentication to complete the payment asynchronously. The benefit is that the payment succeeds or declines immediately and payment confirmation happens on the server, so you can handle immediate post-payment actions without a webhook.

    How does it compare to the global integration?

    Feature This Integration Global Integration
    Custom payment form
    Sensitive data never touches your server
    Works for your U.S. & Canada customers
    Declines payments with incorrect card details or no funds
    Declines payments with bank authentication requests
    Works for your global customers
    Automatically handles card payments that require bank authentication
    Webhooks recommended for post-payment tasks
    Easily scales to other payment methods (e.g., bank debits)

    Growing or global businesses should use Stripe’s global integration to support bank requests for two-factor authentication and allow customers to pay with more payment methods.

    1 Build a checkout form Client-side

    Elements, part of Stripe.js, provides drop-in UI components for collecting card information from customers. They are hosted by Stripe and placed into your payment form as an iframe so your customer’s card details never touch your code.

    First, include the Stripe.js script in the head of every page on your site.

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

    Including the script on every page of your site lets you take advantage of Stripe’s advanced fraud functionality and ability to detect anomalous browsing behavior.

    Security requirements

    This script must always load directly from js.stripe.com to remain PCI compliant. You can’t include the script in a bundle or host a copy of it yourself.

    When you use Elements, all payment information is submitted over a secure HTTPS connection.

    The address of the page that contains Elements must also start with https:// rather than http://. For more information about getting SSL certificates and integrating them with your server to enable a secure HTTPS connection, see our security documentation.

    Add Elements to your page

    Next, you need a Stripe account. Register now.

    Create empty DOM elements (containers) with unique IDs within your payment form.

    <form id="payment-form"> <div id="card-element"><!-- placeholder for Elements --></div> <button id="card-button">Submit Payment</button> <p id="payment-result"><!-- we'll pass the response from the server here --></p> </form>

    Create an instance of the Stripe object, providing your publishable API key as the first parameter. Afterwards, create an instance of the Elements object and use it to mount a Card element in the empty DOM element container on 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');

    Use stripe.createPaymentMethod on your client to collect the card details and create a PaymentMethod when the user submits the payment form. Send the ID of the PaymentMethod to your server.

    var form = document.getElementById('payment-form'); var resultContainer = document.getElementById('payment-result'); cardElement.addEventListener('change', function(event) { if (event.error) { resultContainer.textContent = event.error.message; } else { resultContainer.textContent = ''; } }); form.addEventListener('submit', function(event) { event.preventDefault(); resultContainer.textContent = ""; stripe.createPaymentMethod({ type: 'card', card: cardElement, }).then(handlePaymentMethodResult); }); function handlePaymentMethodResult(result) { if (result.error) { // An error happened when collecting card details, show it in the payment form resultContainer.textContent = result.error.message; } else { // Otherwise send paymentMethod.id to your server (see Step 3) fetch('/pay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_method_id: result.paymentMethod.id }) }).then(function(result) { return result.json(); }).then(handleServerResponse); } } function handleServerResponse(responseJson) { if (responseJson.error) { // An error happened when charging the card, show it in the payment form resultContainer.textContent = responseJson.error; } else { // Show a success message resultContainer.textContent = 'Success!'; } }
    const form = document.getElementById("payment-form"); var resultContainer = document.getElementById('payment-result'); cardElement.addEventListener('change', function(event) { if (event.error) { resultContainer.textContent = event.error.message; } else { resultContainer.textContent = ''; } }); form.addEventListener('submit', async event => { event.preventDefault(); resultContainer.textContent = ''; const result = await stripe.createPaymentMethod({ type: 'card', card: cardElement, }); handlePaymentMethodResult(result); }); const handlePaymentMethodResult = async ({ paymentMethod, error }) => { if (error) { // An error happened when collecting card details, show error in payment form resultContainer.textContent = result.error.message; } else { // Send paymentMethod.id to your server (see Step 3) const response = await fetch("/pay", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ payment_method_id: paymentMethod.id }) }); const responseJson = await response.json(); handleServerResponse(responseJson); } }; const handleServerResponse = async responseJson => { if (responseJson.error) { // An error happened when charging the card, show it in the payment form resultContainer.textContent = responseJson.error; } else { // Show a success message resultContainer.textContent = 'Success!'; } };

    First, install Stripe.js and React Stripe.js.

    npm install --save @stripe/stripe-js @stripe/react-stripe-js

    Security requirements

    When you use Elements, all payment information is submitted over a secure HTTPS connection.

    The address of the page that contains Elements must also start with https:// rather than http://. For more information about getting SSL certificates and integrating them with your server to enable a secure HTTPS connection, see our security documentation.

    Load Stripe.js and add Elements to your page

    To use Elements, wrap the root of your React app in an Elements provider. Call loadStripe with your publishable key and pass the returned Promise to the Elements provider.

    It is important to import and call loadStripe at the root of your React app to take advantage of Stripe’s advanced fraud functionality and ability to detect anomalous browsing behavior.

    import React from 'react'; import ReactDOM from 'react-dom'; import {Elements} from '@stripe/react-stripe-js'; import {loadStripe} from '@stripe/stripe-js'; import CheckoutForm from './CheckoutForm'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe("pk_test_TYooMQauvdEDq54NiTphI7jx"); function App() { return ( <Elements stripe={stripePromise}> <CheckoutForm /> </Elements> ); }; ReactDOM.render(<App />, document.getElementById('root'));

    Create a PaymentMethod

    Use the CardElement and stripe.createPaymentMethod on your client to collect the card details and create a PaymentMethod when the user submits the payment form. Send the ID of the PaymentMethod to your server.

    To call stripe.createPaymentMethod from your payment form component, use the useStripe and useElements hooks. If you prefer traditional class components over hooks, you can instead use an ElementsConsumer.

    import React from 'react'; import {useStripe, useElements, CardElement} from '@stripe/react-stripe-js'; export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); const result = await stripe.createPaymentMethod({ type: 'card', card: elements.getElement(CardElement), billing_details: { // Include any additional collected billing details. name: 'Jenny Rosen', }, }); handlePaymentMethodResult(result); }; const handlePaymentMethodResult = async (result) => { if (result.error) { // An error happened when collecting card details, // show `result.error.message` in the payment form. } else { // Otherwise send paymentMethod.id to your server (see Step 3) const response = await fetch('/pay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_method_id: result.paymentMethod.id, }), }); const serverResponse = await response.json(); handleServerResponse(serverResponse); } }; const handleServerResponse = (serverResponse) => { if (serverResponse.error) { // An error happened when charging the card, // show the error in the payment form. } else { // Show a success message } }; const handleCardChange = (event) => { if (event.error) { // Show `event.error.message` in the payment form. } }; return ( <form onSubmit={this.handleSubmit}> <CardElement onChange={handleCardChange} /> <button type="submit" disabled={!stripe}> Submit Payment </button> </form> ); }
    import React from 'react'; import {ElementsConsumer, CardElement} from '@stripe/react-stripe-js'; class CheckoutForm extends React.Component { handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); const {stripe, elements} = this.props; const result = await stripe.createPaymentMethod({ type: 'card', card: elements.getElement(CardElement), billing_details: { // Include any additional collected billing details. name: 'Jenny Rosen', }, }); this.handlePaymentMethodResult(result); }; handlePaymentMethodResult = async (result) => { if (result.error) { // An error happened when collecting card details, // show `result.error.message` in the payment form. } else { // Otherwise send paymentMethod.id to your server (see Step 3) const response = await fetch('/pay', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ payment_method_id: result.paymentMethod.id, }), }); const serverResponse = await response.json() this.handleServerResponse(serverResponse); } } handleServerResponse = (serverResponse) => { if (serverResponse.error) { // An error happened when charging the card, // show the error in the payment form. } else { // Show a success message } } handleCardChange = (event) => { if (event.error) { // Show `event.error.message` in the payment form. } } render() { const {stripe} = this.props; return ( <form onSubmit={this.handleSubmit}> <CardElement onChange={this.handleCardChange} /> <button type="submit" disabled={!stripe}> Submit Payment </button> </form> ); } } export default function InjectedCheckoutForm() { return ( <ElementsConsumer> {({stripe, elements}) => ( <CheckoutForm stripe={stripe} elements={elements} /> )} </ElementsConsumer> ); }

    2 Set up Stripe Server-side

    Use an official library to make requests to the Stripe API from your application:

    # Available as a gem sudo gem install stripe
    # If you use bundler, you can add this line to your Gemfile gem 'stripe'
    # Install through pip pip install --upgrade stripe
    # Or find the Stripe package on http://pypi.python.org/pypi/stripe/
    # Install the PHP library via Composer composer require stripe/stripe-php
    # Or download the source directly: https://github.com/stripe/stripe-php/releases
    /* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
    <!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
    # For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
    # Install via npm npm install --save stripe
    # Install via go go get github.com/stripe/stripe-go
    // Then import the package import ( "github.com/stripe/stripe-go" )
    # Install via dotnet dotnet add package Stripe.net dotnet restore
    # Or install via NuGet PM> Install-Package Stripe.net

    3 Make a payment Server-side

    Set up an endpoint on your server to receive the request from the client.

    Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

    Always decide how much to charge on the server, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    Create an HTTP endpoint to respond to the AJAX request from step 1. In that endpoint, you should decide how much to charge the customer. To create a payment, create a PaymentIntent using the PaymentMethod ID from step 1 with the following code:

    # Check the status of the PaymentIntent to make sure it succeeded curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d currency=usd \ -d confirm=true \ -d payment_method="{{PAYMENT_METHOD_ID}}" \ -d error_on_requires_action=true
    require 'sinatra' require 'json' require 'stripe' # 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' get '/' do # Display checkout page content_type 'text/html' send_file File.join('./index.html') end # Endpoint for when `/pay` is called from client post '/pay' do data = JSON.parse(request.body.read.to_s) begin # Create the PaymentIntent intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'usd', payment_method: data['payment_method_id'], # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. confirm: true, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. error_on_requires_action: true, }) generate_response(intent) rescue Stripe::CardError => e # Display error on client return [200, { error: e.message }.to_json] end end def generate_response(intent) if intent.status == 'succeeded' [200, { success: true }.to_json] else # Any other status would be unexpected, so error [500, { error: 'Invalid PaymentIntent status' }.to_json] end end
    import stripe import json from flask import request, render_template app = Flask(__name__, static_folder=".", static_url_path="", template_folder=".") @app.route('/', methods=['GET']) def get_example(): # Display checkout page return render_template('index.html') # 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' # Endpoint for when `/pay` is called from client @app.route('/pay', methods=['POST']) def confirm_payment(): data = request.get_json() try: # Create the PaymentIntent intent = stripe.PaymentIntent.create( amount=1099, currency='usd', payment_method=data['payment_method_id'], # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. confirm=True, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. error_on_requires_action=True, ) return generate_response(intent) except stripe.error.CardError as e: # Display error on client return json.dumps({'error': e.user_message}), 200 def generate_response(intent): if intent.status == 'succeeded': # Handle post-payment fulfillment return json.dumps({'success': True}), 200 else: # Any other status would be unexpected, so error return json.dumps({'error': 'Invalid PaymentIntent status'}), 500 if __name__ == '__main__': app.run()
    <?php # vendor using composer require_once('vendor/autoload.php'); // 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\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); try { // Create the PaymentIntent $intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'usd', 'payment_method' => $json_obj->payment_method_id, # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. 'confirm' => true, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. 'error_on_requires_action' => true, ]); generateResponse($intent); } catch (\Stripe\Exception\ApiErrorException $e) { // Display error on client echo json_encode(['error' => $e->getMessage()]); } function generateResponse($intent) { if ($intent->status == 'succeeded') { // Handle post-payment fulfillment echo json_encode(['success' => true]); } else { // Any other status would be unexpected, so error echo json_encode(['error' => 'Invalid PaymentIntent status']); } }
    // Using Express const express = require('express'); const app = express(); app.use(express.json()); const { resolve } = require("path"); app.get("/", (req, res) => { // Display checkout page const path = resolve("./index.html"); res.sendFile(path); }); // 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'); // Endpoint for when `/pay` is called from client app.post('/pay', async (request, response) => { try { // Create the PaymentIntent let intent = await stripe.paymentIntents.create({ amount: 1099, currency: 'usd', payment_method: request.body.payment_method_id, // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. confirm: true, // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. error_on_requires_action: true }); return generateResponse(response, intent); } catch (e) { if (e.type === 'StripeCardError') { // Display error on client return response.send({ error: e.message }); } else { // Something else happened return response.status(500).send({ error: e.type }); } } }); function generateResponse(response, intent) { if (intent.status === 'succeeded') { // Handle post-payment fulfillment return response.send({ success: true }); } else { // Any other status would be unexpected, so error return response.status(500).send({error: 'Unexpected status ' + intent.status}); } } app.listen(4242, () => console.log(`Node server listening on port ${4242}!`));
    package com.stripe.sample; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.stripe.Stripe; import com.stripe.exception.CardException; import com.stripe.model.PaymentIntent; import com.stripe.param.PaymentIntentCreateParams; import spark.Response; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import static spark.Spark.*; public class Server { public static void main(String[] args) { port(4242); // 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.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/pay", (request, response) -> { Gson gson = new Gson(); String paymentMethod = gson.fromJson(request.body(), JsonObject.class) .get("payment_method_id").getAsString(); try { PaymentIntent intent = PaymentIntent.create(PaymentIntentCreateParams.builder() .setAmount(1099L) .setCurrency("usd") .setPaymentMethod(paymentMethod) // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. .setConfirm(true) // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. .setErrorOnRequiresAction(true) .build()); return gson.toJson(buildResponse(response, intent)); } catch (CardException e) { response.status(200); Map<String, String> errorResponse = new HashMap<>(); errorResponse.put("error", e.getStripeError().getMessage()); return gson.toJson(errorResponse); } }); } private static Map<String, Object> buildResponse(Response response, PaymentIntent intent) { Map<String, Object> responseData = new HashMap<>(); if (intent.getStatus().equals("succeeded")) { // Handle post-payment fulfillment response.status(200); responseData.put("success", true); } else { // Any other status would be unexpected, so error response.status(500); responseData.put("error", "Invalid PaymentIntent status"); } return responseData; } }
    package main import ( "encoding/json" "log" "net/http" "github.com/stripe/stripe-go" "github.com/stripe/stripe-go/paymentintent" ) func main() { // 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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" fs := http.FileServer(http.Dir("static")) http.Handle("/", fs) http.HandleFunc("/pay", handlePay) addr := "localhost:4242" log.Printf("Listening on %s ...", addr) log.Fatal(http.ListenAndServe(addr, nil)) } type CreatePaymentIntentResponse struct { Success bool `json:"success"` ErrorMessage string `json:"error"` } func handlePay(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentMethodID string `json:"payment_method_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(2000), Currency: stripe.String(string(stripe.CurrencyUSD)), PaymentMethod: stripe.String(req.PaymentMethodID), // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. Confirm: stripe.Bool(true), // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. ErrorOnRequiresAction: stripe.Bool(true), } pi, err := paymentintent.New(params) if err != nil { if stripeErr, ok := err.(*stripe.Error); ok { // return error message to the client w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ ErrorMessage: stripeErr.Msg, }) } else { w.WriteHeader(http.StatusInternalServerError) } } else { generateResponse(pi, w) } } func generateResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) { if intent.Status == stripe.PaymentIntentStatusSucceeded { // Handle post-payment fulfillment w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ Success: true, }) } else { // Any other status would be unexpected, so error w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ Success: false, ErrorMessage: "Invalid Payment Intent status", }) } }
    using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Stripe; using Newtonsoft.Json; namespace Controllers { public class CreatePaymentIntentRequest { [JsonProperty("payment_method_id")] public string PaymentMethodId { get; set; } } // Endpoint for when `/pay` is called from client [Route("/pay")] public class CreatePaymentIntentController : Controller { public IActionResult Index([FromBody] CreatePaymentIntentRequest request) { var service = new PaymentIntentService(); PaymentIntent paymentIntent = null; try { // Create the PaymentIntent var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "usd", PaymentMethodId = request.PaymentMethodId, // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. Confirm = true, // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. ErrorOnRequiresAction = true, }; paymentIntent = service.Create(options); } catch (StripeException e) { return Json(new { error = e.StripeError.Message }); } return generatePaymentResponse(paymentIntent); } private IActionResult generatePaymentResponse(PaymentIntent intent) { if (intent.Status == "succeeded") { // Handle post-payment fulfillment return Json(new { success = true }); } else { // Any other status would be unexpected, so error return StatusCode(500, new { error = "Invalid PaymentIntent status" }); } } } }

    Payment Intents API response

    When you make a payment with the API, the response includes a status of the PaymentIntent. If the payment was successful, it will have a status of succeeded.

    { "id": "pi_0FdpcX589O8KAxCGR6tGNyWj", "object": "payment_intent", "amount": 1099, "charges": { "object": "list", "data": [ { "id": "ch_GA9w4aF29fYajT", "object": "charge", "amount": 1099, "refunded": false, "status": "succeeded", } ] }, "client_secret": "pi_0FdpcX589O8KAxCGR6tGNyWj_secret_e00tjcVrSv2tjjufYqPNZBKZc", "currency": "usd", "last_payment_error": null, "status": "succeeded", }

    If the payment is declined, the response includes the error with the code and message included. Below is an example of a failed payment due to two-factor authentication being required for the credit card.

    { "error": { "code": "authentication_required", "decline_code": "authentication_not_handled", "doc_url": "https://stripe.com/docs/error-codes/authentication-required", "message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "payment_intent": { "id": "pi_1G8JtxDpqHItWkFAnB32FhtI", "object": "payment_intent", "amount": 1099, "status": "requires_payment_method", "last_payment_error": { "code": "authentication_required", "decline_code": "authentication_not_handled", "doc_url": "https://stripe.com/docs/error-codes/authentication-required", "message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "type": "card_error" }, }, "type": "card_error" } }

    4 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000000000009995 Always fails with a decline code of insufficient_funds.
    4000002500003155 Requires authentication, which in this integration will fail with a decline code of authentication_not_handled.

    For the full list of test cards see our guide on testing.

    Upgrading your integration to handle card authentication

    Congratulations! You completed a payments integration for basic card payments. Note that this integration declines cards that require authentication during payment.

    If you start seeing many payments in the Dashboard listed as Failed, then it’s time to upgrade your integration. Stripe’s global integration handles these payments instead of automatically declining.

    Overview

    This integration supports businesses accepting only U.S. and Canadian cards. It’s simpler up front, but does not scale to support a global customer base.


    How does this integration work?

    Banks in regions like Europe and India often require two-factor authentication to confirm a purchase. If you primarily do business in the U.S. and Canada, ignoring card authentication can simplify your integration, as banks rarely request it in these regions.

    When a bank requires authentication, this basic integration immediately declines the payment (like a card decline), instead of handling authentication to complete the payment asynchronously. The benefit is that the payment succeeds or declines immediately and payment confirmation happens on the server, so you can handle immediate post-payment actions without a webhook.

    How does it compare to the global integration?

    Feature This Integration Global Integration
    Custom payment form
    Sensitive data never touches your server
    Works for your U.S. & Canada customers
    Declines payments with incorrect card details or no funds
    Declines payments with bank authentication requests
    Works for your global customers
    Automatically handles card payments that require bank authentication
    Webhooks recommended for post-payment tasks
    Easily scales to other payment methods (e.g., bank debits)

    Growing or global businesses should use Stripe’s global integration to support bank requests for two-factor authentication and allow customers to pay with more payment methods.

    1 Install the Stripe iOS SDK Client-side

    First, you need a Stripe account. Register now.

    The iOS SDK is open source, fully documented, and compatible with apps supporting iOS 10 or above.

    1. If you haven't already, install the latest version of CocoaPods.
    2. If you don't have an existing Podfile, run the following command to create one:
      pod init
    3. Add this line to your Podfile:
      pod 'Stripe'
    4. Run the following command:
      pod install
    5. Don't forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.
    6. In the future, to update to the latest version of the SDK, just run:
      pod update Stripe
    1. If you haven't already, install the latest version of Carthage.
    2. Add this line to your Cartfile:
      github "stripe/stripe-ios"
    3. Follow the Carthage installation instructions.
    4. In the future, to update to the latest version of the SDK, run the following command:
      carthage update stripe-ios --platform ios
    1. Head to our GitHub releases page and download and unzip Stripe.framework.zip.
    2. Drag Stripe.framework to the "Embedded Binaries" section of your Xcode project's "General" settings. Make sure to select "Copy items if needed".
    3. Head to the "Build Phases" section of your Xcode project settings, and create a new "Run Script Build Phase". Paste the following snippet into the text field:
      bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Stripe.framework/integrate-dynamic-framework.sh"
    4. In the future, to update to the latest version of our SDK, just repeat steps 1 and 2.

    When your app starts, configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API.

    import UIKit import Stripe @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { Stripe.setDefaultPublishableKey("pk_test_TYooMQauvdEDq54NiTphI7jx") // do any other necessary launch configuration return true } }
    #import "AppDelegate.h" #import <Stripe/Stripe.h> @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Stripe setDefaultPublishableKey:@"pk_test_TYooMQauvdEDq54NiTphI7jx"]; // do any other necessary launch configuration return YES; } @end

    2 Collect card details Client-side

    Securely collect card information on the client with STPPaymentCardTextField, a drop-in UI component provided by the SDK.

    STPPaymentCardTextField performs on-the-fly validation and formatting.

    Create an instance of the card component and a Pay button with the following code:

    import UIKit import Stripe class CheckoutViewController: UIViewController { lazy var cardTextField: STPPaymentCardTextField = { let cardTextField = STPPaymentCardTextField() return cardTextField }() lazy var payButton: UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 5 button.backgroundColor = .systemBlue button.titleLabel?.font = UIFont.systemFont(ofSize: 22) button.setTitle("Pay", for: .normal) button.addTarget(self, action: #selector(pay), for: .touchUpInside) return button }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white let stackView = UIStackView(arrangedSubviews: [cardTextField, payButton]) stackView.axis = .vertical stackView.spacing = 20 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView) NSLayoutConstraint.activate([ stackView.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: 2), view.rightAnchor.constraint(equalToSystemSpacingAfter: stackView.rightAnchor, multiplier: 2), stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.safeAreaLayoutGuide.topAnchor, multiplier: 2), ]) } @objc func pay() { // ... } }
    #import "CheckoutViewController.h" #import <Stripe/Stripe.h> @interface CheckoutViewController () @property (weak) STPPaymentCardTextField *cardTextField; @property (weak) UIButton *payButton; @end @implementation CheckoutViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; STPPaymentCardTextField *cardTextField = [[STPPaymentCardTextField alloc] init]; self.cardTextField = cardTextField; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.layer.cornerRadius = 5; button.backgroundColor = [UIColor systemBlueColor]; button.titleLabel.font = [UIFont systemFontOfSize:22]; [button setTitle:@"Pay" forState:UIControlStateNormal]; [button addTarget:self action:@selector(pay) forControlEvents:UIControlEventTouchUpInside]; self.payButton = button; UIStackView *stackView = [[UIStackView alloc] initWithArrangedSubviews:@[cardTextField, button]]; stackView.axis = UILayoutConstraintAxisVertical; stackView.translatesAutoresizingMaskIntoConstraints = NO; stackView.spacing = 20; [self.view addSubview:stackView]; [NSLayoutConstraint activateConstraints:@[ [stackView.leftAnchor constraintEqualToSystemSpacingAfterAnchor:self.view.leftAnchor multiplier:2], [self.view.rightAnchor constraintEqualToSystemSpacingAfterAnchor:stackView.rightAnchor multiplier:2], [stackView.topAnchor constraintEqualToSystemSpacingBelowAnchor:self.view.safeAreaLayoutGuide.topAnchor multiplier:2], ]]; } - (void)pay { // ... } @end

    Run your app, and make sure your checkout page shows the card component and pay button.

    Next, use createPaymentMethod to create a PaymentMethod when the user taps the pay button. Send the ID of the PaymentMethod to your server.

    func pay() { // Create a PaymentMethod with the card text field's card details let cardParams = cardTextField.cardParams let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil) STPAPIClient.shared().createPaymentMethod(with: paymentMethodParams) { (paymentMethod, error) in guard let paymentMethod = paymentMethod else { // Display the error to the customer return } let paymentMethodID = paymentMethod.stripeId // Send paymentMethodID to your server for the next step } }
    - (void)pay { // Create a PaymentMethod with the card text field's card details STPPaymentMethodCardParams *cardParams = self.cardTextField.cardParams; STPPaymentMethodParams *params = [STPPaymentMethodParams paramsWithCard:cardParams billingDetails:nil metadata:nil]; [[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams completion:^(STPPaymentMethod * _Nullable paymentMethod, NSError * _Nullable error) { if (error) { // Display the error to the customer return; } NSString *paymentMethodID = paymentMethod.stripeId; // Send paymentMethodID to your server for the next step }]; }

    3 Make a payment Server-side

    Set up an endpoint on your server to receive the request from the client. Use our official libraries for access to the Stripe API from your server:

    # Available as a gem sudo gem install stripe
    # If you use bundler, you can add this line to your Gemfile gem 'stripe'
    # Install through pip pip install --upgrade stripe
    # Or find the Stripe package on http://pypi.python.org/pypi/stripe/
    # Install the PHP library via Composer composer require stripe/stripe-php
    # Or download the source directly: https://github.com/stripe/stripe-php/releases
    /* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
    <!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
    # For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
    # Install via npm npm install --save stripe
    # Install via go go get github.com/stripe/stripe-go
    // Then import the package import ( "github.com/stripe/stripe-go" )
    # Install via dotnet dotnet add package Stripe.net dotnet restore
    # Or install via NuGet PM> Install-Package Stripe.net

    Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

    Create an HTTP endpoint to respond to the request from step 2. In that endpoint, you should decide how much to charge the customer. To create a payment, create a PaymentIntent using the PaymentMethod ID from step 2 with the following parameters:

    Always decide how much to charge on the server, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    # Check the status of the PaymentIntent to make sure it succeeded curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d currency=usd \ -d confirm=true \ -d payment_method="{{PAYMENT_METHOD_ID}}" \ -d error_on_requires_action=true
    require 'sinatra' require 'json' require 'stripe' # 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' get '/' do # Display checkout page content_type 'text/html' send_file File.join('./index.html') end # Endpoint for when `/pay` is called from client post '/pay' do data = JSON.parse(request.body.read.to_s) begin # Create the PaymentIntent intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'usd', payment_method: data['payment_method_id'], # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. confirm: true, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. error_on_requires_action: true, }) generate_response(intent) rescue Stripe::CardError => e # Display error on client return [200, { error: e.message }.to_json] end end def generate_response(intent) if intent.status == 'succeeded' [200, { success: true }.to_json] else # Any other status would be unexpected, so error [500, { error: 'Invalid PaymentIntent status' }.to_json] end end
    import stripe import json from flask import request, render_template app = Flask(__name__, static_folder=".", static_url_path="", template_folder=".") @app.route('/', methods=['GET']) def get_example(): # Display checkout page return render_template('index.html') # 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' # Endpoint for when `/pay` is called from client @app.route('/pay', methods=['POST']) def confirm_payment(): data = request.get_json() try: # Create the PaymentIntent intent = stripe.PaymentIntent.create( amount=1099, currency='usd', payment_method=data['payment_method_id'], # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. confirm=True, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. error_on_requires_action=True, ) return generate_response(intent) except stripe.error.CardError as e: # Display error on client return json.dumps({'error': e.user_message}), 200 def generate_response(intent): if intent.status == 'succeeded': # Handle post-payment fulfillment return json.dumps({'success': True}), 200 else: # Any other status would be unexpected, so error return json.dumps({'error': 'Invalid PaymentIntent status'}), 500 if __name__ == '__main__': app.run()
    <?php # vendor using composer require_once('vendor/autoload.php'); // 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\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); try { // Create the PaymentIntent $intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'usd', 'payment_method' => $json_obj->payment_method_id, # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. 'confirm' => true, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. 'error_on_requires_action' => true, ]); generateResponse($intent); } catch (\Stripe\Exception\ApiErrorException $e) { // Display error on client echo json_encode(['error' => $e->getMessage()]); } function generateResponse($intent) { if ($intent->status == 'succeeded') { // Handle post-payment fulfillment echo json_encode(['success' => true]); } else { // Any other status would be unexpected, so error echo json_encode(['error' => 'Invalid PaymentIntent status']); } }
    // Using Express const express = require('express'); const app = express(); app.use(express.json()); const { resolve } = require("path"); app.get("/", (req, res) => { // Display checkout page const path = resolve("./index.html"); res.sendFile(path); }); // 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'); // Endpoint for when `/pay` is called from client app.post('/pay', async (request, response) => { try { // Create the PaymentIntent let intent = await stripe.paymentIntents.create({ amount: 1099, currency: 'usd', payment_method: request.body.payment_method_id, // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. confirm: true, // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. error_on_requires_action: true }); return generateResponse(response, intent); } catch (e) { if (e.type === 'StripeCardError') { // Display error on client return response.send({ error: e.message }); } else { // Something else happened return response.status(500).send({ error: e.type }); } } }); function generateResponse(response, intent) { if (intent.status === 'succeeded') { // Handle post-payment fulfillment return response.send({ success: true }); } else { // Any other status would be unexpected, so error return response.status(500).send({error: 'Unexpected status ' + intent.status}); } } app.listen(4242, () => console.log(`Node server listening on port ${4242}!`));
    package com.stripe.sample; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.stripe.Stripe; import com.stripe.exception.CardException; import com.stripe.model.PaymentIntent; import com.stripe.param.PaymentIntentCreateParams; import spark.Response; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import static spark.Spark.*; public class Server { public static void main(String[] args) { port(4242); // 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.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/pay", (request, response) -> { Gson gson = new Gson(); String paymentMethod = gson.fromJson(request.body(), JsonObject.class) .get("payment_method_id").getAsString(); try { PaymentIntent intent = PaymentIntent.create(PaymentIntentCreateParams.builder() .setAmount(1099L) .setCurrency("usd") .setPaymentMethod(paymentMethod) // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. .setConfirm(true) // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. .setErrorOnRequiresAction(true) .build()); return gson.toJson(buildResponse(response, intent)); } catch (CardException e) { response.status(200); Map<String, String> errorResponse = new HashMap<>(); errorResponse.put("error", e.getStripeError().getMessage()); return gson.toJson(errorResponse); } }); } private static Map<String, Object> buildResponse(Response response, PaymentIntent intent) { Map<String, Object> responseData = new HashMap<>(); if (intent.getStatus().equals("succeeded")) { // Handle post-payment fulfillment response.status(200); responseData.put("success", true); } else { // Any other status would be unexpected, so error response.status(500); responseData.put("error", "Invalid PaymentIntent status"); } return responseData; } }
    package main import ( "encoding/json" "log" "net/http" "github.com/stripe/stripe-go" "github.com/stripe/stripe-go/paymentintent" ) func main() { // 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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" fs := http.FileServer(http.Dir("static")) http.Handle("/", fs) http.HandleFunc("/pay", handlePay) addr := "localhost:4242" log.Printf("Listening on %s ...", addr) log.Fatal(http.ListenAndServe(addr, nil)) } type CreatePaymentIntentResponse struct { Success bool `json:"success"` ErrorMessage string `json:"error"` } func handlePay(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentMethodID string `json:"payment_method_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(2000), Currency: stripe.String(string(stripe.CurrencyUSD)), PaymentMethod: stripe.String(req.PaymentMethodID), // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. Confirm: stripe.Bool(true), // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. ErrorOnRequiresAction: stripe.Bool(true), } pi, err := paymentintent.New(params) if err != nil { if stripeErr, ok := err.(*stripe.Error); ok { // return error message to the client w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ ErrorMessage: stripeErr.Msg, }) } else { w.WriteHeader(http.StatusInternalServerError) } } else { generateResponse(pi, w) } } func generateResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) { if intent.Status == stripe.PaymentIntentStatusSucceeded { // Handle post-payment fulfillment w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ Success: true, }) } else { // Any other status would be unexpected, so error w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ Success: false, ErrorMessage: "Invalid Payment Intent status", }) } }
    using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Stripe; using Newtonsoft.Json; namespace Controllers { public class CreatePaymentIntentRequest { [JsonProperty("payment_method_id")] public string PaymentMethodId { get; set; } } // Endpoint for when `/pay` is called from client [Route("/pay")] public class CreatePaymentIntentController : Controller { public IActionResult Index([FromBody] CreatePaymentIntentRequest request) { var service = new PaymentIntentService(); PaymentIntent paymentIntent = null; try { // Create the PaymentIntent var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "usd", PaymentMethodId = request.PaymentMethodId, // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. Confirm = true, // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. ErrorOnRequiresAction = true, }; paymentIntent = service.Create(options); } catch (StripeException e) { return Json(new { error = e.StripeError.Message }); } return generatePaymentResponse(paymentIntent); } private IActionResult generatePaymentResponse(PaymentIntent intent) { if (intent.Status == "succeeded") { // Handle post-payment fulfillment return Json(new { success = true }); } else { // Any other status would be unexpected, so error return StatusCode(500, new { error = "Invalid PaymentIntent status" }); } } } }

    Payment Intents API response

    When you make a payment with the API, the response includes a status of the PaymentIntent. If the payment was successful, it will have a status of succeeded.

    { "id": "pi_0FdpcX589O8KAxCGR6tGNyWj", "object": "payment_intent", "amount": 1099, "charges": { "object": "list", "data": [ { "id": "ch_GA9w4aF29fYajT", "object": "charge", "amount": 1099, "refunded": false, "status": "succeeded", } ] }, "client_secret": "pi_0FdpcX589O8KAxCGR6tGNyWj_secret_e00tjcVrSv2tjjufYqPNZBKZc", "currency": "usd", "last_payment_error": null, "status": "succeeded", }

    If the payment is declined, the response includes the error with the code and message included. Below is an example of a failed payment due to two-factor authentication being required for the credit card.

    { "error": { "code": "authentication_required", "decline_code": "authentication_not_handled", "doc_url": "https://stripe.com/docs/error-codes/authentication-required", "message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "payment_intent": { "id": "pi_1G8JtxDpqHItWkFAnB32FhtI", "object": "payment_intent", "amount": 1099, "status": "requires_payment_method", "last_payment_error": { "code": "authentication_required", "decline_code": "authentication_not_handled", "doc_url": "https://stripe.com/docs/error-codes/authentication-required", "message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "type": "card_error" }, }, "type": "card_error" } }

    4 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000000000009995 Always fails with a decline code of insufficient_funds.
    4000002500003155 Requires authentication, which in this integration will automatically error.

    For the full list of test cards see our guide on testing.

    Upgrading your integration to handle card authentication

    Congratulations! You completed a payments integration for basic card payments. Note that this integration declines cards that require authentication during payment.

    If you start seeing failed payments in the Dashboard listed as Failed, then it’s time to upgrade your integration. Stripe’s global integration handles these payments instead of automatically declining.

    Overview

    This integration supports businesses accepting only U.S. and Canadian cards. It’s simpler up front, but does not scale to support a global customer base.


    How does this integration work?

    Banks in regions like Europe and India often require two-factor authentication to confirm a purchase. If you primarily do business in the U.S. and Canada, ignoring card authentication can simplify your integration, as banks rarely request it in these regions.

    When a bank requires authentication, this basic integration immediately declines the payment (like a card decline), instead of handling authentication to complete the payment asynchronously. The benefit is that the payment succeeds or declines immediately and payment confirmation happens on the server, so you can handle immediate post-payment actions without a webhook.

    How does it compare to the global integration?

    Feature This Integration Global Integration
    Custom payment form
    Sensitive data never touches your server
    Works for your U.S. & Canada customers
    Declines payments with incorrect card details or no funds
    Declines payments with bank authentication requests
    Works for your global customers
    Automatically handles card payments that require bank authentication
    Webhooks recommended for post-payment tasks
    Easily scales to other payment methods (e.g., bank debits)

    Growing or global businesses should use Stripe’s global integration to support bank requests for two-factor authentication and allow customers to pay with more payment methods.

    1 Install the Stripe Android SDK Client-side

    First, you need a Stripe account. Register now.

    The Android SDK is open source and fully documented.

    To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

    apply plugin: 'com.android.application' android { ... } dependencies { // ... // Stripe Android SDK implementation 'com.stripe:stripe-android:14.0.0' }

    Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

    import com.stripe.android.PaymentConfiguration class MyApp: Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext, "pk_test_TYooMQauvdEDq54NiTphI7jx" ) } }
    import com.stripe.android.PaymentConfiguration; public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); PaymentConfiguration.init( getApplicationContext(), "pk_test_TYooMQauvdEDq54NiTphI7jx" ); } }

    2 Collect card details Client-side

    Securely collect card information on the client with CardInputWidget, a drop-in UI component provided by the SDK.

    CardInputWidget performs on-the-fly validation and formatting.

    Create an instance of the card component and a “Pay” button by adding the following to your checkout page’s layout:

    <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:showIn="@layout/activity_checkout" tools:context=".CheckoutActivity"> <!-- ... --> <com.stripe.android.view.CardInputWidget android:id="@+id/cardInputWidget" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="20dp"/> <Button android:text="Pay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/payButton" android:layout_marginTop="20dp" app:layout_constraintTop_toBottomOf="@+id/cardInputWidget" app:layout_constraintStart_toStartOf="@+id/cardInputWidget" app:layout_constraintEnd_toEndOf="@+id/cardInputWidget"/> <!-- ... --> </androidx.constraintlayout.widget.ConstraintLayout>

    Use Stripe#createPaymentMethod() in your app to collect the card details and create a PaymentMethod when the user taps the pay button. Send the ID of the PaymentMethod to your server.

    PaymentMethodCreateParams paymentMethodParams = cardInputWidget.getPaymentMethodCreateParams(); if (paymentMethodParams != null) { stripe.createPaymentMethod( paymentMethodParams, new ApiResultCallback<PaymentMethod>() { @Override public void onSuccess(@NonNull PaymentMethod paymentMethod) { // Send the ID of the PaymentMethod to your server. final String paymentMethodId = paymentMethod.getId(); } @Override public void onError(@NonNull Exception e) { // Display the error to the customer. } }); }
    cardInputWidget.paymentMethodCreateParams?.let { paymentMethodParams -> stripe.createPaymentMethod( paymentMethodParams, callback = object: ApiResultCallback<PaymentMethod> { override fun onSuccess(paymentMethod: PaymentMethod) { // Send the ID of the PaymentMethod to your server. val paymentMethodId = paymentMethod.id } override fun onError(e: Exception) { // Display the error to the customer. } } ) }

    3 Make a payment Server-side

    Set up an endpoint on your server to receive the request from the client. Use our official libraries for access to the Stripe API from your server:

    # Available as a gem sudo gem install stripe
    # If you use bundler, you can add this line to your Gemfile gem 'stripe'
    # Install through pip pip install --upgrade stripe
    # Or find the Stripe package on http://pypi.python.org/pypi/stripe/
    # Install the PHP library via Composer composer require stripe/stripe-php
    # Or download the source directly: https://github.com/stripe/stripe-php/releases
    /* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
    <!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
    # For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
    # Install via npm npm install --save stripe
    # Install via go go get github.com/stripe/stripe-go
    // Then import the package import ( "github.com/stripe/stripe-go" )
    # Install via dotnet dotnet add package Stripe.net dotnet restore
    # Or install via NuGet PM> Install-Package Stripe.net

    Stripe uses a PaymentIntent object to represent your intent to collect payment from a customer, tracking charge attempts and payment state changes throughout the process.

    Create an HTTP endpoint to respond to the request from step 2. In that endpoint, you should decide how much to charge the customer. To create a payment, create a PaymentIntent using the PaymentMethod ID from step 2 with the following parameters:

    Always decide how much to charge on the server, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

    # Check the status of the PaymentIntent to make sure it succeeded curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d currency=usd \ -d confirm=true \ -d payment_method="{{PAYMENT_METHOD_ID}}" \ -d error_on_requires_action=true
    require 'sinatra' require 'json' require 'stripe' # 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' get '/' do # Display checkout page content_type 'text/html' send_file File.join('./index.html') end # Endpoint for when `/pay` is called from client post '/pay' do data = JSON.parse(request.body.read.to_s) begin # Create the PaymentIntent intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'usd', payment_method: data['payment_method_id'], # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. confirm: true, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. error_on_requires_action: true, }) generate_response(intent) rescue Stripe::CardError => e # Display error on client return [200, { error: e.message }.to_json] end end def generate_response(intent) if intent.status == 'succeeded' [200, { success: true }.to_json] else # Any other status would be unexpected, so error [500, { error: 'Invalid PaymentIntent status' }.to_json] end end
    import stripe import json from flask import request, render_template app = Flask(__name__, static_folder=".", static_url_path="", template_folder=".") @app.route('/', methods=['GET']) def get_example(): # Display checkout page return render_template('index.html') # 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' # Endpoint for when `/pay` is called from client @app.route('/pay', methods=['POST']) def confirm_payment(): data = request.get_json() try: # Create the PaymentIntent intent = stripe.PaymentIntent.create( amount=1099, currency='usd', payment_method=data['payment_method_id'], # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. confirm=True, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. error_on_requires_action=True, ) return generate_response(intent) except stripe.error.CardError as e: # Display error on client return json.dumps({'error': e.user_message}), 200 def generate_response(intent): if intent.status == 'succeeded': # Handle post-payment fulfillment return json.dumps({'success': True}), 200 else: # Any other status would be unexpected, so error return json.dumps({'error': 'Invalid PaymentIntent status'}), 500 if __name__ == '__main__': app.run()
    <?php # vendor using composer require_once('vendor/autoload.php'); // 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\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); try { // Create the PaymentIntent $intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'usd', 'payment_method' => $json_obj->payment_method_id, # A PaymentIntent can be confirmed some time after creation, # but here we want to confirm (collect payment) immediately. 'confirm' => true, # If the payment requires any follow-up actions from the # customer, like two-factor authentication, Stripe will error # and you will need to prompt them for a new payment method. 'error_on_requires_action' => true, ]); generateResponse($intent); } catch (\Stripe\Exception\ApiErrorException $e) { // Display error on client echo json_encode(['error' => $e->getMessage()]); } function generateResponse($intent) { if ($intent->status == 'succeeded') { // Handle post-payment fulfillment echo json_encode(['success' => true]); } else { // Any other status would be unexpected, so error echo json_encode(['error' => 'Invalid PaymentIntent status']); } }
    // Using Express const express = require('express'); const app = express(); app.use(express.json()); const { resolve } = require("path"); app.get("/", (req, res) => { // Display checkout page const path = resolve("./index.html"); res.sendFile(path); }); // 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'); // Endpoint for when `/pay` is called from client app.post('/pay', async (request, response) => { try { // Create the PaymentIntent let intent = await stripe.paymentIntents.create({ amount: 1099, currency: 'usd', payment_method: request.body.payment_method_id, // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. confirm: true, // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. error_on_requires_action: true }); return generateResponse(response, intent); } catch (e) { if (e.type === 'StripeCardError') { // Display error on client return response.send({ error: e.message }); } else { // Something else happened return response.status(500).send({ error: e.type }); } } }); function generateResponse(response, intent) { if (intent.status === 'succeeded') { // Handle post-payment fulfillment return response.send({ success: true }); } else { // Any other status would be unexpected, so error return response.status(500).send({error: 'Unexpected status ' + intent.status}); } } app.listen(4242, () => console.log(`Node server listening on port ${4242}!`));
    package com.stripe.sample; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.stripe.Stripe; import com.stripe.exception.CardException; import com.stripe.model.PaymentIntent; import com.stripe.param.PaymentIntentCreateParams; import spark.Response; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import static spark.Spark.*; public class Server { public static void main(String[] args) { port(4242); // 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.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/pay", (request, response) -> { Gson gson = new Gson(); String paymentMethod = gson.fromJson(request.body(), JsonObject.class) .get("payment_method_id").getAsString(); try { PaymentIntent intent = PaymentIntent.create(PaymentIntentCreateParams.builder() .setAmount(1099L) .setCurrency("usd") .setPaymentMethod(paymentMethod) // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. .setConfirm(true) // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. .setErrorOnRequiresAction(true) .build()); return gson.toJson(buildResponse(response, intent)); } catch (CardException e) { response.status(200); Map<String, String> errorResponse = new HashMap<>(); errorResponse.put("error", e.getStripeError().getMessage()); return gson.toJson(errorResponse); } }); } private static Map<String, Object> buildResponse(Response response, PaymentIntent intent) { Map<String, Object> responseData = new HashMap<>(); if (intent.getStatus().equals("succeeded")) { // Handle post-payment fulfillment response.status(200); responseData.put("success", true); } else { // Any other status would be unexpected, so error response.status(500); responseData.put("error", "Invalid PaymentIntent status"); } return responseData; } }
    package main import ( "encoding/json" "log" "net/http" "github.com/stripe/stripe-go" "github.com/stripe/stripe-go/paymentintent" ) func main() { // 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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" fs := http.FileServer(http.Dir("static")) http.Handle("/", fs) http.HandleFunc("/pay", handlePay) addr := "localhost:4242" log.Printf("Listening on %s ...", addr) log.Fatal(http.ListenAndServe(addr, nil)) } type CreatePaymentIntentResponse struct { Success bool `json:"success"` ErrorMessage string `json:"error"` } func handlePay(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { PaymentMethodID string `json:"payment_method_id"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(2000), Currency: stripe.String(string(stripe.CurrencyUSD)), PaymentMethod: stripe.String(req.PaymentMethodID), // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. Confirm: stripe.Bool(true), // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. ErrorOnRequiresAction: stripe.Bool(true), } pi, err := paymentintent.New(params) if err != nil { if stripeErr, ok := err.(*stripe.Error); ok { // return error message to the client w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ ErrorMessage: stripeErr.Msg, }) } else { w.WriteHeader(http.StatusInternalServerError) } } else { generateResponse(pi, w) } } func generateResponse(intent *stripe.PaymentIntent, w http.ResponseWriter) { if intent.Status == stripe.PaymentIntentStatusSucceeded { // Handle post-payment fulfillment w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ Success: true, }) } else { // Any other status would be unexpected, so error w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(CreatePaymentIntentResponse{ Success: false, ErrorMessage: "Invalid Payment Intent status", }) } }
    using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Stripe; using Newtonsoft.Json; namespace Controllers { public class CreatePaymentIntentRequest { [JsonProperty("payment_method_id")] public string PaymentMethodId { get; set; } } // Endpoint for when `/pay` is called from client [Route("/pay")] public class CreatePaymentIntentController : Controller { public IActionResult Index([FromBody] CreatePaymentIntentRequest request) { var service = new PaymentIntentService(); PaymentIntent paymentIntent = null; try { // Create the PaymentIntent var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "usd", PaymentMethodId = request.PaymentMethodId, // A PaymentIntent can be confirmed some time after creation, // but here we want to confirm (collect payment) immediately. Confirm = true, // If the payment requires any follow-up actions from the // customer, like two-factor authentication, Stripe will error // and you will need to prompt them for a new payment method. ErrorOnRequiresAction = true, }; paymentIntent = service.Create(options); } catch (StripeException e) { return Json(new { error = e.StripeError.Message }); } return generatePaymentResponse(paymentIntent); } private IActionResult generatePaymentResponse(PaymentIntent intent) { if (intent.Status == "succeeded") { // Handle post-payment fulfillment return Json(new { success = true }); } else { // Any other status would be unexpected, so error return StatusCode(500, new { error = "Invalid PaymentIntent status" }); } } } }

    Payment Intents API response

    When you make a payment with the API, the response includes a status of the PaymentIntent. If the payment was successful, it will have a status of succeeded.

    { "id": "pi_0FdpcX589O8KAxCGR6tGNyWj", "object": "payment_intent", "amount": 1099, "charges": { "object": "list", "data": [ { "id": "ch_GA9w4aF29fYajT", "object": "charge", "amount": 1099, "refunded": false, "status": "succeeded", } ] }, "client_secret": "pi_0FdpcX589O8KAxCGR6tGNyWj_secret_e00tjcVrSv2tjjufYqPNZBKZc", "currency": "usd", "last_payment_error": null, "status": "succeeded", }

    If the payment is declined, the response includes the error with the code and message included. Below is an example of a failed payment due to two-factor authentication being required for the credit card.

    { "error": { "code": "authentication_required", "decline_code": "authentication_not_handled", "doc_url": "https://stripe.com/docs/error-codes/authentication-required", "message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "payment_intent": { "id": "pi_1G8JtxDpqHItWkFAnB32FhtI", "object": "payment_intent", "amount": 1099, "status": "requires_payment_method", "last_payment_error": { "code": "authentication_required", "decline_code": "authentication_not_handled", "doc_url": "https://stripe.com/docs/error-codes/authentication-required", "message": "This payment required an authentication action to complete, but `error_on_requires_action` was set. When you're ready, you can upgrade your integration to handle actions at https://stripe.com/docs/payments/payment-intents/upgrade-to-handle-actions.", "type": "card_error" }, }, "type": "card_error" } }

    4 Test the integration

    By this point you should have a basic card integration that collects card details and makes a payment.

    There are several test cards you can use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date.

    Number Description
    4242424242424242 Succeeds and immediately processes the payment.
    4000000000009995 Always fails with a decline code of insufficient_funds.
    4000002500003155 Requires authentication, which in this integration will fail with a decline code of authentication_not_handled.

    For the full list of test cards see our guide on testing.

    Upgrading your integration to handle card authentication

    Congratulations! You completed a payments integration for basic card payments. Note that this integration declines cards that require authentication during payment.

    If you start seeing payments in the Dashboard listed as Failed, then it’s time to upgrade your integration. Stripe’s global integration handles these payments instead of automatically declining.

    Was this page helpful?

    Feedback about this page?

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

    On this page