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.

Buy Access to Course
02.

Embedded Checkout Form

Share this awesome video!

|

Click on the documentation link at the top, and then click Embedded Form. There are two ways to build a checkout-form: the easy & lazy way - via an embedded form that Stripe builds for you - or the harder way - with an HTML form that you build yourself. Our sheep investors want us to hoof-it and get this live, so let's do the easy way first - and switch to a custom form later.

Getting the Form Script Code

To get the embedded form, copy the form code. Then, head to the app and open the app/Resources/views/order/checkout.html.twig file. This is the template for the checkout page.

At the bottom, I already have a spot waiting for the checkout form. Paste the code there:

// ... lines 1 - 3
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
// ... lines 8 - 34
<div class="col-xs-12 col-sm-6">
<form action="" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="pk_test_HxZzNHy8LImKK9LDtgMDRBwd"
data-amount="999"
data-name="Dollar Shear Club"
data-description="Widget"
data-image="/img/documentation/checkout/marketplace.png"
data-locale="auto">
</script>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

Oh! And as promised: this pk_test value is the public key from our test environment.

Your Stripe Public and Private Keys

Let me show you what I mean: in Stripe, open your "Account Settings" on the top and then select "API Keys". Each environment - Test and Live - have their own two keys: the secret key and the publishable or public key. Right now, we're using the public key for the test environment - so once we get this going, orders will show up there. After we deploy, we'll need to switch to the Live environment keys.

Oh, and, I think it's obvious - but these secret keys need to be kept secret. The last thing you should do is create a screencast and publish them to the web. Oh crap.

Hey, A Checkout Form

But anyways, without doing any more work, go back to the browser and refresh the page. Hello checkout button! And hello checkout form! Obviously, $9.99 isn't the right price, for these amazing sheep accessories.

To fix that, head back to the template. Everything about the form is controlled with these HTML attributes. Obviously, the most important one is amount. Set it to {{ cart.total }} - cart is a variable I've passed into the template - then the important part: * 100:

// ... lines 1 - 3
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
// ... lines 8 - 34
<div class="col-xs-12 col-sm-6">
<form action="" method="POST">
<script
// ... lines 38 - 39
data-amount="{{ cart.total * 100 }}"
// ... lines 41 - 45
</script>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

Whenever you talk about amounts in Stripe, you use the smallest denomination of the currency, so cents in USD. If you need to charge the user $5, then tell Stripe to charge them an amount of 500 cents.

Then, fill in anything else that's important to you, for example, data-image. I'll set this to our logo:

// ... lines 1 - 3
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
// ... lines 8 - 34
<div class="col-xs-12 col-sm-6">
<form action="" method="POST">
<script
// ... lines 38 - 42
data-image="{{ asset('images/logo.png') }}"
// ... lines 44 - 45
</script>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

Checking out with a Test Card

Refresh to reflect the new settings. The total should be $62, and it is! Because we're using the test environment, Stripe gives us fake, test cards we can use to checkout. I'll show you others later - but to checkout successfully, use 4242 4242 4242 4242. You can use any valid future expiration and any CVC.

Ok, moment of truth: hit pay!

It worked! I think... Wait, what just happened? Well, a really important step just happened - a step that's core to how Stripe checkout works.

The Stripe Checkout Token

First, credit card information is never, ever sent to our servers... which is the greatest news I have ever heard from a security standpoint. I do not want to handle your CC number: this would greatly increase the security requirements on my server.

Instead, when you hit "Pay", this sends the credit card information to Stripe directly, via AJAX. If the card is valid, it sends back a token string, which represents that card. The Stripe JS puts that token into the form as an hidden input field and then submits the form like normal to our server. So the only thing that's sent to our server is this token. The customer has not been charged yet, but with a little more work - we can fetch that token in our code and ask Stripe to charge that credit card.

Fetching the Stripe Token

Let's go get that token on the server. Open up src/AppBundle/Controller/OrderController.php and find checkoutAction():

// ... lines 1 - 9
class OrderController extends BaseController
{
// ... lines 12 - 25
/**
* @Route("/checkout", name="order_checkout")
* @Security("is_granted('ROLE_USER')")
*/
public function checkoutAction()
{
$products = $this->get('shopping_cart')->getProducts();
return $this->render('order/checkout.html.twig', array(
'products' => $products,
'cart' => $this->get('shopping_cart')
));
}
}

This controller renders the checkout page. And because the HTML form has action="":

// ... lines 1 - 3
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
// ... lines 8 - 34
<div class="col-xs-12 col-sm-6">
<form action="" method="POST">
// ... lines 37 - 46
</form>
</div>
</div>
</div>
</div>
{% endblock %}

When Stripe submits the form, it submits right back to this same URL and controller.

To fetch the token, add a Request argument, and make sure you have the use statement on top:

// ... lines 1 - 8
use Symfony\Component\HttpFoundation\Request;
class OrderController extends BaseController
{
// ... lines 13 - 30
public function checkoutAction(Request $request)
{
// ... lines 33 - 45
}
}

Then, inside the method, say if ($request->isMethod('POST'), then we know the form was just submitted. If so, dump($request->get('stripeToken')):

// ... lines 1 - 10
class OrderController extends BaseController
{
// ... lines 13 - 30
public function checkoutAction(Request $request)
{
$products = $this->get('shopping_cart')->getProducts();
if ($request->isMethod('POST')) {
$token = $request->request->get('stripeToken');
dump($token);
}
// ... lines 40 - 45
}
}

If you read Stripe's documentation, that's the name of the hidden input field.

Try it out! Refresh and fill in the info again: use your trusty fake credit card number, some fake data and Pay. The form submits and the page refreshes. But thanks to the dump() function, hover over the target icon in the web debug toolbar. Perfect! We were able to fetch the token.

In a second, we're going to send this back to Stripe and ask them to actually charge the credit card it represents. But before we do that, head back to the Stripe dashboard.

Stripe shows you a log of pretty much everything that happens. Click the Logs link: these are all the interactions we've had with Stripe's API, including a few from before I hit record on the screencast. Click the first one: this is the AJAX request that the JavaScript made to Stripe: it sent over the credit card information, and Stripe sent back the token. If I search for the token that was just dumped, it matches.

Ok, let's use that token to charge our customer.