Building a custom payment form

This tutorial helps you build your first payment form with Stripe. If you need help, check out our answers to common questions or chat live with our developers in #stripe on freenode.

At a high level, here's what you'll accomplish in this tutorial:

  1. Collect credit card information with Stripe.js
  2. Convert those details to what we call a single-use token
  3. Send that token, with the rest of your form, to your server

We'll cover what to do after that in our next tutorial. Before we get to the first step, let's take a quick look at a typical payment form. This is the part you can build with your web framework, or by hand in HTML — however you're used to building forms on the web.

<form action="" method="POST" id="payment-form">
  <span class="payment-errors"></span>

  <div class="form-row">
      <span>Card Number</span>
      <input type="text" size="20" data-stripe="number"/>

  <div class="form-row">
      <input type="text" size="4" data-stripe="cvc"/>

  <div class="form-row">
      <span>Expiration (MM/YYYY)</span>
      <input type="text" size="2" data-stripe="exp-month"/>
    <span> / </span>
    <input type="text" size="4" data-stripe="exp-year"/>

  <button type="submit">Submit Payment</button>

Fairly standard. Note how input fields representing sensitive card data (number, CVC, expiration month and year) do not have a "name" attribute. This prevents them from hitting your server when the form is submitted. We're also including a data-stripe attribute on the relevant fields, which we'll discuss later in the tutorial.

Your life becomes easier if sensitive cardholder data does not hit your servers. You no longer need to worry about redacting logs, encrypting cardholder details, or other burdens of PCI compliance.

With Stripe.js, you never have to handle sensitive card data. It's automatically converted to a token which you can safely send to your servers and use to charge your customers.

Step 1: Collecting credit card information

First, include Stripe.js in the page:

<script type="text/javascript" src=""></script>

To prevent problems with some older browsers, we recommend putting the script tag in the <head> tag of your page, or as a direct descendant of the <body> at the end of your page.

In a separate script tag, after the first, set your publishable key:

<script type="text/javascript">
  // This identifies your website in the createToken call below
  // ...

Stripe.setPublishableKey(..) identifies your website when communicating with Stripe. Note that we've pre-filled the example with your test publishable API key. Remember to replace the test key with your live key in production. You can get all your keys from your account page.

Step 2: Create a single use token

Next, we will want to create a single-use token that can be used to represent the credit card information your customer enters. Note that you should not store or attempt to reuse single-use tokens -- if you wish to charge a credit card multiple times, you should use your token to create a Customer object. After the code we just added, we'll add an event handler to our form. We want to capture the submit event, and then use the credit card information to create a single-use token:

jQuery(function($) {
  $('#payment-form').submit(function(event) {
    var $form = $(this);

    // Disable the submit button to prevent repeated clicks
    $form.find('button').prop('disabled', true);

    Stripe.card.createToken($form, stripeResponseHandler);

    // Prevent the form from submitting with the default action
    return false;

The important code to notice is the call to Stripe.card.createToken. The first argument is the form element containing credit card data entered by the user. The relevant values are fetched from their associated inputs using the data-stripe attribute specified in the first example.

You should provide at least the card number and expiration info. The complete list of fields you can provide is available in the Stripe.js documentation.

The second argument stripeResponseHandler is a callback that handles the response from Stripe. createToken is an asynchronous call – it returns immediately and invokes stripeResponseHandler when it receives a response from Stripe's servers. Whatever function you pass should take two arguments, status and response:

status is one of the status codes described in the API docs.

response is an Object with these properties:

  id : "tok_u5dg20Gra", // String of token identifier,
  card : {...}, // Dictionary of the card used to create the token
  created : 1397758988, // Integer of date token was created
  currency: "usd", // String currency that the token was created in
  livemode: true, // Boolean of whether this token was created with a live or test API key
  object: "token", // String identifier of the type of object, always "token"
  used : false, // Boolean of whether this token has been used,

Step 3: Sending the form to your server

In our example, this is done in stripeResponseHandler:

  • If the card information entered by the user returned an error, it gets displayed on the page.
  • If no errors were returned (i.e. a single-use token was created successfully), add the returned token to the form in the stripeToken field and submit the form to the server.
var stripeResponseHandler = function(status, response) {
  var $form = $('#payment-form');

  if (response.error) {
    // Show the errors on the form
    $form.find('button').prop('disabled', false);
  } else {
    // token contains id, last4, and card type
    var token =;
    // Insert the token into the form so it gets submitted to the server
    $form.append($('<input type="hidden" name="stripeToken" />').val(token));
    // and submit

Notice that in order to add the token to the info submitted to your server, we're adding a new input tag into the form, and setting its value to the id of the token.

After we've added the info to the form, we re-submit the form (remember, we stopped it from submitting before so we could wait for Stripe to tokenize the credit card details). We call submit() on the form directly, rather than the jQuery wrapper, so that we don't run into an infinite loop. The data will be sent as an HTTP POST to the URL in the form's action.

Take a look at the full example form to see everything put together.