laravel / cashier-paddle

Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services.
https://laravel.com/docs/cashier-paddle
MIT License
238 stars 57 forks source link

Webhook duplicating the response statuses #238

Closed Benoit1980 closed 7 months ago

Benoit1980 commented 7 months ago

Cashier Paddle Version

2.1.0

Laravel Version

10.4.1

PHP Version

8.1

Database Driver & Version

Mariadb

Description

Hello,

I'm currently testing the Paddle sandbox, and I've observed an unusual behavior with webhook listeners. Unlike V1, the callbacks seem to be triggered multiple times.

Steps To Reproduce

class PaddleEventListener
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle received Paddle webhooks.
     *
     * @param WebhookReceived $event
     * @return void
     */
    public function handle(WebhookReceived $event)
    {
        $payloadData = $event->payload;
        $status = $payloadData['data']['status'];

        Log::info(print_r($status, true));
}

}

With the above listener, this is the LOG outputs, instead of having one of each, we have double callbacks(and even tripples):

[2024-01-18 15:32:41] production.INFO: draft  
[2024-01-18 15:32:43] production.INFO: active  
[2024-01-18 15:32:43] production.INFO: active  
[2024-01-18 15:32:44] production.INFO: ready  
[2024-01-18 15:32:44] production.INFO: ready  
[2024-01-18 15:33:01] production.INFO: paid  
[2024-01-18 15:33:01] production.INFO: paid  
[2024-01-18 15:33:03] production.INFO: active  
[2024-01-18 15:33:03] production.INFO: active  
[2024-01-18 15:33:03] production.INFO: paid  
[2024-01-18 15:33:10] production.INFO: completed  
[2024-01-18 15:33:10] production.INFO: completed  

I encounter a situation where my script wallet had double the expected amount, only to realize that the 'status === completed' condition is being executed twice. Any idea why this is happening please?

Thank you

karlomikus commented 7 months ago

I think you are listening for all events not only specific ones. You should check what type of the webhook is sent via: $payloadData['event_type'].

driesvints commented 7 months ago

Yes can you also please log the event type next to it?

Benoit1980 commented 7 months ago

I will output the $payloadData['event_type'] tonight, I will update today about this. Thank you.

Benoit1980 commented 7 months ago

I am posting the results:

public function handle(WebhookReceived $event)
    {
        $payloadData = $event->payload;
        Log::info(print_r($payloadData['event_type'], true));
    }

V.A.T Number + Address added:

[2024-01-21 23:48:02] production.INFO: transaction.created  
[2024-01-21 23:48:03] production.INFO: customer.updated  
[2024-01-21 23:48:17] production.INFO: address.created  
[2024-01-21 23:48:18] production.INFO: transaction.ready  
[2024-01-21 23:48:18] production.INFO: transaction.updated  
[2024-01-21 23:48:32] production.INFO: transaction.updated  
[2024-01-21 23:48:32] production.INFO: address.updated  
[2024-01-21 23:48:32] production.INFO: business.created  
[2024-01-21 23:48:33] production.INFO: transaction.updated  

Payment passed

[2024-01-21 23:51:08] production.INFO: transaction.paid  
[2024-01-21 23:51:08] production.INFO: transaction.updated  
[2024-01-21 23:51:10] production.INFO: subscription.created  
[2024-01-21 23:51:10] production.INFO: subscription.activated  
[2024-01-21 23:51:10] production.INFO: transaction.updated  
[2024-01-21 23:52:47] production.INFO: transaction.updated  
[2024-01-21 23:52:47] production.INFO: transaction.completed 
[2024-01-21 23:55:12] production.ERROR: Undefined array key "event_type" {"exception":"[object] (ErrorException(code: 0): Undefined array key \"event_type\" at /home/account/laravel/vendor/laravel/cashier-paddle/src/Http/Controllers/WebhookController.php:47)
[stacktrace]
#0 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(255): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError()
#1 /home/account/laravel/vendor/laravel/cashier-paddle/src/Http/Controllers/WebhookController.php(47): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->Illuminate\\Foundation\\Bootstrap\\{closure}()
#2 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): Laravel\\Paddle\\Http\\Controllers\\WebhookController->__invoke()
#3 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction()
#4 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php(260): Illuminate\\Routing\\ControllerDispatcher->dispatch()
#5 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()
#6 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(806): Illuminate\\Routing\\Route->run()
#7 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}()
#8 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#9 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(807): Illuminate\\Pipeline\\Pipeline->then()
#10 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(784): Illuminate\\Routing\\Router->runRouteWithinStack()
#11 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(748): Illuminate\\Routing\\Router->runRoute()
#12 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php(737): Illuminate\\Routing\\Router->dispatchToRoute()
#13 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch()
#14 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(144): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}()
#15 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#16 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#17 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle()
#18 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#19 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle()
#20 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle()
#21 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#22 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle()
#23 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(51): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#24 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle()
#25 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#26 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\HandleCors->handle()
#27 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#28 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(183): Illuminate\\Http\\Middleware\\TrustProxies->handle()
#29 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(119): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#30 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then()
#31 /home/account/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#32 /home/account/public_html/index.php(57): Illuminate\\Foundation\\Http\\Kernel->handle()
#33 {main}
"} 

I tried it multiple times and it seems that 2/2.5 minutes later there is a call back without the ['event_type'] key.

If there are no other event types after that, then I can now see that transaction.completed is the one I need to look for.

Hope this helps.

Thanks

driesvints commented 7 months ago

Honestly I cannot see anything wrong other than it's odd that it fails when it cannot find the event_type key. This is a key that every Paddle webhook has. If something is incoming that doesn't have this it's best to contact Paddle directly.

About the duplicate statuses: I don't think there's anything wrong here. Most likely some events coming in which still have the same status.

Benoit1980 commented 7 months ago

Thank you so much for your help. Yes I will contact Paddle and ask them what is going on regarding this.

About the statuses it is because I was using this, it was the confusing part: $payloadData['data']['status'] and not $payloadData['event_type']

Thank you so much!

Benoit1980 commented 7 months ago

Just to let you know I have sent a report to Paddle so they investigate this issue as I receive daily this errors in my log:

Undefined array key "event_type"

From


 * Handle a Paddle webhook call.

*

 * @param  \Illuminate\Http\Request  $request

* @return \Symfony\Component\HttpFoundation\Response

*/

public function __invoke(Request $request)

{

 $payload = $request->all();

$method = 'handle'.Str::studly(Str::replace('.', ' ', $payload['event_type']));  <-----------

WebhookReceived::dispatch($payload);

        if (method_exists($this, $method)) {
          $this->{$method}($payload);
         WebhookHandled::dispatch($payload);
            return new Response('Webhook Handled');
       }

Hope to receive a reply from them soon.

Benoit1980 commented 7 months ago

Hi @driesvints,

Quick heads-up about an issue I encountered, just in case your users run into the same hiccup. Paddle looked into the error tied to $payload['event_type'] and traced it back to switching from Classic to Billing API. Leaving endpoints in the Classic API on the Paddle panel results in receiving duplicate callbacks from both APIs.

Key point: $status variable uses 'alert_name,' not 'event_type.'

I was scratching my head until I realized I hadn't removed the endpoint from the Classic API. Once I did, the error vanished. It might sound trivial, but if you're not careful with API switches, those old endpoints can sneak up on you.

Hope this answer helps with any related user queries you may encounter.

Cheers!

Benoit