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.
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.
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.
Yo Thierno!
Well, that is weird - we've had some bad luck lately :). How is your checkout form built? Did you use an embedded (non-customized) checkout form from Stripe like we did originally in this tutorial (https://knpuniversity.com/s... or a custom checkout form like we do later https://knpuniversity.com/s... And what exactly doesn't show up (you said "data amount" - where *should* that be printing)?
Let me know - I'm sure we can hunt this down.
Cheers!
Thx Ryan the probleme was that my pay button had a strong tag in it so the event was propagated to the strong tag and that tag has no id to get the amount THX
Ahh, that makes sense. I wonder if e.currentTarget
would have gotten the correct element? I think it might have :). Anyways, glad you sorted it out!
Cheers!
// 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.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"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", // 1.1.1
"twig/twig": "^1.24.1", // v1.35.2
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.2
"hautelook/alice-bundle": "^1.3", // v1.3.1
"doctrine/data-fixtures": "^1.2" // v1.2.1
}
}
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