151 lines
4.8 KiB
PHP
151 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Mail\AbandonedCheckout;
|
|
use App\Models\AbandonedCheckout as AbandonedCheckoutModel;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Throwable;
|
|
|
|
class SendAbandonedCheckoutReminders extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'checkout:send-reminders {--dry-run : Show what would be sent without actually sending}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Send reminder emails for abandoned checkouts';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle()
|
|
{
|
|
$isDryRun = $this->option('dry-run');
|
|
|
|
if ($isDryRun) {
|
|
$this->info('🔍 DRY RUN MODE - No emails will be sent');
|
|
}
|
|
|
|
$this->info('🚀 Starting abandoned checkout reminder process...');
|
|
|
|
// Reminder-Stufen definieren: [Stufe, Stunden, Beschreibung]
|
|
$reminderStages = [
|
|
['1h', 1, '1 hour reminders'],
|
|
['24h', 24, '24 hour reminders'],
|
|
['1w', 168, '1 week reminders'], // 7 * 24 = 168 Stunden
|
|
];
|
|
|
|
$totalProcessed = 0;
|
|
$totalSent = 0;
|
|
|
|
foreach ($reminderStages as [$stage, $hours, $description]) {
|
|
$this->info("📧 Processing {$description}...");
|
|
|
|
$checkouts = AbandonedCheckoutModel::readyForReminder($stage, $hours)
|
|
->with(['user', 'package'])
|
|
->get();
|
|
|
|
$this->info(" Found {$checkouts->count()} checkouts ready for {$stage} reminder");
|
|
|
|
foreach ($checkouts as $checkout) {
|
|
try {
|
|
if ($this->shouldSendReminder($checkout, $stage)) {
|
|
$resumeUrl = $this->generateResumeUrl($checkout);
|
|
|
|
if (!$isDryRun) {
|
|
Mail::to($checkout->user)->queue(
|
|
new AbandonedCheckout(
|
|
$checkout->user,
|
|
$checkout->package,
|
|
$stage,
|
|
$resumeUrl
|
|
)
|
|
);
|
|
|
|
$checkout->updateReminderStage($stage);
|
|
$totalSent++;
|
|
} else {
|
|
$this->line(" 📧 Would send {$stage} reminder to: {$checkout->email} for package: {$checkout->package->name}");
|
|
}
|
|
|
|
$totalProcessed++;
|
|
}
|
|
} catch (Throwable $e) {
|
|
Log::error("Failed to send {$stage} reminder for checkout {$checkout->id}: " . $e->getMessage());
|
|
$this->error(" ❌ Failed to process checkout {$checkout->id}: " . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cleanup: Alte Checkouts löschen (älter als 30 Tage)
|
|
$oldCheckouts = AbandonedCheckoutModel::where('abandoned_at', '<', now()->subDays(30))
|
|
->where('converted', false)
|
|
->count();
|
|
|
|
if ($oldCheckouts > 0) {
|
|
if (!$isDryRun) {
|
|
AbandonedCheckoutModel::where('abandoned_at', '<', now()->subDays(30))
|
|
->where('converted', false)
|
|
->delete();
|
|
$this->info("🧹 Cleaned up {$oldCheckouts} old abandoned checkouts");
|
|
} else {
|
|
$this->info("🧹 Would clean up {$oldCheckouts} old abandoned checkouts");
|
|
}
|
|
}
|
|
|
|
$this->info("✅ Reminder process completed!");
|
|
$this->info(" Processed: {$totalProcessed} checkouts");
|
|
|
|
if (!$isDryRun) {
|
|
$this->info(" Sent: {$totalSent} reminder emails");
|
|
} else {
|
|
$this->info(" Would send: {$totalSent} reminder emails");
|
|
}
|
|
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Prüft ob ein Reminder versendet werden sollte
|
|
*/
|
|
private function shouldSendReminder(AbandonedCheckoutModel $checkout, string $stage): bool
|
|
{
|
|
// Verfällt der Checkout bald? Dann kein Reminder mehr
|
|
if ($checkout->isExpired()) {
|
|
return false;
|
|
}
|
|
|
|
// User existiert noch?
|
|
if (!$checkout->user) {
|
|
return false;
|
|
}
|
|
|
|
// Package existiert noch?
|
|
if (!$checkout->package) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Generiert die URL zum Wiederaufnehmen des Checkouts
|
|
*/
|
|
private function generateResumeUrl(AbandonedCheckoutModel $checkout): string
|
|
{
|
|
// Für jetzt: Einfache Package-URL
|
|
// Später: Persönliche Resume-Token URLs
|
|
return route('buy.packages', $checkout->package_id);
|
|
}
|
|
}
|