From 9509aaf0f791595685af5a6064338c68901d276e Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 29 Sep 2026 13:39:54 +0100 Subject: [PATCH] Always email developers when they make a plugin sale PluginSaleCompleted wasn't marked as transactional, so developers who had turned off email notifications, or who hadn't verified their email address, never got the sale email. They only saw the in-app notification. It now implements TransactionalNotification, like the purchase receipt, so the email is sent regardless of those settings. Co-Authored-By: Claude Opus 5.5 --- app/Contracts/TransactionalNotification.php | 4 +-- app/Notifications/PluginSaleCompleted.php | 3 +- .../Notifications/PluginSaleCompletedTest.php | 32 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/Contracts/TransactionalNotification.php b/app/Contracts/TransactionalNotification.php index 556be31c9..3e589b579 100644 --- a/app/Contracts/TransactionalNotification.php +++ b/app/Contracts/TransactionalNotification.php @@ -8,8 +8,8 @@ * Marker interface for notifications that must always be delivered, * regardless of the user's email preferences or verification state. * - * Account recovery, purchase receipts, and license/entitlement - * notifications fall into this category. + * Account recovery, purchase receipts, developer sale notifications, + * and license/entitlement notifications fall into this category. * * @see SuppressMailNotificationListener */ diff --git a/app/Notifications/PluginSaleCompleted.php b/app/Notifications/PluginSaleCompleted.php index f9ef333f6..dc6451d89 100644 --- a/app/Notifications/PluginSaleCompleted.php +++ b/app/Notifications/PluginSaleCompleted.php @@ -2,6 +2,7 @@ namespace App\Notifications; +use App\Contracts\TransactionalNotification; use App\Models\PluginPayout; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -9,7 +10,7 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\Collection; -class PluginSaleCompleted extends Notification implements ShouldQueue +class PluginSaleCompleted extends Notification implements ShouldQueue, TransactionalNotification { use Queueable; diff --git a/tests/Feature/Notifications/PluginSaleCompletedTest.php b/tests/Feature/Notifications/PluginSaleCompletedTest.php index 4bda8c398..6ee8e727c 100644 --- a/tests/Feature/Notifications/PluginSaleCompletedTest.php +++ b/tests/Feature/Notifications/PluginSaleCompletedTest.php @@ -3,6 +3,7 @@ namespace Tests\Feature\Notifications; use App\Enums\PayoutStatus; +use App\Listeners\SuppressMailNotificationListener; use App\Models\DeveloperAccount; use App\Models\Plugin; use App\Models\PluginLicense; @@ -10,6 +11,7 @@ use App\Models\User; use App\Notifications\PluginSaleCompleted; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Notifications\Events\NotificationSending; use Tests\TestCase; class PluginSaleCompletedTest extends TestCase @@ -193,4 +195,34 @@ public function test_toarray_contains_payout_ids_and_total(): void $this->assertEquals([$payout->id], $array['payout_ids']); $this->assertEquals(2030, $array['total_developer_amount']); } + + public function test_email_is_sent_to_developers_who_opted_out_of_notification_emails(): void + { + $developer = User::factory()->create(['receives_notification_emails' => false]); + + $event = new NotificationSending( + $developer, + new PluginSaleCompleted(collect()), + 'mail', + ); + + $listener = new SuppressMailNotificationListener; + + $this->assertTrue($listener->handle($event)); + } + + public function test_email_is_sent_to_developers_with_unverified_email_addresses(): void + { + $developer = User::factory()->unverified()->create(); + + $event = new NotificationSending( + $developer, + new PluginSaleCompleted(collect()), + 'mail', + ); + + $listener = new SuppressMailNotificationListener; + + $this->assertTrue($listener->handle($event)); + } }