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

Handle duplicated subscription created event #194

Closed AcTiv3MineD closed 1 year ago

AcTiv3MineD commented 1 year ago

If for any reason subscription_created webhook is called more than once and the subscription was created on the first one, the subsequent calls will return a 500 status error as a result of the unique constraint on paddle_id.

This PR solves this issue by checking if the subscription exists before proceeding.

driesvints commented 1 year ago

@GrahamCampbell Not sure what you mean. If a subscription_created for the same subscription ID comes in twice it's most likely a network fluke or retry gone wrong. The events should be exactly the same and the second one can safely be ignored.

GrahamCampbell commented 1 year ago

That's not what the issue is. The issue is two requests come in, both check the DB and see it doesn't exist, then both try to do the insert.

driesvints commented 1 year ago

@GrahamCampbell I still don't follow... surely the second requests sees that the subscription has been added by the first request?

GrahamCampbell commented 1 year ago

Not if the first request has not gotten as far as doing the insert when the second does the select.

driesvints commented 1 year ago

@GrahamCampbell but that would be the same situation we're in right now. We're currently talking if the second request comes in at a later point.

AcTiv3MineD commented 1 year ago

@GrahamCampbell Sorry if the description is not very clear.

The issue is as @driesvints described, the first call can "fail" because of network, maybe a listener execution went wrong or even if you hit retry on Paddle's developer tool.

The proposed approach mimics what already exists on: handleSubscriptionPaymentSucceeded and handlePaymentSucceeded

I thought of checking if there was a subscription first and then proceeding with the execution, but this could lead to Replay attacks, thus, just stopping the execution like the previous endpoints do is the safest option.