297 lines
9.6 KiB
PHP
297 lines
9.6 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Cron\UserMakeOrder;
|
|
use App\Models\UserAbo;
|
|
use App\Models\UserAboOrder;
|
|
use App\Services\AboHelper;
|
|
use App\Services\Incentive\IncentiveTracker;
|
|
use App\Services\MyLog;
|
|
use App\Services\Payment;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class RetryFailedPaypalAbos extends Command
|
|
{
|
|
protected $signature = 'abo:retry-failed-paypal
|
|
{--dry-run : Nur anzeigen, keine Bestellungen ausführen}
|
|
{--abo-id= : Nur ein bestimmtes Abo erneut ausführen}';
|
|
|
|
protected $description = 'Führt Abo-Bestellungen erneut aus, die aufgrund der PayPal-Panne (Error 923) fehlgeschlagen sind';
|
|
|
|
private float $timeStart;
|
|
|
|
public function handle(): int
|
|
{
|
|
$this->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<int, UserAbo>
|
|
*/
|
|
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';
|
|
}
|
|
}
|