How can I style a Stripe Elements input with Bootstrap?

Solution 1:

Alright, so I had to figure this out, because I was using Stripe.js v2, and the security vulnerability was explained to me by Stripe tech support, so I felt obligated to switch to Stripe.js v3 "Elements". What they said was that any javascript on the same page as your credit card form elements could obtain the values of the sensitive credit card data. I suppose this could happen if a person was pulling in external scripts ... and I suppose it must have happened, or they wouldn't care about it. Anyway, this is how I got my Stripe.js v3 elements working with Bootstrap 4 input groups. It's a full working example, you'd just need to change out the public key.

Default jQuery Based Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Stripe.js v3 with Bootstrap 4 Test</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    <style>
        /* Blue outline on focus */
        .StripeElement--focus {
            border-color: #80BDFF;
            outline:0;
            box-shadow: 0 0 0 .2rem rgba(0,123,255,.25);
            transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
        }
        /* Can't see what I type without this */
        #card-number.form-control,
        #card-cvc.form-control,
        #card-exp.form-control {
            display:inline-block;
        }
    </style>
</head>
<body>

    <div class="container-fluid">
        <h1 class="mt-5">Stripe.js v3 with Bootstrap 4 (beta) Test</h1>
        <div id="card-errors" role="alert"></div>
        <div class="card">
            <div class="card-body">
                <form id="payment-form">
                    <label for="name">Name on Card</label>
                    <div class="input-group mb-2">
                        <div class="input-group-prepend">
                            <span class="input-group-text">A</span>
                        </div>
                        <input type="text" class="form-control" id="name">
                        <div class="input-group-append">
                            <span class="input-group-text">B</span>
                        </div>
                    </div>
                    <label for="card-number">Credit Card Number</label>
                    <div class="input-group mb-2">
                        <div class="input-group-prepend">
                            <span class="input-group-text">C</span>
                        </div>
                        <span id="card-number" class="form-control">
                            <!-- Stripe Card Element -->
                        </span>
                        <div class="input-group-append">
                            <span class="input-group-text">D</span>
                        </div>
                    </div>
                    <label for="card-cvc">CVC Number</label>
                    <div class="input-group mb-2">
                        <div class="input-group-prepend">
                            <span class="input-group-text">E</span>
                        </div>
                        <span id="card-cvc" class="form-control">
                            <!-- Stripe CVC Element -->
                        </span>
                    </div>
                    <label for="card-exp">Expiration</label>
                    <div class="input-group mb-2">
                        <span id="card-exp" class="form-control">
                            <!-- Stripe Card Expiry Element -->
                        </span>
                        <div class="input-group-append">
                            <span class="input-group-text">F</span>
                        </div>
                    </div>
                    <button id="payment-submit" class="btn btn-primary mt-1">Submit Payment</button>
                </form>
            </div>
        </div>
    </div>

<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<script src="https://js.stripe.com/v3/"></script>
<script>
    $(document).ready(function(){

        // Create a Stripe client
        var stripe = Stripe('pk_test_XxXxXxXxXxXxXxXxXxXxXxXx');

        // Create an instance of Elements
        var elements = stripe.elements();

        // Try to match bootstrap 4 styling
        var style = {
            base: {
                'lineHeight': '1.35',
                'fontSize': '1.11rem',
                'color': '#495057',
                'fontFamily': 'apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif'
            }
        };

        // Card number
        var card = elements.create('cardNumber', {
            'placeholder': '',
            'style': style
        });
        card.mount('#card-number');

        // CVC
        var cvc = elements.create('cardCvc', {
            'placeholder': '',
            'style': style
        });
        cvc.mount('#card-cvc');

        // Card expiry
        var exp = elements.create('cardExpiry', {
            'placeholder': '',
            'style': style
        });
        exp.mount('#card-exp');

        // Submit
        $('#payment-submit').on('click', function(e){
            e.preventDefault();
            var cardData = {
                'name': $('#name').val()
            };
            stripe.createToken(card, cardData).then(function(result) {
                console.log(result);
                if(result.error && result.error.message){
                    alert(result.error.message);
                }else{
                    alert(result.token.id);
                }
            });
        });

    });
</script>
</body>
</html>

I tested only in Firefox, Chrome, and Chrome on Android. Seems to work fine. Let me know if you experience any issues.

Optional Vue.js Based Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Stripe.js v3 with Bootstrap 4 and Vue.js</title>
    <link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
    <link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css"/>
    <style>
        /* This background color not essential for the example */
        html, body {
            background:#999;
        }

        /* Padding for Stripe Element containers */
        .stripe-element-container {
            padding-top: .55rem;
            padding-bottom: .50rem;
        }

        /* Blue outline on focus */
        .StripeElement--focus {
            border-color: #80BDFF;
            outline:0;
            box-shadow: 0 0 0 .2rem rgba(0,123,255,.25);
            transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
        }

        /* Can't see what I type without this */
        #card-number.form-control,
        #card-cvc.form-control,
        #card-exp.form-control {
            display:inline-block;
        }
    </style>
