lmsqueezy / laravel

A package to easily integrate your Laravel application with Lemon Squeezy.
https://lemonsqueezy.com
MIT License
468 stars 47 forks source link

Custom Listener for Webhook fails #72

Closed matthiasweiss closed 4 months ago

matthiasweiss commented 4 months ago

Lemon Squeezy for Laravel Version

1.4.0

Laravel Version

10.40.0

PHP Version

8.3

Description

Hi šŸ˜

Thank you for providing this great package, it's really a joy to work with!

I've been running into an issue where I cannot add a custom listener for the OrderCreated webhook. For context, I have a LemonSqueezy product variant (one time purchase) which allows users to buy a lifetime pro membership to my service. For convenience, I want to store when a user is upgraded to pro.

However, when I add in the listener, I get the following error (id is hidden):

08:41:52] local.ERROR: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '{{id}}' for key 'lemon_squeezy_orders.lemon_squeezy_orders_lemon_squeezy_id_unique'

I made sure that the lemon_squeezy_orders is empty before the webhook arrives. Once the webhook is called there exists a row with the correct data, so the actual webhook logic (including the signing secret) seems to work. It kind of looks, as if the webhook handler is called multiple times for some reason. However, I could not find anything related in the LemonSqueezy documentation šŸ˜­

The listener, which is never executed, looks as follows:

<?php

namespace App\Listeners;

use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use LemonSqueezy\Laravel\Events\OrderCreated;

class UpgradeToPro
{
    /**
     * Handle the event.
     */
    public function handle(OrderCreated $event): void
    {
        /** @var User $user */
        $user = $event->billable;

        Log::info(sprintf('Upgrading user "%s" to Pro!'), $user->email);

        $user->pro_member_since = Carbon::now();
        $user->save();
    }
}

However, the webhook shows up as successful when I remove all the logic from the listener, as shown in the example below. The statement is also logged.

<?php

namespace App\Listeners;

use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use LemonSqueezy\Laravel\Events\OrderCreated;

class UpgradeToPro
{
    /**
     * Handle the event.
     */
    public function handle(OrderCreated $event): void
    {
        /** @var User $user */
        // $user = $event->billable;

        // Log::info(sprintf('Upgrading user "%s" to Pro!'), $user->email);

        // $user->pro_member_since = Carbon::now();
        // $user->save();
        Log::info('WITHOUT CALLS IT WORKS');
    }
}

This is how I've added the listener in the EventServiceProvider, which should be correct, since the listener works when I remove the Eloquent related code:

<?php

namespace App\Providers;

use App\Listeners\UpgradeToPro;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use LemonSqueezy\Laravel\Events\OrderCreated;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event to listener mappings for the application.
     *
     * @var array<class-string, array<int, class-string>>
     */
    protected $listen = [
        Registered::class => [SendEmailVerificationNotification::class],
        OrderCreated::class => [UpgradeToPro::class],
    ];

    ...
}

Steps To Reproduce

1) add product variant in LemonSqueezy and set up lmsqueezy/laravel

2) add pro_member_since column to create_users_table migration

            $table->timestamp('pro_member_since')->nullable();

3) Run php artisan make:listener UpgradeToPro and update the handle method:

    public function handle(OrderCreated $event): void
    {
        /** @var User $user */
        $user = $event->billable;

        Log::info(sprintf('Upgrading user "%s" to Pro!'), $user->email);

        $user->pro_member_since = Carbon::now();
        $user->save();
    }

4) update $listen array as follows:

    protected $listen = [
        Registered::class => [SendEmailVerificationNotification::class],
        OrderCreated::class => [UpgradeToPro::class],
    ];

5) Test webhook for OrderCreated

matthiasweiss commented 4 months ago

Wow, the issue ended up being wrongly closed paranetheses šŸ˜­

Changing Log::info(sprintf('Upgrading user "%s" to Pro!'), $user->email); to Log::info(sprintf('Upgrading user "%s" to Pro!', $user->email)); resolves the issue