Handling Braintree webhooks in Laravel Cashier

Laravel Cashier is already handling Braintree's 'subscription_canceled' and 'subscription_expired' webhooks and will cancel a subscription when any of those webhooks get triggered. The webhooks are handled in WebhookController, which can be easily extended to add support for other kinds of webhooks.

Let's see how that would work if you wanted to send a notification to a user when a charge is unsuccessful.

namespace App\Http\Controllers;

use App\Jobs\SendChargedUnsuccessfullyNotification;
use App\Models\User;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierWebhookController;
use Symfony\Component\HttpFoundation\Response;

class WebhookController extends CashierWebhookController
{
    use DispatchesJobs;

    /**
     * Handle a subscription charged unsuccessfully notification from Braintree.
     *
     * @param  WebhookNotification $webhook
     * @return Response
     */
    public function handleSubscriptionChargedUnsuccessfully($webhook)
    {
        $subscription = $this->getSubscriptionById($webhook->subscription->id);

        if ($subscription) {
            $user = User::findOrFail($subscription->user_id);
            $this->dispatch(new SendChargedUnsuccessfullyNotification($user));
        }

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

Please note the method name is prefixed with handle and the camel-cased name of the Braintree webhook. For all webhook names see constants defined in WebhookNotification, which is a part of 'braintree_php' library.

Don't forget to point a route to the controller method in routes.php and exclude the URL from CSRF verification. It's all described in the documentation.

Now here is how the 'Destination URL' can be added in Braintree's control panel (go to Settings -> Webhooks):

Braintree Webhooks

You can quite easily add tests to verify whether the custom webhooks are handled correctly (without actually communicating with Braintree). To write a basic test I used WebhookTesting class from the 'braintree_php' library to generate a sample notification. I also used model factories to insert sample user and subscription into the database right before the test is run.

use App\Jobs\SendChargedUnsuccessfullyNotification;
use App\Models\User;
use Braintree\WebhookNotification;
use Braintree\WebhookTesting;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Laravel\Cashier\Subscription;

class WebhookControllerTest extends TestCase
{
    use DatabaseTransactions;

    /**
     * Test Braintree webhook (SUBSCRIPTION_CHARGED_UNSUCCESSFULLY).
     */
    public function testChargedUnsuccessfully()
    {
        $this->createTestData();

        $this->expectsJobs([
            SendChargedUnsuccessfullyNotification::class
        ]);

        $sampleNotification = WebhookTesting::sampleNotification(
            WebhookNotification::SUBSCRIPTION_CHARGED_UNSUCCESSFULLY,
            '1b3efx'
        );

        $this->post('/braintree/webhook', $sampleNotification)
            ->assertResponseStatus(200);
    }

    /**
     * Create test data (User + Subscription).
     */
    public function createTestData()
    {
        $user = factory(User::class)->create();

        factory(Subscription::class)->create([
            'user_id' => $user->id,
            'braintree_id' => '1b3efx'
        ]);
    }
}