</head>
<body>
    <div id="app">
        <stripe-form inline-template>
            <div class="container-fluid">
                <div class="row">
                    <div class="col-md-4 offset-md-4 pt-5">
                        <div class="card">
                            <div class="card-header">
                                <h3 class="mb-0">Pay Now</h3>
                            </div>
                            <div class="card-body">
                                <div v-bind:class="{alert: activeError, 'alert-danger': activeError}" role="alert" v-html="errorText"></div>

                                <form>

                                    <div class="form-group mb-4">
                                        <label for="name">Name on Card</label>
                                        <input type="text" class="form-control" v-model="ccName" />
                                    </div>

                                    <div class="form-group">
                                        <label for="card-number">Credit Card Number</label>
                                        <span id="card-number" class="form-control stripe-element-container">
                                            <!-- Stripe Card Element -->
                                        </span>
                                    </div>

                                    <div class="form-group">
                                        <label for="card-cvc">CVC Number</label>
                                        <span id="card-cvc" class="form-control stripe-element-container">
                                            <!-- Stripe CVC Element -->
                                        </span>
                                    </div>

                                    <div class="form-group">
                                        <label for="card-exp">Expiration</label>
                                        <span id="card-exp" class="form-control stripe-element-container">
                                            <!-- Stripe Card Expiry Element -->
                                        </span>
                                    </div>

                                    <button @click.prevent="paymentSubmit" class="btn btn-primary mt-1 float-right">Submit Payment</button>

                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </stripe-form>
        <modal></modal>
    </div>

<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<script src="https://unpkg.com/babel-polyfill@latest/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.js"></script>
<script src="https://js.stripe.com/v3/"></script>
<script>

// Your Stripe public key
const stripePublicKey = 'pk_test_XxXxXxXxXxXxXxXxXxXxXxXx';

/**
 * Class allows firing of events and 
 * listening of events between components
 */
window.Events = new class {

    constructor(){
        this.vue = new Vue();
    }

    fire( event, data = null ){
        this.vue.$emit( event, data );
    }

    listenFor( event, callback ){
        this.vue.$on( event, callback );
    }
}

/**
 * See: https://bootstrap-vue.js.org/docs/components/modal/
 */
Vue.component('modal', {

    template: `
        <div>
            <b-modal ref="myModalRef" ok-only ok-title="Close" v-bind:title="title">
                <p class="mb-0">{{ body }}</p>
            </b-modal>
        </div>
    `,

    data: function(){
        return {
            title: '',
            body: ''
        }
    },

    methods: {
        showModal () {
          this.$refs.myModalRef.show()
        }
        /* This not needed for this example
        ,
        hideModal () {
          this.$refs.myModalRef.hide()
        }
        */
    },

    created(){
        Events.listenFor('modalShow', ( data ) => {
            this.title = data.title;
            this.body = data.message;
            this.showModal();
        });
    }

});

Vue.component('stripe-form', {

    data: function(){
        return { 
            activeError: false,
            errorText: '',
            ccName: '',
            stripe: null,
            card: null,
            cvc: null,
            exp: null
        }
    },

    methods: {
        paymentSubmit: function(){
            let cardData = {
                'name': this.ccName
            };
            // Ensure the name field is not empty
            if( cardData.name.trim() == '' ){
                // Show an error
                this.activeError = true;
                this.errorText = '<b>Submission Error:</b><br />Name is required.';
                // Abort !!
                return;
            }
            this.stripe.createToken( this.card, cardData).then( (result) => {
                if(result.error && result.error.message){

                    // Show any errors
                    this.activeError = true;
                    this.errorText = '<b>Submission Error:</b><br />' + result.error.message;

                }else{

                    /**
                     * Success message in modal.
                     * This is normally where you'd post to your server, 
                     * and have it actually attempt the credit card transaction
                     * using the token ID that was just received.
                     */
                    Events.fire('modalShow', {
                        'title': 'Success',
                        'message': result.token.id
                    });

                    // Clear the form
                    this.activeError = false;
                    this.errorText = '';
                    this.ccName = '';
                    // Stripe elements must be cleared in a special way
                    this.card.clear();
                    this.cvc.clear();
                    this.exp.clear();
                }
            });
        }
    },

    mounted: function(){
        // Create a Stripe client
        this.stripe = Stripe( stripePublicKey );

        // Create an instance of Elements
        const elements = this.stripe.elements();

        /**
         * Try to match bootstrap 4 styling.
         * --------------------------------
         * fontSize was in rem units, but Stripe says that it should be in pixels.
         */
        const style = {
            base: {
                'fontSize': '16px',
                'color': '#495057',
                'fontFamily': 'apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif'
            }
        };

        // Card number
        this.card = elements.create('cardNumber', {
            'placeholder': '',
            'style': style
        });
        this.card.mount('#card-number');

        // CVC
        this.cvc = elements.create('cardCvc', {
            'placeholder': '',
            'style': style
        });
        this.cvc.mount('#card-cvc');

        // Card expiry
        this.exp = elements.create('cardExpiry', {
            'placeholder': '',
            'style': style
        });
        this.exp.mount('#card-exp');
    }

});

new Vue({ el: '#app' });

</script>
</body>
</html>

This Vue.js example could benefit from some work, but may help get you started.

Solution 2:

After digging around the docs a bit more, I found that https://stripe.com/docs/stripe.js#the-element-container says "You should style the container you mount an Element to as if it were an on your page."

By adding Bootstrap's form-control class to the <div> I'm mounting the Element in, the field looks almost like any other Bootstrap-styled input field:

<div id="card-element" class="form-control"></div>

For some reason, the height of the field doesn't quite match, but through trial and error, I got it with:

var stripe = Stripe('your_key');
var elements = stripe.elements();
var card = elements.create('card', { style:
  {
    base: {
      lineHeight: '1.429'
    }
  }
});
card.mount('#card-element');

Solution 3:

This is all that was required for us using

  <div class="form-group">
    <label for="card-element">Credit or debit card</label>
    <div id="card-element" class="form-control" style='height: 2.4em; padding-top: .7em;'>
      <!-- A Stripe Element will be inserted here. -->
    </div>
  </div>

enter image description here