feat(notification): implement email notifications for user registration and purchase events
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Domains\Auth\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class RegisterUserRequest extends FormRequest
|
||||
{
|
||||
@@ -18,9 +19,10 @@ class RegisterUserRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'tenant_codigo' => ['nullable', 'string', Rule::exists('tenants', 'codigo')],
|
||||
'nombre_apellido' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')],
|
||||
'password' => ['required', 'string', 'confirmed', \Illuminate\Validation\Rules\Password::min(8)->mixedCase()->symbols()],
|
||||
'password' => ['required', 'string', 'confirmed', Password::min(8)->mixedCase()->symbols()],
|
||||
'dni' => ['nullable', 'string', 'max:255'],
|
||||
'telefono' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
|
||||
@@ -3,20 +3,27 @@
|
||||
namespace App\Domains\Auth\Services;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Notification\Events\UserRegistered;
|
||||
|
||||
class RegisterUserService
|
||||
{
|
||||
/**
|
||||
* @param array{nombre_apellido: string, email: string, password: string, dni?: string|null, telefono?: string|null} $data
|
||||
* @param array{tenant_codigo?: string|null, nombre_apellido: string, email: string, password: string, dni?: string|null, telefono?: string|null} $data
|
||||
*/
|
||||
public function register(array $data): User
|
||||
{
|
||||
return User::query()->create([
|
||||
$user = User::query()->create([
|
||||
'nombre_apellido' => $data['nombre_apellido'],
|
||||
'email' => $data['email'],
|
||||
'password' => $data['password'],
|
||||
'dni' => $data['dni'] ?? null,
|
||||
'telefono' => $data['telefono'] ?? null,
|
||||
]);
|
||||
|
||||
if (! empty($data['tenant_codigo'])) {
|
||||
UserRegistered::dispatch($user, $data['tenant_codigo']);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
20
app/Domains/Notification/Events/TicketsAvailable.php
Normal file
20
app/Domains/Notification/Events/TicketsAvailable.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Events;
|
||||
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TicketsAvailable
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param array<int, int> $ticketIds
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Purchase $purchase,
|
||||
public readonly array $ticketIds,
|
||||
) {}
|
||||
}
|
||||
17
app/Domains/Notification/Events/UserRegistered.php
Normal file
17
app/Domains/Notification/Events/UserRegistered.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Events;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserRegistered
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public readonly User $user,
|
||||
public readonly string $tenantCode,
|
||||
) {}
|
||||
}
|
||||
25
app/Domains/Notification/Listeners/SendPurchasePaidEmail.php
Normal file
25
app/Domains/Notification/Listeners/SendPurchasePaidEmail.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Listeners;
|
||||
|
||||
use App\Domains\Notification\Services\NotificationMailService;
|
||||
use App\Domains\Purchase\Events\PurchasePaid;
|
||||
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SendPurchasePaidEmail implements ShouldQueueAfterCommit
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
|
||||
public string $queue = 'emails';
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
/** @var array<int, int> */
|
||||
public array $backoff = [30, 120, 300];
|
||||
|
||||
public function handle(PurchasePaid $event): void
|
||||
{
|
||||
app(NotificationMailService::class)->sendPurchasePaid($event->purchase->getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Listeners;
|
||||
|
||||
use App\Domains\Notification\Events\TicketsAvailable;
|
||||
use App\Domains\Notification\Services\NotificationMailService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SendTicketsAvailableEmail implements ShouldQueueAfterCommit
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
|
||||
public string $queue = 'emails';
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
/** @var array<int, int> */
|
||||
public array $backoff = [30, 120, 300];
|
||||
|
||||
public function handle(TicketsAvailable $event): void
|
||||
{
|
||||
app(NotificationMailService::class)->sendTicketsAvailable($event->purchase->getKey(), $event->ticketIds);
|
||||
}
|
||||
}
|
||||
25
app/Domains/Notification/Listeners/SendWelcomeEmail.php
Normal file
25
app/Domains/Notification/Listeners/SendWelcomeEmail.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Listeners;
|
||||
|
||||
use App\Domains\Notification\Events\UserRegistered;
|
||||
use App\Domains\Notification\Services\NotificationMailService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class SendWelcomeEmail implements ShouldQueueAfterCommit
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
|
||||
public string $queue = 'emails';
|
||||
|
||||
public int $tries = 3;
|
||||
|
||||
/** @var array<int, int> */
|
||||
public array $backoff = [30, 120, 300];
|
||||
|
||||
public function handle(UserRegistered $event): void
|
||||
{
|
||||
app(NotificationMailService::class)->sendWelcome($event->user->getKey(), $event->tenantCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Notification\Services;
|
||||
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Integration\Services\MailService;
|
||||
use App\Domains\Purchase\Models\Purchase;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use App\Domains\Ticket\Models\Ticket;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class NotificationMailService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MailService $mailService,
|
||||
) {}
|
||||
|
||||
public function sendWelcome(int $userId, string $tenantCode): void
|
||||
{
|
||||
$tenant = Tenant::query()->where('codigo', $tenantCode)->firstOrFail();
|
||||
$user = User::query()->findOrFail($userId);
|
||||
|
||||
$this->mailService
|
||||
->forTenant($tenantCode)
|
||||
->send(
|
||||
$user->email,
|
||||
"Bienvenido a {$tenant->nombre}",
|
||||
view('mail.notifications.welcome', compact('tenant', 'user'))->render(),
|
||||
);
|
||||
}
|
||||
|
||||
public function sendPurchasePaid(int $purchaseId): void
|
||||
{
|
||||
$purchase = Purchase::query()
|
||||
->with(['tenant', 'user', 'items'])
|
||||
->findOrFail($purchaseId);
|
||||
|
||||
$this->mailService
|
||||
->forTenant($purchase->tenant_codigo)
|
||||
->send(
|
||||
$this->recipientFor($purchase),
|
||||
"Pago confirmado - Compra #{$purchase->getKey()}",
|
||||
view('mail.notifications.purchase-paid', compact('purchase'))->render(),
|
||||
);
|
||||
}
|
||||
|
||||
/** @param array<int, int> $ticketIds */
|
||||
public function sendTicketsAvailable(int $purchaseId, array $ticketIds): void
|
||||
{
|
||||
$purchase = Purchase::query()->with(['tenant', 'user'])->findOrFail($purchaseId);
|
||||
/** @var Collection<int, Ticket> $tickets */
|
||||
$tickets = Ticket::query()
|
||||
->where('tenant_code', $purchase->tenant_codigo)
|
||||
->where('user_id', $purchase->user_id)
|
||||
->whereKey($ticketIds)
|
||||
->get();
|
||||
|
||||
if ($tickets->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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(),
|
||||
);
|
||||
}
|
||||
|
||||
private function recipientFor(Purchase $purchase): string
|
||||
{
|
||||
return (string) ($purchase->email ?: $purchase->user?->email);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
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\Ticket\Exceptions\TicketGenerationException;
|
||||
use App\Domains\Ticket\Services\TicketGeneratorService;
|
||||
@@ -20,6 +21,7 @@ class GenerateTicketsForPaidPurchase
|
||||
->with(['user', 'items'])
|
||||
->findOrFail($event->purchase->getKey());
|
||||
$user = $purchase->user;
|
||||
$ticketIds = [];
|
||||
|
||||
foreach ($purchase->items as $purchaseItem) {
|
||||
$catalogItem = CatalogItem::query()
|
||||
@@ -38,12 +40,18 @@ class GenerateTicketsForPaidPurchase
|
||||
throw TicketGenerationException::purchaseWithoutUser($purchase);
|
||||
}
|
||||
|
||||
$this->ticketGenerator->generate(
|
||||
$generatedTickets = $this->ticketGenerator->generate(
|
||||
$catalogItem,
|
||||
$user,
|
||||
$purchaseItem->cantidad,
|
||||
$purchaseItem->source_variant_id,
|
||||
);
|
||||
|
||||
array_push($ticketIds, ...$generatedTickets->pluck('id')->all());
|
||||
}
|
||||
|
||||
if ($ticketIds !== []) {
|
||||
TicketsAvailable::dispatch($purchase, $ticketIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Domains\Notification\Events\TicketsAvailable;
|
||||
use App\Domains\Notification\Events\UserRegistered;
|
||||
use App\Domains\Notification\Listeners\SendPurchasePaidEmail;
|
||||
use App\Domains\Notification\Listeners\SendTicketsAvailableEmail;
|
||||
use App\Domains\Notification\Listeners\SendWelcomeEmail;
|
||||
use App\Domains\Purchase\Events\PurchasePaid;
|
||||
use App\Domains\Ticket\Listeners\GenerateTicketsForPaidPurchase;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -23,7 +28,10 @@ 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(UserRegistered::class, SendWelcomeEmail::class);
|
||||
|
||||
Builder::macro('paginateFromRequest', function (int $defaultPerPage = 15, int $maxPerPage = 100, ?int $page = null) {
|
||||
/** @var Builder $this */
|
||||
|
||||
Reference in New Issue
Block a user