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.
Stripe 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.
The first two important objects in Stripe are Charge
and Customer
.
Let's talk about the third important object: Invoice
. Here's the idea: right
now, we simply charge the Customer
. But instead of doing that, we could add
invoice items to the Customer
, create an Invoice
for those items, and then pay
that Invoice
.
To the user, this feels the same. But in Stripe, instead of having a charge, you
will have an Invoice
full of invoice items, and a charge to pay that invoice. Why
do we care? Well first, it let's you have detailed line-items - like two separate
items if our customer orders 2 products.
And second, invoices are central to handling subscriptions. In fact, you'll find
the Invoice
API documentation under the subscription area. But, it can be used for
any charges.
Creating & Paying the Invoice
Let's hook this up. First, instead of creating a Stripe Charge
, create a Stripe
InvoiceItem
:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 31 | |
public function checkoutAction(Request $request) | |
{ | |
// ... lines 34 - 35 | |
if ($request->isMethod('POST')) { | |
// ... lines 37 - 59 | |
\Stripe\InvoiceItem::create(array( | |
"amount" => $this->get('shopping_cart')->getTotal() * 100, | |
"currency" => "usd", | |
"customer" => $user->getStripeCustomerId(), | |
"description" => "First test charge!" | |
)); | |
// ... lines 66 - 75 | |
} | |
// ... lines 77 - 83 | |
} | |
} |
But all the data is the same.
Below that, add $invoice = \Stripe\Invoice::create()
and pass that an array with
customer
set to $user->getStripeCustomerId()
:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 31 | |
public function checkoutAction(Request $request) | |
{ | |
// ... lines 34 - 35 | |
if ($request->isMethod('POST')) { | |
// ... lines 37 - 59 | |
\Stripe\InvoiceItem::create(array( | |
"amount" => $this->get('shopping_cart')->getTotal() * 100, | |
"currency" => "usd", | |
"customer" => $user->getStripeCustomerId(), | |
"description" => "First test charge!" | |
)); | |
$invoice = \Stripe\Invoice::create(array( | |
"customer" => $user->getStripeCustomerId() | |
)); | |
// ... lines 69 - 75 | |
} | |
// ... lines 77 - 83 | |
} | |
} |
Finally, add $invoice->pay()
:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 31 | |
public function checkoutAction(Request $request) | |
{ | |
// ... lines 34 - 35 | |
if ($request->isMethod('POST')) { | |
// ... lines 37 - 65 | |
$invoice = \Stripe\Invoice::create(array( | |
"customer" => $user->getStripeCustomerId() | |
)); | |
// guarantee it charges *right* now | |
$invoice->pay(); | |
// ... lines 71 - 75 | |
} | |
// ... lines 77 - 83 | |
} | |
} |
Let's break this down. The first part creates an InvoiceItem
in Stripe, but nothing
is charged yet. Then, when you create an Invoice
, Stripe looks for all unpaid invoice
items and attaches them to that Invoice. The last line charges the customer to pay
that invoice's balance.
Usually, when you create an Invoice
, Stripe will charge the customer immediately.
But, if you have web hooks setup - something we'll talk about in the second course -
that will delay charging the user by 1 hour. Calling ->pay()
guarantees that this
happens right now.
Ok, go back and try this out. Find a wonderful and high-quality product, and add it to the cart. Checkout using your favorite fake credit card and fake information.
Looks like it worked! And since this user already is a Stripe customer, refresh
that customer's page in Stripe. Check this out! We have two payments and we can
see the Invoice
. If you click that, the Invoice
has 1 line item and a related
Charge
object.
Tip
If all charges belong to an invoice, you can use Stripe's API to retrieve your customer's past invoices and render them as a receipt.
One InvoiceItem per Product
This now gives us more flexibility. Since sheep love to shop, they'll often buy
multiple products. In fact, let's go buy some shears, and some Sheerly Conditioned.
If we checked out right now, this would show up as one giant line item for $106.00
on the Invoice
. We can do better than that.
In OrderController
, around the InvoiceItem
part, add a foreach
, over
$this->get('shopping_cart')->getProducts() as $product
. In other words, let's loop
over all the actual products in our cart and create a separate InvoiceItem
for
each. All we need to do is change the amount
to be: $product->getPrice() * 100
.
We can even improve the description: set it to $product->getName()
:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 31 | |
public function checkoutAction(Request $request) | |
{ | |
// ... lines 34 - 35 | |
if ($request->isMethod('POST')) { | |
// ... lines 37 - 59 | |
foreach ($this->get('shopping_cart')->getProducts() as $product) { | |
\Stripe\InvoiceItem::create(array( | |
"amount" => $product->getPrice() * 100, | |
"currency" => "usd", | |
"customer" => $user->getStripeCustomerId(), | |
"description" => $product->getName() | |
)); | |
} | |
// ... lines 68 - 77 | |
} | |
// ... lines 79 - 85 | |
} | |
} |
Now, if we eventually send the user a receipt, it's going to be very easy to look on this Stripe invoice and see exactly what we charged them for.
Test time! Put in our awesome fake information, hit ENTER... and no errors.
In Stripe, click back to the customer page and find the new invoice - for $106
.
Click on that. Yes! 2 crystal clear invoice line items.
So yes, you can just charge customers. But if you create an Invoice
with detailed
line items, you're going to have a much better accounting system in the long run.
hi ryan i use stripe checkout and it wooeks fine on desktop but on mobile the data amount is not showing can you help please