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.
Webhook: Payment Failed!
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.
Ok, there's just one more webhook we need to worry about, and it's the easiest
one: invoice.payment_failed
. Send a test webhook for this event.
Refresh RequestBin to check it out.
This webhook type is important for only one reason: to send your user an email so that they know we're having problems charging their card. That's it! We're already using a different webhook to actually cancel their subscription if the failures continue.
This has almost the same body as the invoice.payment_succeeded
event: the embedded
object is an invoice
and if that invoice is related to a subscription, it has a
subscription
property.
That means that in WebhookController
, this is a pretty easy one to handle. Add
a new case for invoice.payment_failed
:
// ... lines 1 - 8 | |
class WebhookController extends BaseController | |
{ | |
// ... lines 11 - 13 | |
public function stripeWebhookAction(Request $request) | |
{ | |
// ... lines 16 - 31 | |
switch ($stripeEvent->type) { | |
// ... lines 33 - 50 | |
case 'invoice.payment_failed': | |
// ... lines 52 - 62 | |
break; | |
// ... lines 64 - 66 | |
} | |
// ... lines 68 - 69 | |
} | |
// ... lines 71 - 90 | |
} |
Then, start just like before: grab the $stripeSubscriptionId
. Then, add an if
statement - just in case this invoice has no subscription:
// ... lines 1 - 8 | |
class WebhookController extends BaseController | |
{ | |
// ... lines 11 - 13 | |
public function stripeWebhookAction(Request $request) | |
{ | |
// ... lines 16 - 31 | |
switch ($stripeEvent->type) { | |
// ... lines 33 - 50 | |
case 'invoice.payment_failed': | |
$stripeSubscriptionId = $stripeEvent->data->object->subscription; | |
if ($stripeSubscriptionId) { | |
// ... lines 55 - 60 | |
} | |
break; | |
// ... lines 64 - 66 | |
} | |
// ... lines 68 - 69 | |
} | |
// ... lines 71 - 90 | |
} |
What to do when a Payment Fails?
Earlier, we talked about what happens when a payment fails. It depends on your Subscription settings in Stripe, but ultimately, Stripe will attempt to charge the card a few times, and then cancel the subscription.
You could send your user an email each time Stripe tries to charge their card and fails, but that'll probably be a bit annoying. So, I like to send an email only after the first attempt fails.
To know if this webhook is being fired after the first, second or third attempt,
use a field called attempt_count
. If this equals one, send an email. In the
controller, add if $stripeEvent->data->object->attempt_count == 1
, then send them
an email. Well, I'll leave that step to you guys:
// ... lines 1 - 8 | |
class WebhookController extends BaseController | |
{ | |
// ... lines 11 - 13 | |
public function stripeWebhookAction(Request $request) | |
{ | |
// ... lines 16 - 31 | |
switch ($stripeEvent->type) { | |
// ... lines 33 - 50 | |
case 'invoice.payment_failed': | |
$stripeSubscriptionId = $stripeEvent->data->object->subscription; | |
if ($stripeSubscriptionId) { | |
// ... lines 55 - 56 | |
if ($stripeEvent->data->object->attempt_count == 1) { | |
// ... line 58 | |
// todo - send the user an email about the problem | |
} | |
} | |
break; | |
// ... lines 64 - 66 | |
} | |
// ... lines 68 - 69 | |
} | |
// ... lines 71 - 90 | |
} |
If you need to know which user the subscription belongs to, first fetch the
Subscription
from the database by using our findSubscription()
method. Then,
add $user = $subscription->getUser()
:
// ... lines 1 - 8 | |
class WebhookController extends BaseController | |
{ | |
// ... lines 11 - 13 | |
public function stripeWebhookAction(Request $request) | |
{ | |
// ... lines 16 - 31 | |
switch ($stripeEvent->type) { | |
// ... lines 33 - 50 | |
case 'invoice.payment_failed': | |
$stripeSubscriptionId = $stripeEvent->data->object->subscription; | |
if ($stripeSubscriptionId) { | |
$subscription = $this->findSubscription($stripeSubscriptionId); | |
if ($stripeEvent->data->object->attempt_count == 1) { | |
$user = $subscription->getUser(); | |
// todo - send the user an email about the problem | |
} | |
} | |
break; | |
// ... lines 64 - 66 | |
} | |
// ... lines 68 - 69 | |
} | |
// ... lines 71 - 90 | |
} |
I like this webhook - it's easy! And actually, we're done with webhooks! Except for preventing replay attacks... which is important, but painless.
Hey guys,
maybe you can help me :) At the moment I do setup my webhooks, and I'm following this lecture.
I understand the logic that we query only for invoices where actually a subscription was involved.
But why do we look for $stripeEvent->data->object->subscription?
In Stripe I created a test webhook and it returned at this field a value of null. Contrary, it returned within the invoice items array a subscription item.
This is just an excerpt of the line item's array
And further below in the invoice event:
When I look into the API https://stripe.com/docs/api#invoice_object I don't understand the field description of event.data.object.subscription: 'The subscription that this invoice was prepared for, if any.'
According to our setup the subscription filter would not trigger, although within the line Items there is a subscription.