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.

Sweet Invoices

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

Ha! We've made it! We've survived our subscription payment setup! So, umm, go celebrate: eat some cake! Sing a song! Or, like we'll do, do some accounting.

Because our last topic is about that: your users will need to see a receipt after purchase. And good news: we've setup our system so that every charge is done by creating an Invoice in Stripe's system. That'll make our life easy.

Store Invoices Locally?

In fact, all the information we need to render a receipt is already stored in Stripe. So, you could create a local invoices database table and store details there... or, you can take a shortcut and use Stripe's API to fetch invoice data whenever you actually need it. Let's do that.

Fetch all Paid Invoices

Open up StripeClient. At the bottom, add a new public function findPaidInvoices() with a User argument:

... lines 1 - 4
use AppBundle\Entity\User;
... lines 6 - 8
class StripeClient
{
... lines 11 - 206
public function findPaidInvoices(User $user)
{
... lines 209 - 222
}
}

Here's the idea: we'll use Stripe's API to find all Invoices for a customer, but then filter those to only return invoices that were paid. That will remove some garbage invoices the user shouldn't see: like invoices for payments that failed and then were closed immediately.

Start with: $allInvoices = \Stripe\Invoice::all() and pass that an array with a customer key set to $user->getStripeCustomerId():

... lines 1 - 8
class StripeClient
{
... lines 11 - 206
public function findPaidInvoices(User $user)
{
$allInvoices = \Stripe\Invoice::all([
'customer' => $user->getStripeCustomerId()
]);
... lines 212 - 222
}
}

Next - and this will look a little weird at first - create an $iterator variable set to $allInvoices->autoPagingIterator():

... lines 1 - 8
class StripeClient
{
... lines 11 - 206
public function findPaidInvoices(User $user)
{
$allInvoices = \Stripe\Invoice::all([
'customer' => $user->getStripeCustomerId()
]);
$iterator = $allInvoices->autoPagingIterator();
... lines 214 - 222
}
}

This is actually really cool: if the user has a lot of invoices, then Stripe will paginate your results. But with the iterator, it will automatically make new API calls behind-the-scenes, allowing us to loop over every invoice, no matter how many there are.

Let's do that: start with $invoices = array(). Then foreach over $iterator as $invoice:

... lines 1 - 8
class StripeClient
{
... lines 11 - 206
public function findPaidInvoices(User $user)
{
$allInvoices = \Stripe\Invoice::all([
'customer' => $user->getStripeCustomerId()
]);
$iterator = $allInvoices->autoPagingIterator();
$invoices = [];
foreach ($iterator as $invoice) {
... lines 217 - 219
}
... lines 221 - 222
}
}

Very simply, we want to know if this invoice is paid. If $invoice->paid, then add this to the $invoices array. Finally, return those paid $invoices at the bottom:

... lines 1 - 8
class StripeClient
{
... lines 11 - 206
public function findPaidInvoices(User $user)
{
$allInvoices = \Stripe\Invoice::all([
'customer' => $user->getStripeCustomerId()
]);
$iterator = $allInvoices->autoPagingIterator();
$invoices = [];
foreach ($iterator as $invoice) {
if ($invoice->paid) {
$invoices[] = $invoice;
}
}
return $invoices;
}
}

Heck, let's over-achieve by adding some PHPDoc that shows that this method return an array of \Stripe\Invoice objects:

... lines 1 - 8
class StripeClient
{
... lines 11 - 202
/**
* @param User $user
* @return \Stripe\Invoice[]
*/
public function findPaidInvoices(User $user)
{
... lines 209 - 222
}
}

Listing all Invoices

Thanks to this function, on the account page, at the bottom, we'll print a list of all the Customer's invoices. Eventually, they'll be able to click each invoice to see all the details.

Start in ProfileController... all the way at the top: this method renders the account page. Fetch the invoices with $invoices = $this->get('stripe_client')->findPaidInvoices() with the current user. Pass that as a new variable into the template:

... lines 1 - 14
class ProfileController extends BaseController
{
... lines 17 - 19
public function accountAction()
{
... lines 22 - 35
$invoices = $this->get('stripe_client')
->findPaidInvoices($this->getUser());
return $this->render('profile/account.html.twig', [
... lines 40 - 44
'invoices' => $invoices
]);
}
... lines 48 - 177
}

Now inside the template, find the bottom of the table. Add a new row, and title it Invoices:

... lines 1 - 67
{% block body %}
<div class="nav-space">
<div class="container">
<div class="row">
<div class="col-xs-6">
... lines 73 - 88
<table class="table">
<tbody>
... lines 91 - 148
<tr>
<th>Invoices</th>
<td>
... lines 152 - 160
</td>
</tr>
</tbody>
</table>
</div>
... lines 166 - 174
</div>
</div>
</div>
{% endblock %}
... lines 179 - 180

Next, create a list and then loop with for invoice in invoices. Add the endfor. Create an anchor tag, but keep the href empty for now - we don't have an invoice "show" page yet. Add some classes to make this look a little fancy:

... lines 1 - 67
{% block body %}
<div class="nav-space">
<div class="container">
<div class="row">
<div class="col-xs-6">
... lines 73 - 88
<table class="table">
<tbody>
... lines 91 - 148
<tr>
<th>Invoices</th>
<td>
<div class="list-group">
{% for invoice in invoices %}
<a href="" class="list-group-item">
... lines 155 - 157
</a>
{% endfor %}
</div>
</td>
</tr>
</tbody>
</table>
</div>
... lines 166 - 174
</div>
</div>
</div>
{% endblock %}
... lines 179 - 180

So let's see, the user might be looking for a specific invoice, so let's print its date. Check out the Invoice API. Hey! There's actually a date field, which is a UNIX timestamp. Print invoice.date and then pipe that through the date filter with Y-m-d:

... lines 1 - 67
{% block body %}
<div class="nav-space">
<div class="container">
<div class="row">
<div class="col-xs-6">
... lines 73 - 88
<table class="table">
<tbody>
... lines 91 - 148
<tr>
<th>Invoices</th>
<td>
<div class="list-group">
{% for invoice in invoices %}
<a href="" class="list-group-item">
Date: {{ invoice.date|date('Y-m-d') }}
... lines 156 - 157
</a>
{% endfor %}
</div>
</td>
</tr>
</tbody>
</table>
</div>
... lines 166 - 174
</div>
</div>
</div>
{% endblock %}
... lines 179 - 180

Next, add a span label, float it right, and inside, add the amount: $ then print invoice.amount_due / 100 to convert it from cents to dollars:

... lines 1 - 67
{% block body %}
<div class="nav-space">
<div class="container">
<div class="row">
<div class="col-xs-6">
... lines 73 - 88
<table class="table">
<tbody>
... lines 91 - 148
<tr>
<th>Invoices</th>
<td>
<div class="list-group">
{% for invoice in invoices %}
<a href="" class="list-group-item">
Date: {{ invoice.date|date('Y-m-d') }}
<span class="label label-success pull-right">${{ invoice.amount_due/100 }} </span>
</a>
{% endfor %}
</div>
</td>
</tr>
</tbody>
</table>
</div>
... lines 166 - 174
</div>
</div>
</div>
{% endblock %}
... lines 179 - 180

The amount_due field is what the user should have actually been charged, after accounting for coupons or a positive account balance.

Try things out so far: head back to the account page and refresh! Bam! Here's our long invoice list. Next, let's give each invoice its own detailed display page, complete with invoice items, discounts and anything else that might have happened on that invoice.

Leave a comment!

0
Login or Register to join the conversation
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