This course is archived!
This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.
Building the Custom Checkout Form
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Earlier, we were rushing to get the site up and the sheep shopping. That's why we used Stripe's pre-built embedded form. And this is completely fine if you like it. But I want to build a custom form that looks like native on our site.
To do that, go back to the Stripe docs. Instead of embedded form, click "Custom Form". Using a custom form is very similar: we still send the credit card information to Stripe, and Stripe will still give us back a token. The difference is that we are responsible for building the HTML form.
Setting up the Stripe JavaScript
To help communicate with Stripe, we need some JavaScript. Copy the first JavaScript
code and then find the checkout.html.twig
template. At the top, override
{% block javascripts %}
and then call the {{ parent() }}
function. Paste the
script tag below:
// ... lines 1 - 3 | |
{% block javascripts %} | |
{{ parent() }} | |
<script type="text/javascript" src="https://js.stripe.com/v2/"></script> | |
// ... lines 8 - 11 | |
{% endblock %} | |
// ... lines 13 - 52 |
This is just the Twig way of adding some new JavaScript to our page. The base layout
also has a javascripts
block and jQuery is already included:
// ... line 1 | |
<html> | |
// ... lines 3 - 14 | |
<body> | |
// ... lines 16 - 73 | |
{% block javascripts %} | |
<script src="https://code.jquery.com/jquery-3.1.0.js" | |
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk=" | |
crossorigin="anonymous"></script> | |
// ... lines 78 - 79 | |
{% endblock %} | |
</body> | |
</html> |
Next, we need to tell the JavaScript about our publishable key. Copy that code from the docs and add it in the block:
// ... lines 1 - 3 | |
{% block javascripts %} | |
{{ parent() }} | |
<script type="text/javascript" src="https://js.stripe.com/v2/"></script> | |
<script type="text/javascript"> | |
Stripe.setPublishableKey('{{ stripe_public_key }}'); | |
</script> | |
{% endblock %} | |
// ... lines 13 - 52 |
We already know from our original code that we have a variable called stripe_public_key
.
Inside of the JavaScript quotes, print stripe_public_key
:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
Stripe.setPublishableKey('{{ stripe_public_key }}'); | |
</script> | |
{% endblock %} | |
// ... lines 13 - 52 |
Awesome!
Rendering the HTML Form
With that done, it's time to build the form itself. And surprise! I already built
us a basic HTML form. Delete the old, embedded form code. Replace it with
{{ include('order/_cardForm.html.twig') }}
:
// ... lines 1 - 13 | |
{% block body %} | |
<div class="nav-space-checkout"> | |
<div class="container"> | |
<div class="row"> | |
// ... lines 18 - 44 | |
<div class="col-xs-12 col-sm-6"> | |
{{ include('order/_cardForm.html.twig') }} | |
</div> | |
</div> | |
</div> | |
</div> | |
{% endblock %} |
This will read this other template file I prepared: _cardForm.html.twig
:
<form action="" method="POST" class="js-checkout-form checkout-form"> | |
<div class="row"> | |
<div class="col-xs-8 col-sm-6 col-sm-offset-2 form-group"> | |
<div class="input-group"> | |
<span class="input-group-addon"> | |
<i class="fa fa-user"></i> | |
</span> | |
<input data-stripe="name" class="form-control" type="text" autocomplete="off" id="card-name" required placeholder="Card Holder Name"/> | |
</div> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-8 col-sm-6 col-sm-offset-2 form-group"> | |
<div class="input-group"> | |
<span class="input-group-addon"> | |
<i class="fa fa-credit-card"></i> | |
</span> | |
<input data-stripe="number" type="text" autocomplete="off" class="form-control js-cc-number" id="card-number" required placeholder="Card Number"/> | |
</div> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-4 col-sm-3 col-sm-offset-2 form-group"> | |
<div class="input-group"> | |
<span class="input-group-addon"> | |
<i class="fa fa-calendar-o"></i> | |
</span> | |
<input data-stripe="exp" type="text" size="4" autocomplete="off" class="form-control js-cc-exp" id="card-expiration" required="required" placeholder="mm/yy"/> | |
</div> | |
</div> | |
<div class="col-xs-4 col-sm-3 form-group"> | |
<div class="input-group"> | |
<span class="input-group-addon"> | |
<i class="fa fa-lock"></i> | |
</span> | |
<input data-stripe="cvc" type="text" size="4" autocomplete="off" class="form-control js-cc-cvc" id="card-cvc" required="required" placeholder="CVC"/> | |
</div> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-8 col-sm-3 col-sm-offset-2 form-group"> | |
<div class="input-group"> | |
<span class="input-group-addon"> | |
<i class="fa fa-map-marker"></i> | |
</span> | |
<input type="text" autocomplete="off" class="form-control" id="card-zip" placeholder="Zip"/> | |
</div> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-8 col-sm-6 col-sm-offset-2 text-center"> | |
<div class="alert alert-danger js-checkout-error hidden"></div> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-xs-8 col-sm-6 col-sm-offset-2 text-center"> | |
<button type="submit" class="js-submit-button btn btn-lg btn-danger"> | |
Checkout | |
</button> | |
</div> | |
</div> | |
</form> |
And as you can see, this is a normal HTML form. Its method
is POST and its action
is still empty so that it will submit right back to the same URL and controller.
Then, there's just a bunch of fields that are rendered to look good with Bootstrap.
Let's see how awesome my design skills are: go back and refresh. Hey, it looks pretty good! Probably because someone styled this for me.
Do NOT Submit Card Data to your Server
There are a few really important things about this form. Most importantly, notice
that the input fields have no name
attribute. This is crucial. Eventually, we
will submit this form, but we do not want to submit these fields because we do
not want credit card information passing through our server. Because these fields
do not have a name
attribute, they are not submitted.
So instead of name
, Stripe asks you to use a data-stripe
attribute. This tells
Stripe which data this field holds. Since this is the cardholder name, we have
data-stripe="name"
. Then below, data-stripe="number"
, data-stripe="exp"
and
so-on.
But I'm not choosing these values at random. Inside Stripe's documentation, it tells
you which data-stripe
value to use for each piece. If you follow the rules, Stripe's
JavaScript will do all the work of collecting this data and sending it to Stripe.
OK, let's hook up that JavaScript logic next.
Hi
Thanks for you awesome tutorial.