diff --git a/app/Domains/Notification/Events/TicketsAvailable.php b/app/Domains/Notification/Events/TicketsAvailable.php deleted file mode 100644 index 3afb501..0000000 --- a/app/Domains/Notification/Events/TicketsAvailable.php +++ /dev/null @@ -1,19 +0,0 @@ - $ticketIds - */ - public function __construct( - public readonly int $purchaseId, - public readonly array $ticketIds, - ) {} -} diff --git a/app/Domains/Notification/Listeners/SendPurchasePaidEmail.php b/app/Domains/Notification/Listeners/SendPurchaseConfirmedEmail.php similarity index 76% rename from app/Domains/Notification/Listeners/SendPurchasePaidEmail.php rename to app/Domains/Notification/Listeners/SendPurchaseConfirmedEmail.php index 2205d8e..231fd35 100644 --- a/app/Domains/Notification/Listeners/SendPurchasePaidEmail.php +++ b/app/Domains/Notification/Listeners/SendPurchaseConfirmedEmail.php @@ -7,7 +7,7 @@ use App\Domains\Purchase\Events\PurchasePaid; use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; use Illuminate\Queue\InteractsWithQueue; -class SendPurchasePaidEmail implements ShouldQueueAfterCommit +class SendPurchaseConfirmedEmail implements ShouldQueueAfterCommit { use InteractsWithQueue; @@ -20,6 +20,6 @@ class SendPurchasePaidEmail implements ShouldQueueAfterCommit public function handle(PurchasePaid $event): void { - app(NotificationMailService::class)->sendPurchasePaid($event->purchaseId); + app(NotificationMailService::class)->sendPurchaseConfirmed($event->purchaseId); } } diff --git a/app/Domains/Notification/Listeners/SendTicketsAvailableEmail.php b/app/Domains/Notification/Listeners/SendTicketsAvailableEmail.php deleted file mode 100644 index 75c3ff1..0000000 --- a/app/Domains/Notification/Listeners/SendTicketsAvailableEmail.php +++ /dev/null @@ -1,25 +0,0 @@ - */ - public array $backoff = [30, 120, 300]; - - public function handle(TicketsAvailable $event): void - { - app(NotificationMailService::class)->sendTicketsAvailable($event->purchaseId, $event->ticketIds); - } -} diff --git a/app/Domains/Notification/Services/NotificationMailService.php b/app/Domains/Notification/Services/NotificationMailService.php index dc24276..6b5382a 100644 --- a/app/Domains/Notification/Services/NotificationMailService.php +++ b/app/Domains/Notification/Services/NotificationMailService.php @@ -119,55 +119,17 @@ class NotificationMailService }); } - public function sendPurchasePaid(int $purchaseId): void + public function sendPurchaseConfirmed(int $purchaseId): void { $context = ['purchase_id' => $purchaseId]; - $this->sendLogged('purchase_paid', $context, function () use ($purchaseId, $context): ?array { + $this->sendLogged('purchase_confirmed', $context, function () use ($purchaseId, $context): ?array { $purchase = Purchase::query() ->with(['tenant', 'user', 'items']) ->find($purchaseId); if ($purchase === null) { - $this->logSkipped('purchase_paid', array_merge($context, [ - 'reason' => 'purchase_not_found', - 'missing_model' => Purchase::class, - ])); - - return null; - } - - $this->mailService - ->forTenant($purchase->tenant_codigo) - ->send( - $this->recipientFor($purchase), - "Pago confirmado - Compra #{$purchase->getKey()}", - view('mail.notifications.purchase-paid', compact('purchase'))->render(), - ); - - return [ - 'tenant_code' => $purchase->tenant_codigo, - 'user_id' => $purchase->user_id, - 'purchase_status' => $purchase->status, - 'purchase_item_count' => $purchase->items->count(), - ]; - }); - } - - /** @param array $ticketIds */ - public function sendTicketsAvailable(int $purchaseId, array $ticketIds): void - { - $context = [ - 'purchase_id' => $purchaseId, - 'requested_ticket_count' => count($ticketIds), - 'requested_ticket_ids' => $ticketIds, - ]; - - $this->sendLogged('tickets_available', $context, function () use ($purchaseId, $ticketIds, $context): ?array { - $purchase = Purchase::query()->with(['tenant', 'user'])->find($purchaseId); - - if ($purchase === null) { - $this->logSkipped('tickets_available', array_merge($context, [ + $this->logSkipped('purchase_confirmed', array_merge($context, [ 'reason' => 'purchase_not_found', 'missing_model' => Purchase::class, ])); @@ -177,41 +139,34 @@ class NotificationMailService /** @var Collection $tickets */ $tickets = Ticket::query() + ->where('source_purchase_id', $purchase->getKey()) ->where('tenant_code', $purchase->tenant_codigo) - ->where('user_id', $purchase->user_id) - ->whereKey($ticketIds) ->with(TicketPresentationResolver::RELATIONS) ->get(); - - if ($tickets->isEmpty()) { - $this->logSkipped('tickets_available', array_merge($context, [ - 'reason' => 'tickets_not_found', - 'missing_model' => Ticket::class, - 'tenant_code' => $purchase->tenant_codigo, - 'user_id' => $purchase->user_id, - ])); - - return null; - } + $attachments = $tickets->isEmpty() + ? [] + : [[ + 'data' => $this->ticketPdfService->contents($purchase->tenant, $tickets), + 'name' => $this->ticketPdfService->filename($tickets), + 'mime' => 'application/pdf', + ]]; $this->mailService ->forTenant($purchase->tenant_codigo) ->send( $this->recipientFor($purchase), - 'Tus tickets ya están disponibles', - view('mail.notifications.tickets-available', compact('purchase', 'tickets'))->render(), - attachments: [[ - 'data' => $this->ticketPdfService->contents($purchase->tenant, $tickets), - 'name' => $this->ticketPdfService->filename($tickets), - 'mime' => 'application/pdf', - ]], + "Compra confirmada - Compra #{$purchase->getKey()}", + view('mail.notifications.purchase-confirmed', compact('purchase', 'tickets'))->render(), + attachments: $attachments, ); return [ 'tenant_code' => $purchase->tenant_codigo, 'user_id' => $purchase->user_id, - 'sent_ticket_count' => $tickets->count(), - 'sent_ticket_ids' => $tickets->modelKeys(), + 'purchase_status' => $purchase->status, + 'purchase_item_count' => $purchase->items->count(), + 'ticket_count' => $tickets->count(), + 'ticket_ids' => $tickets->modelKeys(), ]; }); } diff --git a/app/Domains/Notification/documentacion/README.md b/app/Domains/Notification/documentacion/README.md index 6d87fbf..467c0dd 100644 --- a/app/Domains/Notification/documentacion/README.md +++ b/app/Domains/Notification/documentacion/README.md @@ -8,12 +8,11 @@ Orquesta notificaciones de negocio por correo a partir de eventos de otros domin - `UserRegistered`: dispara el correo de bienvenida. - `PasswordResetRequested`: envía el código de recuperación si el intento sigue pendiente. -- `PurchasePaid`: envía la confirmación de pago. -- `TicketsAvailable`: informa y entrega la disponibilidad de tickets. +- `PurchasePaid`: envía la confirmación de compra y adjunta los tickets generados, cuando corresponde. ## Componentes -Los listeners `SendWelcomeEmail`, `SendPasswordResetEmail`, `SendPurchasePaidEmail` y `SendTicketsAvailableEmail` delegan en `NotificationMailService`. Este servicio carga el contexto necesario, renderiza las vistas y envía mediante `Integration/MailService`. +Los listeners `SendWelcomeEmail`, `SendPasswordResetEmail` y `SendPurchaseConfirmedEmail` delegan en `NotificationMailService`. Este servicio carga el contexto necesario, renderiza las vistas y envía mediante `Integration/MailService`. ## API y dependencias @@ -24,5 +23,5 @@ No expone rutas HTTP. Consume datos de `Auth`, `Tenant`, `Purchase` y `Ticket`, - Los listeners reciben identificadores y vuelven a cargar los modelos, evitando transportar entidades obsoletas. - La recuperación no se envía si el intento dejó de estar pendiente. - Los correos de cuenta (bienvenida y recuperación de contraseña) usan la identidad visual del `WebsiteType` asociado al tenant, con fallback al tenant si no tiene uno configurado. -- Los correos transaccionales (pago confirmado y tickets disponibles) usan la identidad visual del tenant/evento de la compra. +- El correo transaccional de compra confirmada usa la identidad visual del tenant y adjunta un único PDF cuando la compra generó tickets. - Los handlers deben permanecer idempotentes o tolerantes a reintentos de cola. diff --git a/app/Domains/Ticket/Listeners/GenerateTicketsForPaidPurchase.php b/app/Domains/Ticket/Listeners/GenerateTicketsForPaidPurchase.php index 3183405..3c7d823 100644 --- a/app/Domains/Ticket/Listeners/GenerateTicketsForPaidPurchase.php +++ b/app/Domains/Ticket/Listeners/GenerateTicketsForPaidPurchase.php @@ -3,7 +3,6 @@ namespace App\Domains\Ticket\Listeners; use App\Domains\Catalog\Models\CatalogItem; -use App\Domains\Notification\Events\TicketsAvailable; use App\Domains\Purchase\Events\PurchasePaid; use App\Domains\Purchase\Models\Purchase; use App\Domains\Ticket\Exceptions\TicketGenerationException; @@ -21,8 +20,6 @@ class GenerateTicketsForPaidPurchase ->with(['user', 'items']) ->findOrFail($event->purchaseId); $user = $purchase->user; - $ticketIds = []; - foreach ($purchase->items as $purchaseItem) { $catalogItem = CatalogItem::query() ->where('tenant_code', $purchase->tenant_codigo) @@ -40,19 +37,13 @@ class GenerateTicketsForPaidPurchase throw TicketGenerationException::purchaseWithoutUser($purchase); } - $generatedTickets = $this->ticketGenerator->generate( + $this->ticketGenerator->generate( $catalogItem, $user, $purchaseItem->cantidad, $purchaseItem->source_variant_id, $purchase->getKey(), ); - - array_push($ticketIds, ...$generatedTickets->pluck('id')->all()); - } - - if ($ticketIds !== []) { - TicketsAvailable::dispatch($purchase->getKey(), $ticketIds); } } diff --git a/app/Domains/Ticket/documentacion/README.md b/app/Domains/Ticket/documentacion/README.md index c07caf4..a266d30 100644 --- a/app/Domains/Ticket/documentacion/README.md +++ b/app/Domains/Ticket/documentacion/README.md @@ -21,7 +21,7 @@ vigente, vencido o usado, y resuelve sus fechas efectivas de inicio y fin sin pe 1. `Purchase` emite `PurchasePaid` al confirmarse el pago. 2. `GenerateTicketsForPaidPurchase` atiende el evento. 3. `TicketGeneratorService` crea los tickets requeridos según ítems, cantidades y vigencia. -4. El flujo puede emitir disponibilidad para que `Notification` informe al comprador. +4. `Notification` envía la confirmación de compra después de la generación y adjunta los tickets cuando existen. ## Endpoints diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f3f8e17..557ef96 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,11 +3,9 @@ namespace App\Providers; use App\Domains\Notification\Events\PasswordResetRequested; -use App\Domains\Notification\Events\TicketsAvailable; use App\Domains\Notification\Events\UserRegistered; use App\Domains\Notification\Listeners\SendPasswordResetEmail; -use App\Domains\Notification\Listeners\SendPurchasePaidEmail; -use App\Domains\Notification\Listeners\SendTicketsAvailableEmail; +use App\Domains\Notification\Listeners\SendPurchaseConfirmedEmail; use App\Domains\Notification\Listeners\SendWelcomeEmail; use App\Domains\Purchase\Events\PurchasePaid; use App\Domains\Ticket\Listeners\GenerateTicketsForPaidPurchase; @@ -33,9 +31,8 @@ class AppServiceProvider extends ServiceProvider */ public function boot(): void { - Event::listen(PurchasePaid::class, SendPurchasePaidEmail::class); Event::listen(PurchasePaid::class, GenerateTicketsForPaidPurchase::class); - Event::listen(TicketsAvailable::class, SendTicketsAvailableEmail::class); + Event::listen(PurchasePaid::class, SendPurchaseConfirmedEmail::class); Event::listen(UserRegistered::class, SendWelcomeEmail::class); Event::listen(PasswordResetRequested::class, SendPasswordResetEmail::class); diff --git a/resources/views/mail/notifications/purchase-paid.blade.php b/resources/views/mail/notifications/purchase-confirmed.blade.php similarity index 82% rename from resources/views/mail/notifications/purchase-paid.blade.php rename to resources/views/mail/notifications/purchase-confirmed.blade.php index 0479420..de371c1 100644 --- a/resources/views/mail/notifications/purchase-paid.blade.php +++ b/resources/views/mail/notifications/purchase-confirmed.blade.php @@ -1,4 +1,4 @@ -

¡Recibimos tu pago!

+

¡Compra realizada con éxito!

La compra #{{ $purchase->id }} fue confirmada correctamente.

@foreach ($purchase->items as $item) @@ -10,3 +10,6 @@ @endforeach

Total pagado: ${{ number_format((float) $purchase->total, 2, ',', '.') }}

+@if ($tickets->isNotEmpty()) +

Tus tickets ya están disponibles

+@endif diff --git a/resources/views/mail/notifications/tickets-available.blade.php b/resources/views/mail/notifications/tickets-available.blade.php deleted file mode 100644 index ecc06d1..0000000 --- a/resources/views/mail/notifications/tickets-available.blade.php +++ /dev/null @@ -1,8 +0,0 @@ -

Tus tickets ya están disponibles

-

Generamos {{ $tickets->count() }} {{ $tickets->count() === 1 ? 'ticket' : 'tickets' }} para la compra #{{ $purchase->id }}.

-

{{ $tickets->count() === 1 ? 'El ticket está adjunto' : 'Los tickets están adjuntos' }} a este correo en formato PDF.

- diff --git a/tests/Feature/Notification/NotificationMailServiceTest.php b/tests/Feature/Notification/NotificationMailServiceTest.php index 87cdae7..bba638e 100644 --- a/tests/Feature/Notification/NotificationMailServiceTest.php +++ b/tests/Feature/Notification/NotificationMailServiceTest.php @@ -142,7 +142,7 @@ class NotificationMailServiceTest extends TestCase }); } - public function test_it_sends_purchase_and_ticket_emails_to_the_purchase_recipient(): void + public function test_it_sends_one_purchase_confirmation_with_generated_tickets_attached(): void { $this->useWebsiteTypeBranding(); $purchase = Purchase::query()->create([ @@ -175,30 +175,23 @@ class NotificationMailServiceTest extends TestCase $ticket = Ticket::query()->create([ 'tenant_code' => $this->tenant->codigo, 'ticket' => fake()->uuid(), + 'source_purchase_id' => $purchase->id, 'source_catalog_item_id' => $catalogItem->id, 'user_id' => $this->user->id, ]); $service = app(NotificationMailService::class); - $service->sendPurchasePaid($purchase->id); - $service->sendTicketsAvailable($purchase->id, [$ticket->id]); + $service->sendPurchaseConfirmed($purchase->id); - Mail::assertSent(Mailable::class, 2); - Mail::assertSent(Mailable::class, function (Mailable $mail) use ($purchase): bool { - $mail->assertTo('checkout@example.com'); - - return $mail->subject === "Pago confirmado - Compra #{$purchase->id}" - && str_contains($mail->render(), 'Total pagado') - && str_contains($mail->render(), 'border-top: 4px solid #112233') - && ! str_contains($mail->render(), 'border-top: 4px solid #ff7006'); - }); - Mail::assertSent(Mailable::class, function (Mailable $mail) use ($ticket): bool { + Mail::assertSent(Mailable::class, 1); + Mail::assertSent(Mailable::class, function (Mailable $mail) use ($purchase, $ticket): bool { $mail->assertTo('checkout@example.com'); $attachment = collect($mail->rawAttachments)->firstWhere('name', "tickets_{$ticket->id}.pdf"); - return $mail->subject === 'Tus tickets ya están disponibles' - && str_contains($mail->render(), 'Entrada general') - && str_contains($mail->render(), 'El ticket está adjunto') + return $mail->subject === "Compra confirmada - Compra #{$purchase->id}" + && str_contains($mail->render(), '¡Compra realizada con éxito!') + && str_contains($mail->render(), 'Total pagado') + && str_contains($mail->render(), 'Tus tickets ya están disponibles') && $attachment !== null && $attachment['options'] === ['mime' => 'application/pdf'] && str_starts_with($attachment['data'], '%PDF-') @@ -207,6 +200,26 @@ class NotificationMailServiceTest extends TestCase }); } + public function test_purchase_confirmation_omits_ticket_content_and_attachment_without_tickets(): void + { + $purchase = Purchase::query()->create([ + 'tenant_codigo' => $this->tenant->codigo, + 'user_id' => $this->user->id, + 'status' => Purchase::STATUS_PAID, + 'payment_method' => 'transfer', + 'total' => 25, + ]); + + app(NotificationMailService::class)->sendPurchaseConfirmed($purchase->id); + + Mail::assertSent(Mailable::class, function (Mailable $mail) use ($purchase): bool { + return $mail->subject === "Compra confirmada - Compra #{$purchase->id}" + && str_contains($mail->render(), '¡Compra realizada con éxito!') + && ! str_contains($mail->render(), 'Tus tickets ya están disponibles') + && $mail->rawAttachments === []; + }); + } + private function useWebsiteTypeBranding(): void { $websiteType = WebsiteType::query()->create([ diff --git a/tests/Feature/Notification/QueuedNotificationListenerTest.php b/tests/Feature/Notification/QueuedNotificationListenerTest.php index 89fcdfc..fbf446f 100644 --- a/tests/Feature/Notification/QueuedNotificationListenerTest.php +++ b/tests/Feature/Notification/QueuedNotificationListenerTest.php @@ -2,9 +2,7 @@ namespace Tests\Feature\Notification; -use App\Domains\Notification\Events\TicketsAvailable; -use App\Domains\Notification\Listeners\SendPurchasePaidEmail; -use App\Domains\Notification\Listeners\SendTicketsAvailableEmail; +use App\Domains\Notification\Listeners\SendPurchaseConfirmedEmail; use App\Domains\Notification\Services\NotificationMailService; use App\Domains\Purchase\Events\PurchasePaid; use Mockery; @@ -12,35 +10,21 @@ use Tests\TestCase; class QueuedNotificationListenerTest extends TestCase { - public function test_purchase_paid_email_delegates_with_the_purchase_id(): void + public function test_purchase_confirmed_email_delegates_with_the_purchase_id(): void { $mailService = Mockery::mock(NotificationMailService::class); - $mailService->shouldReceive('sendPurchasePaid') + $mailService->shouldReceive('sendPurchaseConfirmed') ->once() ->with(123); $this->app->instance(NotificationMailService::class, $mailService); - (new SendPurchasePaidEmail)->handle(new PurchasePaid(123)); + (new SendPurchaseConfirmedEmail)->handle(new PurchasePaid(123)); } - public function test_tickets_available_email_delegates_with_scalar_identifiers(): void - { - $mailService = Mockery::mock(NotificationMailService::class); - $mailService->shouldReceive('sendTicketsAvailable') - ->once() - ->with(123, [10, 11]); - $this->app->instance(NotificationMailService::class, $mailService); - - (new SendTicketsAvailableEmail)->handle(new TicketsAvailable(123, [10, 11])); - } - - public function test_email_events_only_serialize_scalar_identifiers(): void + public function test_purchase_paid_event_only_serializes_the_purchase_id(): void { $purchasePaid = unserialize(serialize(new PurchasePaid(123))); - $ticketsAvailable = unserialize(serialize(new TicketsAvailable(123, [10, 11]))); $this->assertSame(123, $purchasePaid->purchaseId); - $this->assertSame(123, $ticketsAvailable->purchaseId); - $this->assertSame([10, 11], $ticketsAvailable->ticketIds); } } diff --git a/tests/Feature/Ticket/TicketGeneratorServiceTest.php b/tests/Feature/Ticket/TicketGeneratorServiceTest.php index a8c937e..e9eda8a 100644 --- a/tests/Feature/Ticket/TicketGeneratorServiceTest.php +++ b/tests/Feature/Ticket/TicketGeneratorServiceTest.php @@ -11,7 +11,6 @@ use App\Domains\Catalog\Models\CatalogItem; use App\Domains\Catalog\Models\Inventory; use App\Domains\Catalog\Services\CatalogService; use App\Domains\Event\Models\EventDate; -use App\Domains\Notification\Events\TicketsAvailable; use App\Domains\Purchase\Models\Purchase; use App\Domains\Shared\Enums\FieldType; use App\Domains\Tenant\Models\Tenant; @@ -22,7 +21,6 @@ use App\Domains\Ticket\Services\TicketGeneratorService; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Queue; use Illuminate\Support\Str; use Tests\TestCase; @@ -220,7 +218,6 @@ class TicketGeneratorServiceTest extends TestCase public function test_marking_a_purchase_as_paid_generates_its_tickets_once(): void { - Event::fake([TicketsAvailable::class]); $item = $this->createTicketableItem('paid-ticket'); $purchase = $this->createPurchase($item, 2); $purchase->setRelation('items', new EloquentCollection); @@ -229,7 +226,6 @@ class TicketGeneratorServiceTest extends TestCase $this->assertSame(Purchase::STATUS_PAID, $purchase->status); $this->assertDatabaseCount('tickets', 2); - Event::assertDispatchedTimes(TicketsAvailable::class, 1); $this->actingAs($this->user, 'sanctum') ->getJson("/api/tenants/{$this->tenant->codigo}/compras/{$purchase->id}") @@ -240,7 +236,6 @@ class TicketGeneratorServiceTest extends TestCase $purchase->markAsPaid(); $this->assertDatabaseCount('tickets', 2); - Event::assertDispatchedTimes(TicketsAvailable::class, 1); } public function test_a_ticket_generated_from_a_purchase_keeps_its_source_ids(): void @@ -453,7 +448,6 @@ class TicketGeneratorServiceTest extends TestCase public function test_marking_a_purchase_as_paid_ignores_items_without_tickets(): void { - Event::fake([TicketsAvailable::class]); $item = $this->createTicketableItem('regular-product'); $item->update(['has_tickets' => false]); $purchase = $this->createPurchase($item->fresh(), 1); @@ -462,7 +456,6 @@ class TicketGeneratorServiceTest extends TestCase $this->assertSame(Purchase::STATUS_PAID, $purchase->status); $this->assertDatabaseCount('tickets', 0); - Event::assertNotDispatched(TicketsAvailable::class); $this->actingAs($this->user, 'sanctum') ->getJson("/api/tenants/{$this->tenant->codigo}/compras/{$purchase->id}") diff --git a/tests/Unit/Notification/NotificationMailServiceLoggingTest.php b/tests/Unit/Notification/NotificationMailServiceLoggingTest.php index 18003a2..6733354 100644 --- a/tests/Unit/Notification/NotificationMailServiceLoggingTest.php +++ b/tests/Unit/Notification/NotificationMailServiceLoggingTest.php @@ -42,31 +42,11 @@ class NotificationMailServiceLoggingTest extends TestCase 'purchase_id' => 123, 'reason' => 'purchase_not_found', 'missing_model' => Purchase::class, - 'email_type' => 'purchase_paid', + 'email_type' => 'purchase_confirmed', ], ); - $this->service->sendPurchasePaid(123); - } - - public function test_missing_purchase_log_includes_the_requested_ticket_ids(): void - { - $this->createEmptyPurchasesTable(); - - Log::shouldReceive('channel')->once()->with('emails')->andReturnSelf(); - Log::shouldReceive('warning')->once()->with( - 'Notification email skipped.', - [ - 'purchase_id' => 123, - 'requested_ticket_count' => 2, - 'requested_ticket_ids' => [10, 11], - 'reason' => 'purchase_not_found', - 'missing_model' => Purchase::class, - 'email_type' => 'tickets_available', - ], - ); - - $this->service->sendTicketsAvailable(123, [10, 11]); + $this->service->sendPurchaseConfirmed(123); } public function test_it_logs_successful_delivery_with_the_mailer(): void @@ -97,13 +77,13 @@ class NotificationMailServiceLoggingTest extends TestCase Log::shouldReceive('error')->once()->with( 'Notification email delivery failed.', Mockery::on(fn (array $context): bool => $context['purchase_id'] === 123 - && $context['email_type'] === 'purchase_paid' + && $context['email_type'] === 'purchase_confirmed' && $context['exception'] === $exception), ); $this->expectExceptionObject($exception); - $this->sendLogged('purchase_paid', ['purchase_id' => 123], function () use ($exception): array { + $this->sendLogged('purchase_confirmed', ['purchase_id' => 123], function () use ($exception): array { throw $exception; }); }