diff --git a/src/Http/Controllers/WebhookController.php b/src/Http/Controllers/WebhookController.php index 7183fea..1ceb19b 100644 --- a/src/Http/Controllers/WebhookController.php +++ b/src/Http/Controllers/WebhookController.php @@ -157,6 +157,10 @@ protected function handleSubscriptionCreated(array $payload) throw new InvalidPassthroughPayload; } + if ($this->subscriptionExists($payload['subscription_id'])) { + return; + } + $customer = $this->findOrCreateCustomer($payload['passthrough']); $trialEndsAt = $payload['status'] === Subscription::STATUS_TRIALING @@ -278,6 +282,17 @@ protected function findSubscription(string $subscriptionId) return Cashier::$subscriptionModel::firstWhere('paddle_id', $subscriptionId); } + /** + * Determine if a subscription with a given Paddle ID already exists. + * + * @param string $subscriptionId + * @return bool + */ + protected function subscriptionExists(string $subscriptionId) + { + return Cashier::$subscriptionModel::where('paddle_id', $subscriptionId)->exists(); + } + /** * Determine if a receipt with a given Order ID already exists. * diff --git a/tests/Feature/WebhooksTest.php b/tests/Feature/WebhooksTest.php index cff4781..c5fcebe 100644 --- a/tests/Feature/WebhooksTest.php +++ b/tests/Feature/WebhooksTest.php @@ -258,6 +258,50 @@ public function test_it_can_handle_a_subscription_created_event_if_billable_alre }); } + public function test_it_can_handle_a_duplicated_subscription_created_event() + { + Cashier::fake(); + + $user = $this->createUser(); + + for ($i = 0; $i < 2; $i++) { + $this->postJson('paddle/webhook', [ + 'alert_name' => 'subscription_created', + 'user_id' => 'foo', + 'email' => $user->paddleEmail(), + 'passthrough' => json_encode([ + 'billable_id' => $user->id, + 'billable_type' => $user->getMorphClass(), + 'subscription_name' => 'main', + ]), + 'quantity' => 1, + 'status' => Subscription::STATUS_ACTIVE, + 'subscription_id' => 'bar', + 'subscription_plan_id' => 1234, + ])->assertOk(); + } + + $this->assertDatabaseHas('customers', [ + 'billable_id' => $user->id, + 'billable_type' => $user->getMorphClass(), + ]); + + $this->assertDatabaseHas('subscriptions', [ + 'billable_id' => $user->id, + 'billable_type' => $user->getMorphClass(), + 'name' => 'main', + 'paddle_id' => 'bar', + 'paddle_plan' => 1234, + 'paddle_status' => Subscription::STATUS_ACTIVE, + 'quantity' => 1, + 'trial_ends_at' => null, + ]); + + Cashier::assertSubscriptionCreated(function (SubscriptionCreated $event) use ($user) { + return $event->billable->id === $user->id && $event->subscription->paddle_plan === 1234; + }); + } + public function test_it_can_handle_a_subscription_updated_event() { Cashier::fake();