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.
Checkout Form JS Handling Logic
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.
Now that we've got the form in place, we need to add some JavaScript that will send all the card information to Stripe. Once again, Stripe wrote a lot of this code for us. Over-achiever.
Copy their JavaScript and scroll up and paste it with our other JavaScript:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
Stripe.setPublishableKey('{{ stripe_public_key }} | |
// ... lines 25 - 47 | |
</script> | |
{% endblock %} | |
// ... lines 50 - 89 |
Then, back on the docs, scroll down a little further to another function
called stripeResponseHandler()
. Copy that too and paste it:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
Stripe.setPublishableKey('{{ stripe_public_key }} | |
</script> | |
{% endblock %} | |
// ... lines 50 - 89 |
Prepping the JS
Let's look at the code: it uses a jQuery document.ready
block to find the form
and attach an on submit handler function:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 11 | |
// ... lines 15 - 22 | |
// ... lines 25 - 47 | |
</script> | |
{% endblock %} | |
// ... lines 50 - 89 |
Because basically, when the user submits the form, we don't want to submit the form! Ahem, I mean, we want to stop, send all the information to Stripe, wait for the token to come back, put that in the form, and then submit it.
In our case, I've given the form a class called js-checkout-form
:
<form action="" method="POST" class="js-checkout-form checkout-form"> | |
// ... lines 2 - 66 | |
</form> |
Copy that class, and change the JavaScript to look for .js-checkout-form
:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 11 | |
// ... lines 14 - 22 | |
// ... lines 24 - 50 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
This is referenced in one other spot further below. Update that too:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 28 - 49 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
It's not the most organized JS code.
Oh, and you'll notice that I use these js-
classes a lot in my html. That's
a standard that I like to use whenever I give an element a class not because
I want to style it, but because I want to find it with JavaScript.
When this form is submitted, add event.preventDefault()
to prevent the form from
actually submitting:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 11 | |
// ... line 13 | |
// ... lines 16 - 21 | |
// ... lines 24 - 50 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
This does more-or-less the same thing as returning false
at the end of the function,
but with some subtle differences.
Oof - let me fix some of this bad indentation. Next, the code finds the submit
button so it can disable it. In our form, the button has a js-submit-button
class:
<form action="" method="POST" class="js-checkout-form checkout-form"> | |
// ... lines 2 - 59 | |
<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> |
Copy that and update the code here, and once more down below:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 11 | |
// ... line 13 | |
// ... lines 15 - 16 | |
// ... lines 19 - 21 | |
// ... line 24 | |
// ... lines 26 - 28 | |
// ... lines 30 - 34 | |
// ... lines 36 - 48 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
Fetching and Using the Token
Finally, here is the meat of the code. When we call Stripe.card.createToken()
:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 11 | |
// ... line 13 | |
// ... lines 15 - 19 | |
// ... lines 24 - 50 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
Stripe's Javascript will automatically fetch all the credit card data by reading
the data-stripe
attributes. Then, it sends those to stripe via AJAX. When that call
finishes, it will execute the stripeResponseHandler
function, and hopefully
the response will contain that all-important token:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 26 - 49 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
Now, if there was a problem with that card - like an invalid expiration - we need
to show that error to the user. To do that, it looks for a payment-errors
class
and puts the message inside of that:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 26 - 28 | |
// ... lines 34 - 48 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
I have a div
ready for this. Its class is js-checkout-error
and its hidden by default:
<form action="" method="POST" class="js-checkout-form checkout-form"> | |
// ... lines 2 - 53 | |
<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> | |
// ... lines 59 - 66 | |
</form> |
Change the selector to .js-checkout-error
, set the text, but then also call
removeClass('hidden')
so the element shows up:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 26 - 28 | |
// ... lines 35 - 48 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
Below in the else, life is good!! I'll paste the .js-checkout-error
code
from before and modify it to re-add the hidden
class - since now things
are successful:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 26 - 28 | |
// ... lines 30 - 36 | |
// ... lines 40 - 48 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
When things work, the response comes back with a token
, which we get
via response.id
:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 26 - 28 | |
// ... lines 30 - 36 | |
// ... lines 38 - 40 | |
// ... lines 43 - 48 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
To send this to the server, we just smash it into a new input hidden field
called... drumroll ... stripeToken
:
// ... lines 1 - 3 | |
{% block javascripts %} | |
// ... lines 5 - 8 | |
<script type="text/javascript"> | |
// ... lines 10 - 24 | |
// ... lines 26 - 28 | |
// ... lines 30 - 36 | |
// ... lines 38 - 43 | |
// ... lines 46 - 48 | |
</script> | |
{% endblock %} | |
// ... lines 53 - 92 |
This is precisely what the embedded form did. Once the form is submitted, the controller will hum along like nothing ever changed.
Testing the Error and Success
But, that's assuming we didn't mess something up! That's a big but. Go back and refresh the page.
First, test that the error handling works by adding an expiration date in the past. Put in the real credit card number -- oof, ugly formatting - we'll fix that. Then, use an expired expiration. Hit checkout and... boom!
It sent the info to Stripe, Stripe came back with an error, we put the error in the box, and showed that box to our user. In other words, we're awesome. Change this to a future expiration and try again.
It's alive!!!
The only problem I can think of now is how ugly entering a credit card number is: all those numbers just run together. The expiration field is a mess too. Oof. Let's fix that - it's surprisingly easy!
Hi KNP Univ!
I've used Ryan's code and it's working fine, but the code in Stripe documentation as changed (https://stripe.com/docs/ele....
Do you know why? Is it an adaptation to their log or perhaps a security issue?
I'm not a specialist of JS but well I'm curious (as you say in English 'curiosity killed the cat' but not the dev ^_~)
Thanks :)