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.

Add the Subscription to your Cart

Share this awesome video!

|

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:

19 lines | app/config/services.yml
// ... 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.