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.

Charge It (The Stripe PHP SDK)

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.

Stripe has a nice, RESTful API, and you're going to spend a lot of time talking with it. Google for "Stripe API docs" to find this amazing page. You can set this as your new homepage: it describes every endpoint: how to create charges, customers and a lot of other things we're going to talk about.

But first, make sure that you select PHP on the top right. Thanks to this, the docs will show you code snippets in PHP. And those code snippets will use Stripe's PHP SDK library. Google for that and open its Github Page.

First, let's get this guy installed. Copy the composer require line, move over to your terminal, open a new tab and paste that:

composer require stripe/stripe-php

While we're waiting for Jordi to finish, let's keep going.

Using the Token to Create a Charge

To actually charge a user, we need to... well, create a Stripe charge. In the Stripe API, click "Charges" on the left and find Create a Charge.

Hey! It wrote the code for us. Copy the code block on the right. Now, go back to OrderController and first, create a new $token variable and set it to the stripeToken POST parameter. Now, paste that code:

... lines 1 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
$token = $request->request->get('stripeToken');
\Stripe\Stripe::setApiKey("XXX_PRIVATEKEY_XXX");
\Stripe\Charge::create(array(
"amount" => $this->get('shopping_cart')->getTotal() * 100,
"currency" => "usd",
"source" => $token,
"description" => "First test charge!"
));
... lines 45 - 49
}
... lines 51 - 56
}
}

Let's go check on Composer. It's just finishing - perfect! My editor now sees all these new Stripe classes.

See that API key?

... lines 1 - 10
class OrderController extends BaseController
{
... lines 13 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
... lines 36 - 37
\Stripe\Stripe::setApiKey("XXX_PRIVATEKEY_XXX");
... lines 39 - 49
}
... lines 51 - 56
}
}

Once again, this is a real key from our account in the test environment. This time, it's the secret key. The public key is the one in our template:

... 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
... line 38
data-key="pk_test_HxZzNHy8LImKK9LDtgMDRBwd"
... lines 40 - 45
</script>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

Update charge details with our real information. To get the total, I'll fetch a service I created for the project called shopping_cart, call getTotal() on this, and then multiply it by 100:

... lines 1 - 10
class OrderController extends BaseController
{
... lines 13 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
... lines 36 - 38
\Stripe\Charge::create(array(
"amount" => $this->get('shopping_cart')->getTotal() * 100,
... lines 41 - 43
));
... lines 45 - 49
}
... lines 51 - 56
}
}

For source, replace this fake token with the submitted $token variable:

... lines 1 - 10
class OrderController extends BaseController
{
... lines 13 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
$token = $request->request->get('stripeToken');
... lines 37 - 38
\Stripe\Charge::create(array(
... lines 40 - 41
"source" => $token,
... line 43
));
... lines 45 - 49
}
... lines 51 - 56
}
}

The token represents the credit card that was just sent. So we're saying: Use this card as the source for this charge. And then, put whatever you want for description, like "First test charge":

... lines 1 - 10
class OrderController extends BaseController
{
... lines 13 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
... lines 36 - 38
\Stripe\Charge::create(array(
... lines 40 - 42
"description" => "First test charge!"
));
... lines 45 - 49
}
... lines 51 - 56
}
}

When this code runs, it will make an API request to Stripe. If that's successful, the user will be charged. If something goes wrong, Stripe will throw an Exception. More on that later.

Cleaning up after Checkout

But before we try it, we need to finish up a few application-specific things. For example, after check out, we need to empty the shopping cart. The products are great, but the customer probably doesn't want to buy them twice in a row:

... lines 1 - 10
class OrderController extends BaseController
{
... lines 13 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
... lines 36 - 38
\Stripe\Charge::create(array(
... lines 40 - 43
));
$this->get('shopping_cart')->emptyCart();
... lines 47 - 49
}
... lines 51 - 56
}
}

Next, I want to show a success message to the user. To do that in Symfony, call $this->addFlash('success', 'Order Complete! Yay!'):

