FormValidation v0.8.1 is released, supports Bootstrap 4 alpha 3

Validating custom Stripe form

Examples

Stripe is a popular payment platform. It's quite easy for developers to use and integrate its helpful APIs in a payment, checkout form.

This example covers steps to help you create a payment form with Bootstrap framework, validate it with FormValidation. The credit card data is then processed by Stripe API.

Step 1: Creating a payment form

It's easy to use your favorite framework to create a standard payment form as following:

<form id="paymentForm" class="form-horizontal">
    ...

    <div class="form-group">
        <label class="col-xs-3 control-label">Credit card number</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" data-stripe="number" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">Expiration</label>
        <div class="col-xs-3">
            <input type="text" class="form-control" placeholder="Month" data-stripe="exp-month" />
        </div>
        <div class="col-xs-3">
            <input type="text" class="form-control" placeholder="Year" data-stripe="exp-year" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">CVV</label>
        <div class="col-xs-2">
            <input type="text" class="form-control" data-stripe="cvc" />
        </div>
    </div>

    <input type="hidden" name="token" value="" />

    ...
</form>

As you see, all fields for filling the credit card information don't have the name attribute. Instead, they use the data-stripe attribute which are defined by Stripe. The Stripe API then will collects the credit card data from fields using this attribute.

By not using the name attribute for sensitive fields, we can prevent them from sending to the server when the form is submitted.

The form also includes a hidden field named token. Instead of storing the credit card data which are very sensitive, the form stores an equivalent token that is determined and returned by Stripe API.

Step 2: Adding validation rules

All credit card fields are nameless elements. How we can apply the validation rules for them?

Fortunately, FormValidation provides the selector option to support indicating fields via a CSS selector.

We can use the built in validators for the credit card fields via their data-stripe attribute as following:

$('#paymentForm').formValidation({
    fields: {
        ccNumber: {
            // The credit card number field can be retrieved
            // by [data-stripe="number"] attribute
            selector: '[data-stripe="number"]',
            validators: {
                notEmpty: {
                    ...
                },
                creditCard: {
                    ...
                }
            }
        },
        expMonth: {
            selector: '[data-stripe="exp-month"]',
            row: '.col-xs-3',
            validators: {
                notEmpty: {
                    ...
                },
                digits: {
                    ...
                },
                callback: {
                    ...
                }
            }
        },
        expYear: {
            selector: '[data-stripe="exp-year"]',
            row: '.col-xs-3',
            validators: {
                notEmpty: {
                    ...
                },
                digits: {
                    ...
                },
                callback: {
                    ...
                }
            }
        },
        cvvNumber: {
            selector: '[data-stripe="cvc"]',
            validators: {
                notEmpty: {
                    ...
                },
                cvv: {
                    ...
                    creditCardField: 'ccNumber'
                }
            }
        }
    }
});
The expiration month (expMonth) and year (expYear) fields use the row option to indicate that they're placed inside a custom container instead of a default Bootstrap .form-group one

The table below list validators used for validating credit card data:

Validator Purpose
creditCard Validate a credit card number
cvv Validate a CVV number
callback Validate expiration month and year. For more details, see the Validating credit card expiration date example.

Step 3: Using Stripe API to collect credit card data

When the form passes our validation rules, it's time to send credit card data to Stripe and get back the token. It can be done by triggering the success.form.fv event:

// Change the key to your one
Stripe.setPublishableKey('pk_test_IrimHhYZzZiCmaNo5riP9buX');

$('#paymentForm')
    .formValidation({
        ...
    })
    .on('success.form.fv', function(e) {
        // Prevent default form submission
        e.preventDefault();

        // Get the form element
        var $form = $(e.target);

        // Reset the token first
        $form.find('[name="token"]').val('');

        Stripe.card.createToken($form, function(status, response) {
            if (response.error) {
                // Show the error message
                bootbox.alert(response.error.message);
            } else {
                // Set the token value
                $form.find('[name="token"]').val(response.id);

                // You can submit the form to back-end as usual
                // $form.get(0).submit();

                // Or using Ajax
                $.ajax({
                    url: '/path/to/your/back-end/',
                    data: $form.serialize(),
                    dataType: 'json'
                }).success(function(data) {
                    // Handle the response
                    bootbox.alert(data.message);

                    // Reset the form
                    $form.formValidation('resetForm', true);
                });
            }
        });
    });

Inside the handler of Stripe.card.createToken, we use bootboxjs to show the error message from Stripe if there's something wrong with credit card data. Otherwise, we set the token value returned by Stripe API. This token then can be used to charge the customer.

Optionally, you can use the resetForm() method to reset entire form in case you use Ajax to send the form data to server.

You can see how all the steps are implemented in the working example below. We also provide some fake credit card numbers for testing:

