timeStart = microtime(true); $dryRun = $this->option('dry-run'); $singleAboId = $this->option('abo-id'); \Log::channel('abo_order')->info('RetryFailedPaypalAbos: Gestartet', [ 'dry_run' => $dryRun, 'abo_id' => $singleAboId, ]); $this->info($dryRun ? '=== DRY-RUN Modus (keine Bestellungen) ===' : '=== LIVE Modus ==='); $this->newLine(); $abos = $this->getAffectedAbos($singleAboId); if ($abos->isEmpty()) { $this->warn('Keine betroffenen PayPal-Abos gefunden.'); return self::SUCCESS; } $this->displayAboList($abos); if (! $dryRun && ! $singleAboId) { if (! $this->confirm("Sollen alle {$abos->count()} Abos jetzt erneut ausgeführt werden?")) { $this->info('Abgebrochen.'); return self::SUCCESS; } } $results = ['success' => 0, 'error' => 0, 'skipped' => 0]; foreach ($abos as $userAbo) { if ($dryRun) { $this->info(" [DRY-RUN] Abo #{$userAbo->id} würde ausgeführt werden"); $results['skipped']++; continue; } try { $result = $this->retryAboOrder($userAbo); if ($result) { $results['success']++; } else { $results['error']++; } } catch (\Throwable $e) { $results['error']++; \Log::channel('abo_order')->error('RetryFailedPaypalAbos: Exception', [ 'abo_id' => $userAbo->id, 'error' => $e->getMessage(), ]); $this->error(" Abo #{$userAbo->id}: Exception - {$e->getMessage()}"); } } $this->newLine(); $this->table( ['Ergebnis', 'Anzahl'], [ ['Erfolgreich', $results['success']], ['Fehlgeschlagen', $results['error']], ['Übersprungen (Dry-Run)', $results['skipped']], ] ); $executionTime = $this->getExecutionTime(); $this->info("Abgeschlossen in {$executionTime}"); \Log::channel('abo_order')->info("RetryFailedPaypalAbos: Abgeschlossen in {$executionTime}", $results); return self::SUCCESS; } /** * @return \Illuminate\Database\Eloquent\Collection */ private function getAffectedAbos(?string $singleAboId): \Illuminate\Database\Eloquent\Collection { $query = UserAbo::query() ->where('status', 3) ->where('active', true) ->where('clearingtype', 'wlt') ->where('wallettype', 'PPE') ->whereRaw("DATE(next_date) = '2026-04-05'") ->with(['shopping_user', 'user_abo_items']); if ($singleAboId) { $query->where('id', $singleAboId); } return $query->orderBy('id')->get(); } private function displayAboList(\Illuminate\Database\Eloquent\Collection $abos): void { $rows = $abos->map(fn (UserAbo $abo) => [ $abo->id, $abo->user_id ?? '-', $abo->is_for, $abo->email, $abo->abo_interval, $abo->getRawOriginal('next_date'), $abo->user_abo_items->count().' Artikel', ]); $this->table( ['Abo-ID', 'User-ID', 'Typ', 'E-Mail', 'Intervall', 'Next-Date', 'Artikel'], $rows->toArray() ); $this->info("Betroffene Abos: {$abos->count()}"); $this->newLine(); } private function retryAboOrder(UserAbo $userAbo): bool { $this->info(" Verarbeite Abo #{$userAbo->id} ({$userAbo->email})..."); \Log::channel('abo_order')->info('RetryFailedPaypalAbos: Verarbeite Abo', [ 'abo_id' => $userAbo->id, 'email' => $userAbo->email, 'payone_userid' => $userAbo->payone_userid, ]); $alreadyPaidToday = UserAboOrder::where('user_abo_id', $userAbo->id) ->whereDate('created_at', now()->toDateString()) ->where('paid', true) ->exists(); if ($alreadyPaidToday) { $this->warn(" Abo #{$userAbo->id}: Bereits heute bezahlt - übersprungen"); return true; } AboHelper::ensureUserAboItemsFromLatestOrder($userAbo); $shoppingOrder = null; $userOrder = new UserMakeOrder($userAbo); try { if (! $userOrder->createShoppingUser()) { $this->error(" Abo #{$userAbo->id}: Shopping-User konnte nicht erstellt werden"); return false; } $shoppingOrder = $userOrder->makeShoppingOrder(); if (! $shoppingOrder) { $this->error(" Abo #{$userAbo->id}: Bestellung konnte nicht erstellt werden"); return false; } $this->info(" Bestellung #{$shoppingOrder->id} erstellt (Betrag: {$shoppingOrder->total_shipping} EUR)"); $response = $userOrder->makePayment(); if (is_object($response)) { $response = (array) $response; } if (! isset($response['status'])) { $this->error(" Abo #{$userAbo->id}: Ungültige Zahlungsantwort"); $this->markAboError($userAbo, $shoppingOrder); return false; } if ($response['status'] === 'APPROVED') { $this->info(" Zahlung ERFOLGREICH für Abo #{$userAbo->id}"); $this->markAboSuccess($userAbo, $shoppingOrder); return true; } $errorCode = $response['errorcode'] ?? '-'; $errorMsg = $response['errormessage'] ?? '-'; $this->error(" Zahlung FEHLGESCHLAGEN für Abo #{$userAbo->id}: [{$errorCode}] {$errorMsg}"); MyLog::writeLog( 'userabo', 'error', 'Error:RetryPaypal RetryFailedPaypalAbos / makePayment Error', $response ); $this->markAboError($userAbo, $shoppingOrder); $shoppingPayment = $userOrder->getShoppingPayment(); if ($shoppingPayment) { Payment::paymentStatusSendMail($shoppingOrder, $shoppingPayment, [ 'mode' => $shoppingPayment->mode, 'txaction' => 'error', 'send_link' => false, 'payment_error' => $response, ]); } return false; } catch (\Throwable $e) { \Log::channel('abo_order')->error('RetryFailedPaypalAbos: Exception bei Abo-Verarbeitung', [ 'abo_id' => $userAbo->id, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); $this->error(" Exception: {$e->getMessage()}"); if ($shoppingOrder) { $this->markAboError($userAbo, $shoppingOrder); } return false; } } private function markAboSuccess(UserAbo $userAbo, $shoppingOrder): void { DB::transaction(function () use ($userAbo, $shoppingOrder) { $nextDate = AboHelper::setNextDate(now(), $userAbo->abo_interval); $userAbo->update([ 'status' => 2, 'next_date' => $nextDate, 'last_date' => now(), ]); UserAboOrder::create([ 'user_abo_id' => $userAbo->id, 'shopping_order_id' => $shoppingOrder->id, 'status' => 1, 'paid' => true, ]); }); IncentiveTracker::trackAboActivated($shoppingOrder); $nextDateFormatted = Carbon::parse($userAbo->getRawOriginal('next_date'))->format('d.m.Y'); $this->info(" Status → 2 (abo_okay), nächstes Datum → {$nextDateFormatted}"); \Log::channel('abo_order')->info('RetryFailedPaypalAbos: Abo erfolgreich reaktiviert', [ 'abo_id' => $userAbo->id, 'order_id' => $shoppingOrder->id, 'next_date' => $userAbo->getRawOriginal('next_date'), ]); } private function markAboError(UserAbo $userAbo, $shoppingOrder): void { DB::transaction(function () use ($userAbo, $shoppingOrder) { $userAbo->update(['last_date' => now()]); UserAboOrder::create([ 'user_abo_id' => $userAbo->id, 'shopping_order_id' => $shoppingOrder->id, 'status' => 3, 'paid' => false, ]); }); } private function getExecutionTime(): string { $diff = microtime(true) - $this->timeStart; $sec = intval($diff); $micro = $diff - $sec; return $sec.' Sekunden und '.round($micro * 1000, 2).' ms'; } }