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 by 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 in routes.php and exclude the URL from CSRF verification. It's all described in the documentation.

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

Edit Webhook

More interestingly you can test whether the webhooks are handled correctly without communicating with Braintree. To write a basic test I used WebhookTesting class from the 'braintree_php' library to generate a sample notification. Also I chose to use model factories to insert sample user and subscription into the database 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'
        ]);
    }
Did you like this post?
Previous post

Codeception — automated database dumps

I've been recently using Codeception and really appreciate what it can do. I see Codeception as a natural next step from PHPUnit. What's nice about Codeception is that existing classic PHPUnit tests are compatible with it, so not all work is lost for people moving from PHPUnit to Codeception...
Next post

The power of lists and maps in SASS

If you find yourself repeating a lot of similar rules when writing stylesheets or your needs go beyond simple variables think of lists and maps. I just discovered how useful lists and maps can be and therefore share a few examples.