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.

Add the Subscription to your Cart

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.

We can already add products to our cart... but a user should also be able to click these fancy buttons and add a subscription to their cart.

Open up OrderController: the home for the checkout and shopping cart magic. I've already started a new page called addSubscriptionToCartAction():

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 27
/**
* @Route("/cart/subscription/{planId}", name="order_add_subscription_to_cart")
*/
public function addSubscriptionToCartAction($planId)
{
// todo - add the subscription plan to the cart!
}
... lines 35 - 96
}
... lines 98 - 99

When we're done, if the user goes to /cart/subscription/farmer_brent_monthly, this should put that into the cart.

First, hook up the buttons to point here. The template for this page lives at app/Resources/views/product/pricing.html.twig:

... lines 1 - 3
{% block body %}
<div class="container">
... lines 7 - 10
<div class="row">
<div class="col-md-6">
<div class="price-square">
<p class="pricing-length-header monthly text-center">Farmer Brent</p>
<div class="pricing-info-padding">
<p class="price text-center">$99</p>
<p class="text-center">A fresh set of shears monthly along with our beloved after-shear in one of two fragrances!</p>
</div>
<a href="{{ path('order_add_subscription_to_cart', {
'planId': 'TODO'
}) }}" class="btn btn-warning center-block price-btn">
Shear Me!
</a>
</div>
</div>
<div class="col-md-6">
<div class="price-square price-square-yearly">
<p class="pricing-length-header yearly text-center">New Zealander</p>
<div class="pricing-info-padding">
<p class="price text-center">$199</p>
<p class="text-center">The perks of a Farmer Brent membership, but with a fresh bottle of conditioner and a comb each month!</p>
</div>
<a href="{{ path('order_add_subscription_to_cart', {
'planId': 'TODO'
}) }}" class="btn btn-warning center-block price-btn">
Get Shearing!
</a>
</div>
</div>
</div>
... lines 43 - 102
</div>
{% endblock %}

I started adding the link to this page, but left the plan ID blank. Fill 'em in! farmer_brent_monthly and then down below, new_zealander_monthly:

... lines 1 - 3
{% block body %}
<div class="container">
... lines 7 - 10
<div class="row">
<div class="col-md-6">
<div class="price-square">
<p class="pricing-length-header monthly text-center">Farmer Brent</p>
<div class="pricing-info-padding">
<p class="price text-center">$99</p>
<p class="text-center">A fresh set of shears monthly along with our beloved after-shear in one of two fragrances!</p>
</div>
<a href="{{ path('order_add_subscription_to_cart', {
'planId': 'farmer_brent_monthly'
}) }}" class="btn btn-warning center-block price-btn">
Shear Me!
</a>
</div>
</div>
<div class="col-md-6">
<div class="price-square price-square-yearly">
<p class="pricing-length-header yearly text-center">New Zealander</p>
<div class="pricing-info-padding">
<p class="price text-center">$199</p>
<p class="text-center">The perks of a Farmer Brent membership, but with a fresh bottle of conditioner and a comb each month!</p>
</div>
<a href="{{ path('order_add_subscription_to_cart', {
'planId': 'new_zealander_monthly'
}) }}" class="btn btn-warning center-block price-btn">
Get Shearing!
</a>
</div>
</div>
</div>
... lines 43 - 102
</div>
{% endblock %}

Go back to that page and refresh. The links look great!

Put the Plan in the Cart

Now back to the controller! In the first Stripe tutorial, we worked with a ShoppingCart class that I created for us... because it's not really that important. It basically allows you to store products and a subscription in the user's session, so that as they surf around, we know what they have in their cart.

But before we use that, first get an instance of our new SubscriptionHelper object with $subscriptionHelper = $this->get('subscription_helper'):

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 30
public function addSubscriptionToCartAction($planId)
{
$subscriptionHelper = $this->get('subscription_helper');
... lines 34 - 42
}
... lines 44 - 105
}
... lines 107 - 108

I already registered this as a service in Symfony:

... lines 1 - 5
services:
... lines 7 - 15
subscription_helper:
class: AppBundle\Subscription\SubscriptionHelper
autowire: true

Next, add $plan = $subscriptionHelper->findPlan() and pass it the $planId:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 30
public function addSubscriptionToCartAction($planId)
{
$subscriptionHelper = $this->get('subscription_helper');
$plan = $subscriptionHelper->findPlan($planId);
... lines 35 - 42
}
... lines 44 - 105
}
... lines 107 - 108

So this is nice: we give it the plan ID, and it gives us the corresponding, wonderful, SubscriptionPlan object:

... lines 1 - 4
class SubscriptionHelper
{
... lines 7 - 24
/**
* @param $planId
* @return SubscriptionPlan|null
*/
public function findPlan($planId)
{
foreach ($this->plans as $plan) {
if ($plan->getPlanId() == $planId) {
return $plan;
}
}
}
}

But if the $planId doesn't exist for some reason, throw $this->createNotFoundException() to cause a 404 page:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 30
public function addSubscriptionToCartAction($planId)
{
$subscriptionHelper = $this->get('subscription_helper');
$plan = $subscriptionHelper->findPlan($planId);
if (!$plan) {
throw $this->createNotFoundException('Bad plan id!');
}
... lines 39 - 42
}
... lines 44 - 105
}