You also can use www.getcreditcardnumbers.com to generate fake credit card numbers for testing
Card type Example
American Express 340653705597107
Diners Club 30130708434187
Diners Club (US) 5517479515603901
Discover 6011734674929094
JCB 3566002020360505
Maestro 6762835098779303
Mastercard 5303765013600904
Visa 4929248980295542
Don't use the real credit card number in the following form. The website also doesn't store any credit card data
<form id="paymentForm" class="form-horizontal">
    <div class="form-group">
        <label class="col-xs-3 control-label">Product name</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" name="productName" value="T-Shirt" readonly="readonly" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">Price</label>
        <div class="col-xs-3 inputGroupContainer">
            <div class="input-group">
                <input type="text" class="form-control" name="price" value="25" readonly="readonly" />
                <span class="input-group-addon">$</span>
            </div>
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">Your full name</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" name="fullName" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">Credit card number</label>
        <div class="col-xs-5">
            <input type="text" class="form-control" data-stripe="number" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">Expiration</label>
        <div class="col-xs-3">
            <input type="text" class="form-control" placeholder="Month" data-stripe="exp-month" />
        </div>
        <div class="col-xs-3">
            <input type="text" class="form-control" placeholder="Year" data-stripe="exp-year" />
        </div>
    </div>

    <div class="form-group">
        <label class="col-xs-3 control-label">CVV</label>
        <div class="col-xs-2">
            <input type="text" class="form-control" data-stripe="cvc" />
        </div>
    </div>

    <div class="form-group">
        <div class="col-xs-9 col-xs-offset-3">
            <button type="submit" class="btn btn-primary">Purchase</button>
        </div>
    </div>

    <input type="hidden" name="token" value="" />
</form>

<script src="https://js.stripe.com/v2/"></script>
<script src="//oss.maxcdn.com/bootbox/4.2.0/bootbox.min.js"></script>
<script>
$(document).ready(function() {
    // Change the key to your one
    Stripe.setPublishableKey('pk_test_IrimHhYZzZiCmaNo5riP9buX');

    $('#paymentForm')
        .formValidation({
            framework: 'bootstrap',
            icon: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: {
                fullName: {
                    validators: {
                        notEmpty: {
                            message: 'The full name is required'
                        }
                    }
                },
                ccNumber: {
                    selector: '[data-stripe="number"]',
                    validators: {
                        notEmpty: {
                            message: 'The credit card number is required'
                        },
                        creditCard: {
                            message: 'The credit card number is not valid'
                        }
                    }
                },
                expMonth: {
                    selector: '[data-stripe="exp-month"]',
                    row: '.col-xs-3',
                    validators: {
                        notEmpty: {
                            message: 'The expiration month is required'
                        },
                        digits: {
                            message: 'The expiration month can contain digits only'
                        },
                        callback: {
                            message: 'Expired',
                            callback: function(value, validator) {
                                value = parseInt(value, 10);
                                var year         = validator.getFieldElements('expYear').val(),
                                    currentMonth = new Date().getMonth() + 1,
                                    currentYear  = new Date().getFullYear();
                                if (value < 0 || value > 12) {
                                    return false;
                                }
                                if (year == '') {
                                    return true;
                                }
                                year = parseInt(year, 10);
                                if (year > currentYear || (year == currentYear && value >= currentMonth)) {
                                    validator.updateStatus('expYear', 'VALID');
                                    return true;
                                } else {
                                    return false;
                                }
                            }
                        }
                    }
                },
                expYear: {
                    selector: '[data-stripe="exp-year"]',
                    row: '.col-xs-3',
                    validators: {
                        notEmpty: {
                            message: 'The expiration year is required'
                        },
                        digits: {
                            message: 'The expiration year can contain digits only'
                        },
                        callback: {
                            message: 'Expired',
                            callback: function(value, validator) {
                                value = parseInt(value, 10);
                                var month        = validator.getFieldElements('expMonth').val(),
                                    currentMonth = new Date().getMonth() + 1,
                                    currentYear  = new Date().getFullYear();
                                if (value < currentYear || value > currentYear + 100) {
                                    return false;
                                }
                                if (month == '') {
                                    return false;
                                }
                                month = parseInt(month, 10);
                                if (value > currentYear || (value == currentYear && month >= currentMonth)) {
                                    validator.updateStatus('expMonth', 'VALID');
                                    return true;
                                } else {
                                    return false;
                                }
                            }
                        }
                    }
                },
                cvvNumber: {
                    selector: '[data-stripe="cvc"]',
                    validators: {
                        notEmpty: {
                            message: 'The CVV number is required'
                        },
                        cvv: {
                            message: 'The value is not a valid CVV',
                            creditCardField: 'ccNumber'
                        }
                    }
                }
            }
        })
        .on('success.form.fv', function(e) {
            e.preventDefault();

            var $form = $(e.target);

            // Reset the token first
            $form.find('[name="token"]').val('');

            Stripe.card.createToken($form, function(status, response) {
                if (response.error) {
                    bootbox.alert(response.error.message);
                } else {
                    // Set the token value
                    $form.find('[name="token"]').val(response.id);

                    // You can submit the form to back-end as usual
                    // $form.get(0).submit();

                    // Or using Ajax
                    $.ajax({
                        // You need to change the url option to your back-end endpoint
                        url: 'response.json',
                        data: $form.serialize(),
                        dataType: 'json'
                    }).success(function(data) {
                        // Handle the response
                        bootbox.alert(data.message);

                        // Reset the form
                        $form.formValidation('resetForm', true);
                    });
                }
            });
        });
});
</script>