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.
Upgrade: Processing the Upcoming Invoice
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Head over to the account template. When the user clicks upgrade, we need to make
an AJAX call to our new endpoint. To get that URL, find the button and add a new
attribute: data-preview-url
set to path('account_preview_plan_change')
, passing
a planId
wildcard set to otherPlan.planId
:
// ... lines 1 - 33 | |
{% block body %} | |
<div class="nav-space"> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-xs-6"> | |
// ... lines 39 - 54 | |
<table class="table"> | |
<tbody> | |
<tr> | |
<th>Subscription</th> | |
<td> | |
{% if app.user.hasActiveSubscription %} | |
{% if app.user.subscription.isCancelled %} | |
// ... lines 62 - 64 | |
{% else %} | |
// ... lines 66 - 69 | |
<button class="btn btn-xs btn-link pull-right js-change-plan-button" | |
data-preview-url="{{ path('account_preview_plan_change', {'planId': otherPlan.planId}) }}" | |
data-plan-name="{{ otherPlan.name }}" | |
> | |
Change to {{ otherPlan.name }} | |
</button> | |
{% endif %} | |
// ... lines 77 - 78 | |
{% endif %} | |
</td> | |
</tr> | |
// ... lines 82 - 105 | |
</tbody> | |
</table> | |
</div> | |
// ... lines 109 - 117 | |
</div> | |
</div> | |
</div> | |
{% endblock %} | |
// ... lines 122 - 123 |
Cool! Copy that new attribute name and go back up to the JavaScript section. Let's
read that attribute: var previewUrl = $(this).data('preview-url')
. And while
we're here, create a planName
variable set to $(this).data('plan-name')
:
// ... 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...'); | |
var previewUrl = $(this).data('preview-url'); | |
var planName = $(this).data('plan-name'); | |
// ... lines 23 - 28 | |
}) | |
}); | |
</script> | |
{% endblock %} | |
// ... lines 33 - 123 |
Now, make that AJAX call! I'll use $.ajax()
with url
set to previewUrl
. Chain
a .done()
to add a success function with a data
argument. And just to try
things out, open sweet alert with a message: Total $
then data.total
, since the
endpoint returns that field:
// ... 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...'); | |
var previewUrl = $(this).data('preview-url'); | |
var planName = $(this).data('plan-name'); | |
$.ajax({ | |
url: previewUrl | |
}).done(function(data) { | |
swal('Total $'+data.total); | |
}); | |
}) | |
}); | |
</script> | |
{% endblock %} | |
// ... lines 33 - 123 |
Ok team, try that out. Refresh the account page and click "Change to New Zealander". Bam! Total $50!
Using the Upcoming Invoice
With the frontend somewhat functional, let's finish the logic in our endpoint.
At the bottom, Symfony keeps a list of the AJAX requests. Click the 4f4
sha link
to get more information about our AJAX request. Then, click the Debug link on the
left.
In the last chapter, we dumped the upcoming \Stripe\Invoice
object that we got
from the Stripe API. This is it! It looks a little funny, but the data is hiding
under the _values
property, and it holds a couple of really interesting things.
Upcoming Invoice Line Items
First, check out amount_due
, and remember, everything is stored in cents, not
dollars. This is the amount we'll show to the user. But if it seems a little too
high, you're right. Keep watching.
Second, the invoice line items can be found under the lines
key. And there are
three.
The first line item is negative: its a credit for any unused time on your current plan. If you're half-way through a month, then the second half should be applied as a credit. This is that credit. Since we just signed up a few minutes ago, this is just slightly less than the full price of $99.
The second line item is a charge for the new plan, for however much time is left in the month. Again, if we're upgrading half-way through the month, I should only need to pay for half of the new plan in order to use it for the last half of the month.
The third line item, well, this is where things get ugly. This is a charge for a full month on the new plan: $199.
What? Why is that here? Why would I pay for half of the month of the New Zealander plan and also for a full month?
Here's what's going on: when a customer upgrades, Stripe does not charge them anything immediately. Instead, Stripe allows you to switch, but then, at the end of the month, it will charge you for the partial, prorated month you just used, plus the full next month, minus the partial-month refund for your original plan.
Phew! That's why you see three line items: the first two for adjusting to the new plan for part of the month, plus the cost for the full-price renewal.
Charging Immediately for an Upgrade
Honestly, this feels weird to me. So let's do something better: let's charge the customer immediately for the plan price change, and then let them pay for the normal, full-month renewal next month. This is totally possible to do.
But that means, to show the user the amount they will be charged right now, we need
to read the amount_due
value and then subtract the full price of the plan,
to remove the extra line item.
In ProfileController
, add a new variable $total
set to $stripeInvoice->amount_due
:
// ... lines 1 - 13 | |
class ProfileController extends BaseController | |
{ | |
// ... lines 16 - 116 | |
public function previewPlanChangeAction($planId) | |
{ | |
$plan = $this->get('subscription_helper') | |
->findPlan($planId); | |
$stripeInvoice = $this->get('stripe_client') | |
->getUpcomingInvoiceForChangedSubscription( | |
$this->getUser(), | |
$plan | |
); | |
// contains the pro-rations *plus* the next cycle's amount | |
$total = $stripeInvoice->amount_due; | |
// ... lines 130 - 134 | |
} | |
} |
Add a comment above - this stuff is confusing, so let's leave some notes. Then, correct
the total by subtracting $plan->getPrice() * 100
to convert into cents - our price
is stored in dollars:
// ... lines 1 - 13 | |
class ProfileController extends BaseController | |
{ | |
// ... lines 16 - 116 | |
public function previewPlanChangeAction($planId) | |
{ | |
$plan = $this->get('subscription_helper') | |
->findPlan($planId); | |
$stripeInvoice = $this->get('stripe_client') | |
->getUpcomingInvoiceForChangedSubscription( | |
$this->getUser(), | |
$plan | |
); | |
// contains the pro-rations *plus* the next cycle's amount | |
$total = $stripeInvoice->amount_due; | |
// subtract plan price to *remove* next the next cycle's total | |
$total -= $plan->getPrice() * 100; | |
// ... lines 133 - 134 | |
} | |
} |
Then, return $total / 100
in the JSON:
// ... lines 1 - 13 | |
class ProfileController extends BaseController | |
{ | |
// ... lines 16 - 116 | |
public function previewPlanChangeAction($planId) | |
{ | |
$plan = $this->get('subscription_helper') | |
->findPlan($planId); | |
$stripeInvoice = $this->get('stripe_client') | |
->getUpcomingInvoiceForChangedSubscription( | |
$this->getUser(), | |
$plan | |
); | |
// contains the pro-rations *plus* the next cycle's amount | |
$total = $stripeInvoice->amount_due; | |
// subtract plan price to *remove* next the next cycle's total | |
$total -= $plan->getPrice() * 100; | |
return new JsonResponse(['total' => $total/100]); | |
} | |
} |
Let's try it guys: go back and refresh.
Click "Change to New Zealander". Ok, $99.93
- that looks about right. Remember,
the upgrade should cost about $100, but since we've been using the old plan for
a few minutes, the true cost should be slightly lower.
Finishing up the JS
Ok! It's time to execute this upgrade! To save us some time, I'll paste some JavaScript into the AJAX success function:
// ... lines 1 - 2 | |
{% block javascripts %} | |
// ... lines 4 - 7 | |
<script> | |
jQuery(document).ready(function() { | |
// ... lines 10 - 15 | |
$('.js-change-plan-button').on('click', function(e) { | |
// ... lines 17 - 23 | |
$.ajax({ | |
url: previewUrl | |
}).done(function(data) { | |
var message; | |
if (data.total > 0) { | |
message = 'You will be charged $'+data.total +' immediately'; | |
} else { | |
message = 'You will have a balance of $'+(Math.abs(data.total))+' that will be automatically applied to future invoices!'; | |
} | |
swal({ | |
title: 'Change to '+planName, | |
text: message, | |
type: "info", | |
showCancelButton: true, | |
closeOnConfirm: false, | |
showLoaderOnConfirm: true | |
}, function () { | |
// todo - actually change the plan! | |
}); | |
}); | |
}) | |
}); | |
</script> | |
{% endblock %} | |
// ... lines 49 - 139 |
This first display how much we will charge the user. And check this out: it could be positive, meaning we'll charge them, or negative for a downgrade, meaning they'll get an account credit that will automatically be used for future charges.
Finally, this shows the user one last alert to confirm the change. If they click "Ok", the last callback will be executed. And it'll be our job to send one more AJAX call back to the server to finally change their plan.
Let's do it!
Hey! In case we have a plan that has price ranges (depending on the units). Imagine a user who has a subscription with 150 units. He wants to modify his subscription and take only 100 units but for the same plan (or else for another plan with units, or just change plan for another plan with a fixed price, without quantity), in this case we must specify the quantity in our function, what that it happens.
So, should I have something that looks like this:
Now, in my controller, on the <b>previewPlanChange function</b>
It works if I increase the quantity. On the other hand, if I decide to decrease the quantity, then, systematically, the "amount_due" will be equal to 0 while the "total" will have the good value taking into account the prorations.
The point is if I change :<b>$total = $stripeInvoice->amount_due;</b> by<b>$total = $stripeInvoice->total;</b>
I'm not sure if it's "correct".
Note that I only have this problem in the preview of the invoice. If I confirm, Stripe will do the prorations, the calculations according to what I have to pay, what I have left in my balance, etc. The problem really lies in the preview of the invoice when changing plans