Finally, add the plan to the cart, with $this->get('shopping_cart')->addSubscription() and pass it the plan ID:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 30
public function addSubscriptionToCartAction($planId)
{
$subscriptionHelper = $this->get('subscription_helper');
$plan = $subscriptionHelper->findPlan($planId);
if (!$plan) {
throw $this->createNotFoundException('Bad plan id!');
}
$this->get('shopping_cart')->addSubscription($planId);
... lines 41 - 42
}
... lines 44 - 105
}
... lines 107 - 108

And boom! Our cart knows about the subscription! Finally, send them to the checkout page with return $this->redirectToRoute('order_checkout') - that's the name of our checkoutAction route:

... lines 1 - 11
class OrderController extends BaseController
{
... lines 14 - 30
public function addSubscriptionToCartAction($planId)
{
$subscriptionHelper = $this->get('subscription_helper');
$plan = $subscriptionHelper->findPlan($planId);
if (!$plan) {
throw $this->createNotFoundException('Bad plan id!');
}
$this->get('shopping_cart')->addSubscription($planId);
return $this->redirectToRoute('order_checkout');
}
... lines 44 - 105
}
... lines 107 - 108

Adding the Subscription on the Checkout Page

Okay team, give it a try! Add the Farmer Brent plan. Bah! We need to login: use the pre-filled email and the password used by all sheep: breakingbaad.

Ok, this looks kinda right: the total is $99 because the ShoppingCart object knows about the subscription... but we haven't printed anything about the subscription in the cart table. So it looks weird.

Let's get this looking right: open the order/checkout.html.twig template and scroll down to the checkout table. We loop over the products and show the total, but never print anything about the subscription. Add a new if near the bottom: if cart - which is the ShoppingCart object - if cart.subscriptionPlan - which will be a SubscriptionPlan object or null:

... lines 1 - 59
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
... lines 64 - 66
<div class="col-xs-12 col-sm-6">
<table class="table table-bordered">
... lines 69 - 74
<tbody>
... lines 76 - 82
{% if cart.subscriptionPlan %}
... lines 84 - 87
{% endif %}
</tbody>
... lines 90 - 95
</table>
</div>
... lines 98 - 100
</div>
</div>
</div>
{% endblock %}

Then copy the <tr> from above and paste it here. Print out cart.subscriptionPlan.name:

... lines 1 - 59
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
... lines 64 - 66
<div class="col-xs-12 col-sm-6">
<table class="table table-bordered">
... lines 69 - 74
<tbody>
... lines 76 - 82
{% if cart.subscriptionPlan %}
<tr>
<th class="col-xs-6 checkout-product-name">Subscription: {{ cart.subscriptionPlan.name }}</th>
... line 86
</tr>
{% endif %}
</tbody>
... lines 90 - 95
</table>
</div>
... lines 98 - 100
</div>
</div>
</div>
{% endblock %}

That's why having this SubscriptionPlan object with all of those fields is really handy. Below, use cart.subscriptionPlan.price and add / month. And, whoops - I meant to use name on the first part, not price:

... lines 1 - 59
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
... lines 64 - 66
<div class="col-xs-12 col-sm-6">
<table class="table table-bordered">
... lines 69 - 74
<tbody>
... lines 76 - 82
{% if cart.subscriptionPlan %}
<tr>
<th class="col-xs-6 checkout-product-name">Subscription: {{ cart.subscriptionPlan.name }}</th>
<td class="col-xs-3">${{ cart.subscriptionPlan.price }} / month</td>
</tr>
{% endif %}
</tbody>
... lines 90 - 95
</table>
</div>
... lines 98 - 100
</div>
</div>
</div>
{% endblock %}

Let's give it a try now. It looks great! The plans are in Stripe, the plans are in our code, and you can add a plan to the cart. Time to checkout and create our first subscription.

Leave a comment!

6
Login or Register to join the conversation
Artur T. Avatar
Artur T. Avatar Artur T. | posted 3 years ago

Hi. $this->get('shopping_cart')->addSubscription($planId) method not exist.Pleas get this method. under comment or emai.

Reply

Hey Artur,

I just checked the project's code and ShoppingCart::addSubscription() method exists. Did you download the project from this page?

Cheers!

Reply
Djibril D. Avatar
Djibril D. Avatar Djibril D. | posted 5 years ago

You have requested a non-existent service "shopping_cart".

Reply

Hey Djibril D.

What Symfony version are you using? or did you just download the tutorial's code?

Reply
Djibril D. Avatar

Symfony 4

Reply

Hey Djibril D.!

Sorry about the issues! Basically, the tutorial was created in an older version of Symfony. If you download the "start" code from this page and code along, everything will work fine. But, if you use a new Symfony 4 project and put the code in that, a few things will not work.

You can find more info about this on my replies here: https://knpuniversity.userv...

See my original reply, and my other comment below. And if you have any questions at all, please let us know right here!

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.8
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "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", // v1.2.1
        "phpunit/phpunit": "^5.5", // 5.7.20
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.3.0
        "hautelook/alice-bundle": "^1.3", // v1.4.1
        "doctrine/data-fixtures": "^1.2" // v1.2.2
    }
}
userVoice