... lines 1 - 10
class OrderController extends BaseController
{
... lines 13 - 30
public function checkoutAction(Request $request)
{
... lines 33 - 34
if ($request->isMethod('POST')) {
... lines 36 - 45
$this->get('shopping_cart')->emptyCart();
$this->addFlash('success', 'Order Complete! Yay!');
return $this->redirectToRoute('homepage');
}
... lines 51 - 56
}
}

And finally, you should definitely redirect the page somewhere. I'll use redirectToRoute() to send the user to the homepage.

And that is it. Now for the real moment of truth. Hit enter to reload our page without submitting, put in the fake credit card, any date, any CVC, and...

Tip

If you get some sort of API or connection, you may need to upgrade some TLS security settings.

Hey! Okay. No errors. That should mean it worked. How can we know? Go check out the Stripe Dashboard. This time, click "Payments". And there's our payment for $62. You can even see all the information that was used.

Congratulations guys! You just added a checkout to your site in 15 minutes. Now let's make this thing rock-solid.

Leave a comment!

15
Login or Register to join the conversation
Djibril D. Avatar
Djibril D. Avatar Djibril D. | posted 4 years ago

Hello i have a error with my token:
No such token: tok_1D0HYdGNRPEKuC6IlRFsOREX

Reply

Hey djibril,

Your token does look like a valid Stripe token. If you have this error, probably the token was regenerated somehow. Do you hardcode this token manually as a string? Or do you retrieve a dynamic token from Request object and pass it with a variable as we show in screencast?

Cheers!

Reply
Default user avatar
Default user avatar Kosta Andonovski | posted 4 years ago

when i process the payment it tries to redirect me to /your-server-side-code

This is very strange because my controller specifies to redirect me to my home page "return $this->redirectToRoute('user_home_show'); "
Is that stripe trying to auto-redirect me somewhere?

Reply
Default user avatar

all good i fixed it was a stupid mistake thanks anyway

1 Reply
Default user avatar

what was the mistake you did ?

Reply

Hey Blueblazer172 ,

Do you have a similar issue? If you describe the problem a bit more - we'll try to help you.

Cheers!

Reply
Default user avatar
Default user avatar Blueblazer172 | victor | posted 4 years ago

again I solved it myself :P
I had to remove the action="/your-server-side-code" parameter in

<form method="POST">

in the checkout.html.twig file

that easy xD

Reply

Easy indeed! ;)

Cheers!

Reply

Great! Quick catch ;)

Cheers!

Reply
Jovan P. Avatar
Jovan P. Avatar Jovan P. | posted 4 years ago

Awesome screencast so far! :) Thanks a lot! :)

Just an extra step: Make sure you upgrade nss, curl and openssl, or else Stripe will reject our request with message saying that we're trying to setup non-TLS1.2 communication. According to their blog (source: https://stripe.com/blog/upg... ), the new users starting from July 1st will have to use TLS 1.2. Prior to upgrade (and error) I had:

curl 7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.16.2.3 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Protocols: tftp ftp telnet dict ldap ldaps http file https ftps scp sftp
Features: GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz

Cheers! :)

Reply

Ah, thanks for the note Jovan - very helpful! I hadn't seen this - but I'm sure others will have this exact same error :).

Cheers!

Reply

there what "shopping cart" project were you referring to at -2:20?

Reply

Yo azeem!

It's not a "shopping cart project", but instead, a "ShoppingCart" class (and shoppping_cart service) that I pre-configured in our project before starting the tutorial :). If you check out the start code, you'll find the class in src/AppBundle/Store/ShoppingCart.php, and the service is configured in services.yml. I didn't want to take the time to setup a shopping cart, so I did it before the tutorial so we could use it.

Does that help? Let me know!

Cheers!

Reply

Hey Dany,

As you can see from logs, your form is submitting to the wrong page. Instead of submitting to the "/charge" page you should submit form to the "/checkout", because in this screencast the form handling in CheckoutAction().

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
    }
}