Handling laravel failed payment stripe subscription

Handling laravel failed payment stripe subscription

How to handle subscription failed payments on stripe?

After days of monitoring for handling laravel failed payment on stripe subscription, I found out the better way how to handle a subscription to update user status in the website from the status of stripe’s customer using webhooks.

This will set the status of the website user into canceled when customer.subscription.deleted event fired. Others, used the customer.subscription.updated which is good too, but I feel comfortable using the subscription deleted event.

To create a stripe webhook event, go to your stripe dashboard and click the Webhooks menu at the left sidebar then click the Add endpoint button as shown in the image below.

Stripe add endpoint

When the Add a webhook endpoint modal shown, fill the Endpoint URL, Version, and Events to send fields. Select the ​customer.subscription.deleted for the Events to send field, then Add endpoint.

add a webhook endpoint

Next thing is you need to make sure that you have the laravel cashier package installed on your application. Kindly run this command( composer require laravel/cashier ) on your terminal.

Once the laravel cashier is installed, kindly put this code below in the routes/web.php file.

Route::post('stripe/webhook','SubscriptionController@handleWebhook');

into your laravel routes file. The stripe/webhook path should be match to the path of the Endpoint URL entered above. I used SubscriptionController as my controller on my routes, but you can change it if you have different controller.
Extend the SubscriptionController to the WebhookController of laravel cashier then create a function called handleCustomerSubscriptionDeleted(). The SubscriptionController will be look like in the code below:

namespace AppHttpControllers;
 //..
 // Some of your code here
 //..
 use LaravelCashierHttpControllersWebhookController;
 class SubscriptionController extends WebhookController
 {
 //..
 // Some of your code here
 //..
public function handleCustomerSubscriptionDeleted(array $payload){
        $data = $payload['data'];

        $user = $this->getUserByStripeId($payload['data']['object']['customer']);
        
        $this->cancel = false;
        if ($user) {

            $user->subscriptions->filter(function ($subscription) use ($payload) {
                return $subscription->stripe_id === $payload['data']['object']['id'];
            })->each(function ($subscription){
                
                if ($subscription != null) {
                    $status = $subscription->asStripeSubscription()->status;
                    if($status == 'canceled'){
                        $this->cancel = true;
                    }else{
                        $this->cancel = false;
                    }
                }
            });

            if($this->cancel){
                $user->status = 'Cancelled';
                $user->save();
                $mail['name'] = $user->name;
                $mail['updateCardURL'] = 'https://'.env('APP_URL').'/my-account/update-card';
                $subject = ($data['object']['plan']['amount'] / 100).' '.$data['object']['plan']['currency'];
                $mail['subject'] = 'Your payment of '. $subject.' was unsuccessful';
                $mail['card_last_four'] = $user->card_last_four;
                $mail['card_brand'] = $user->card_brand;
                
                Mail::send('emails.subscription_updated', $mail, function($message) use ($user, $data){
                    $message->from(Config::get('mail.from.address'), Config::get('mail.from.name'));
                    $message->to($user->email, $user->name);
                    $subject = ($data['object']['plan']['amount'] / 100).' '.$data['object']['plan']['currency'];
                    $message->subject('Your payment of '. $subject.' was unsuccessful');
                });


            }

        }

        return new Response('Webhook Handled', 200);
    }
 }

We all know that laravel requires CSRF token for the post request. Since stripe will send a post request event to our website via webhook, we need to put the stripe/* as an exception for verification. On VerifyCsrfToken.php file, please put the stripe/* into $except variable. It will looks like in the code below.

    protected $except = [
        // some other exception here
        'stripe/*',
    ];

In order to test the effects of payment failure on an active subscription, attach the 4000 0000 0000 0341 card as the customer’s default payment method, but use a trial period to defer the attempt.

Depending on your retry settings, you will have to wait a day or more to see the first retry attempt. If required, you can use that period to update the customer’s payment method to a working test card to see what happens for a successful retry.

I created the video below to show how Handling laravel failed payment on stripe subscription works.

If you have any questions, don’t hesitate to comment below or shoot me an email via our contact form.

Leave a Reply