Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
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.

Embedded Checkout Form

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

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.

Leave a comment!

10
Login or Register to join the conversation
Default user avatar
Default user avatar Viet Son Nguyen | posted 3 years ago

Hello,
thank you very much for your tutorial, you did a great job.
In this video, I try to connect stripe to my project (Symfony 5). But it have a lot of change in stripe...
I can't find the bellow in the stripe's document...

<script src="https://checkout.stripe.com..." 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>

Reply

Hey Viet Son Nguyen !

> thank you very much for your tutorial, you did a great job

Thanks! But I *am* sorry to say that this tutorial is starting to show its age! Several parts are quite old and things have changed :/.

For the embedded checkout form, I'm not sure they support this anymore. They do have a way where you can easily redirect a user to Stripe - https://stripe.com/docs/pay... - or you can build a custom form, the new way is to use Stripe elements - https://stripe.com/docs/str... - which are pretty cool because they give you some control, but take care of a lot of the work for you. I think my embedded code used in this tutorial may still work, but it's not the recommended way of doing it anymore.

Cheers!

Reply
Szymon Z. Avatar

can you tell me why this elements dont want to render on my site?
https://ibb.co/8gXrNsB
Is this becouse of xampp server?

Reply

Hey Simon,

Thank you for providing the screenshot, it really helps to understand it better!

It looks like those requests to JS files are blocked by something... probably by your browser? I see you're using Chrome, could you try to open it in Incognito mode? Or if still not work, try to open this in another browser, e.g. Firefox? Does it helps?

If still does not work - could you do a right click on a blocked file and open it in a new tab? Do you see its content? Or do you see any errors? Which errors?

P.S. It might be because of the server you use, i.e. XAMPP. I'd recommend you to try it in another server, e.g. Symfony or PHP built-in servers and see if those JS requests are still blocked.

I hope this helps to debug this!

Cheers!

Reply
Szymon Z. Avatar

It was adblock. It is known problem to his maker. But unresolved.

Reply

Hey Simon,

Ah, yes, that plugin may do things like this too, good find! As a workaround, you probably could whitelist the website and it should be ok.

Cheers!

Reply
Szymon Z. Avatar

and by the way rendering was problem made by form-row. In symfony with bootstrap it was cross rendering with some other class. I had to remove class form-row from div and it worked.

Reply

Hey simon,

Glad you found the problem! And thanks for sharing your solution with others!

Cheers!

Reply
Szymon Z. Avatar

yeah but what about customers ?

Reply

Hey Simon,

Sorry, could you tell a bit more?

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

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.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9, <7.4",
        "symfony/symfony": "3.1.*", // v3.1.10
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "stripe/stripe-php": "^3.15", // v3.23.0
        "doctrine/doctrine-migrations-bundle": "^1.1", // 1.1.1
        "twig/twig": "^1.24.1", // v1.35.2
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.2
        "hautelook/alice-bundle": "^1.3", // v1.3.1
        "doctrine/data-fixtures": "^1.2" // v1.2.1
    }
}
userVoice