If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Imagine this: a sheep customer is so happy with the Farmer Brent subscription that they want to upgrade to the New Zealander! Awesome! Amazing! But also... currently impossible.
Time to fix that. Plan upgrades can be complex, because someone needs to calculate how much of the current month has been used and prorate funds towards the upgrade. Stripe's documentation talks about this. I'll guide you through everything, but this section is worth a read.
Buy a new Farmer Brent Subscription, and then head to the account page.
Let's focus on the plan upgrade user interface first. Here, I need to see which plan I'm currently subscribed to and a button to upgrade to the other plan.
Open the account.html.twig
template and ProfileController
.
Add a new variable: $currentPlan = null
. Then, only if $this->getUser()->hasActiveSubscription()
,
set $currentPlan = $this->get('subscription_helper')->findPlan()
passing that
$this->getUser()->getSubscription()->getStripePlanId()
:
... lines 1 - 12 | |
class ProfileController extends BaseController | |
{ | |
... lines 15 - 17 | |
public function accountAction() | |
{ | |
$currentPlan = null; | |
if ($this->getUser()->hasActiveSubscription()) { | |
$currentPlan = $this->get('subscription_helper') | |
->findPlan($this->getUser()->getSubscription()->getStripePlanId()); | |
} | |
... lines 25 - 30 | |
} | |
... lines 32 - 106 | |
} |
The findPlan()
method will give us a fancy SubscriptionPlan
object.
Pass a new currentPlan
variable into the template, set to $currentPlan
:
... lines 1 - 12 | |
class ProfileController extends BaseController | |
{ | |
... lines 15 - 17 | |
public function accountAction() | |
{ | |
$currentPlan = null; | |
if ($this->getUser()->hasActiveSubscription()) { | |
$currentPlan = $this->get('subscription_helper') | |
->findPlan($this->getUser()->getSubscription()->getStripePlanId()); | |
} | |
return $this->render('profile/account.html.twig', [ | |
... lines 27 - 28 | |
'currentPlan' => $currentPlan | |
]); | |
} | |
... lines 32 - 106 | |
} |
Then in the template, find the "Active Subscription" spot, and print currentPlan.name
:
... lines 1 - 18 | |
{% block body %} | |
<div class="nav-space"> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-xs-6"> | |
... lines 24 - 39 | |
<table class="table"> | |
<tbody> | |
<tr> | |
<th>Subscription</th> | |
<td> | |
{% if app.user.hasActiveSubscription %} | |
{% if app.user.subscription.isCancelled %} | |
... lines 47 - 49 | |
{% else %} | |
{{ currentPlan.name }} | |
<span class="label label-success">Active</span> | |
{% endif %} | |
... lines 55 - 56 | |
{% endif %} | |
</td> | |
</tr> | |
... lines 60 - 83 | |
</tbody> | |
</table> | |
</div> | |
... lines 87 - 95 | |
</div> | |
</div> | |
</div> | |
{% endblock %} | |
... lines 100 - 101 |
Refresh the page! Great! Step 1 done: we have the "Farmer Brent" plan.
Now, step two: add an upgrade button that mentions the plan they could switch to. Since we only have 2 plans, it's pretty simple: if they're on the Farmer Brent, we want to allow them to upgrade to the New Zealander. And if they're on the New Zealander, we should let them downgrade to the Farmer Brent.
To find the other plan, open SubscriptionHelper
and add a new public function
called findPlanToChangeTo
with a $currentPlanId
argument:
... lines 1 - 8 | |
class SubscriptionHelper | |
{ | |
... lines 11 - 45 | |
/** | |
* @param $currentPlanId | |
* @return SubscriptionPlan | |
*/ | |
public function findPlanToChangeTo($currentPlanId) | |
{ | |
if (strpos($currentPlanId, 'farmer_brent') !== false) { | |
$newPlanId = str_replace('farmer_brent', 'new_zealander', $currentPlanId); | |
} else { | |
$newPlanId = str_replace('new_zealander', 'farmer_brent', $currentPlanId); | |
} | |
return $this->findPlan($newPlanId); | |
} | |
... lines 60 - 106 | |
} |
I'll paste in the logic: it's kind of silly, but it gets the job done. I'm using
str_replace
instead of something simpler, because in a few minutes, we're going
to add yearly plans, and I still want this function to... um... function.
Back to the controller! Add another variable: $otherPlan = null
. Then,
$otherPlan = $this->get('subscription_helper')->findPlanToChangeTo()
and pass
it $currentPlan->getPlanId()
. Pass this into the template as an otherPlan
variable:
... lines 1 - 12 | |
class ProfileController extends BaseController | |
{ | |
... lines 15 - 17 | |
public function accountAction() | |
{ | |
$currentPlan = null; | |
$otherPlan = null; | |
if ($this->getUser()->hasActiveSubscription()) { | |
$currentPlan = $this->get('subscription_helper') | |
->findPlan($this->getUser()->getSubscription()->getStripePlanId()); | |
$otherPlan = $this->get('subscription_helper') | |
->findPlanToChangeTo($currentPlan->getPlanId()); | |
} | |
return $this->render('profile/account.html.twig', [ | |
... lines 31 - 32 | |
'currentPlan' => $currentPlan, | |
'otherPlan' => $otherPlan, | |
]); | |
} | |
... lines 37 - 111 | |
} |
There, after the "Active" label, add a button with some classes: a few for styling,
one to float right and one - js-change-plan-button
- that we'll use in a minute
via JavaScript. Make the text: "Change to" and then otherPlan.name
:
... lines 1 - 24 | |
{% block body %} | |
<div class="nav-space"> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-xs-6"> | |
... lines 30 - 45 | |
<table class="table"> | |
<tbody> | |
<tr> | |
<th>Subscription</th> | |
<td> | |
{% if app.user.hasActiveSubscription %} | |
{% if app.user.subscription.isCancelled %} | |
... lines 53 - 55 | |
{% else %} | |
{{ currentPlan.name }} | |
<span class="label label-success">Active</span> | |
<button class="btn btn-xs btn-link pull-right js-change-plan-button" data-plan-id="{{ otherPlan.planId }}" data-plan-name="{{ otherPlan.name }}"> | |
Change to {{ otherPlan.name }} | |
</button> | |
{% endif %} | |
... lines 65 - 66 | |
{% endif %} | |
</td> | |
</tr> | |
... lines 70 - 93 | |
</tbody> | |
</table> | |
</div> | |
... lines 97 - 105 | |
</div> | |
</div> | |
</div> | |
{% endblock %} | |
... lines 110 - 111 |
Oh, and add one more attribute: data-plan-name
and print otherPlan.name
. We'll
read that attribute in JavaScript.
In fact, let's play with the JavaScript right now: copy the js-change-plan-button
class and find the JavaScript block at the top of this file. Use jQuery to locate
that element, then on click
, add a callback. Start with the always-in-style
e.preventDefault()
:
... lines 1 - 2 | |
{% block javascripts %} | |
... lines 4 - 7 | |
<script> | |
jQuery(document).ready(function() { | |
... lines 10 - 15 | |
$('.js-change-plan-button').on('click', function(e) { | |
e.preventDefault(); | |
... lines 18 - 19 | |
}) | |
}); | |
</script> | |
{% endblock %} | |
... lines 24 - 111 |
Start really simple: we'll use a library that I already installed called
Sweet Alerts. Call swal()
and pass a message Loading Plan Details
:
... lines 1 - 2 | |
{% block javascripts %} | |
... lines 4 - 7 | |
<script> | |
jQuery(document).ready(function() { | |
... lines 10 - 15 | |
$('.js-change-plan-button').on('click', function(e) { | |
e.preventDefault(); | |
swal('Loading Plan Details...'); | |
}) | |
}); | |
</script> | |
{% endblock %} | |
... lines 24 - 111 |
Ok, let's see what this Sweet Alerts thing looks like! Refresh that page! Nice! Click the "Change to New Zealander" link. This is Sweet Alert. It's cute, it's easy, and it'll help us do our job.
Because next, we need to do some serious work: we need to calculate how much we should charge the user to upgrade from the Farmer Brent to the New Zealander, and then show it to the user. That's tricky, because the user is probably in the middle of the month that they've already paid for, so they deserve some credits!
Thankfully, Stripe is going to be a champ and help us out.
"Houston: no signs of life"
Start the conversation!
// 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
}
}