10.April 2026
This commit is contained in:
parent
a00c42e770
commit
f58c709945
208 changed files with 19280 additions and 2914 deletions
297
app/Console/Commands/RetryFailedPaypalAbos.php
Normal file
297
app/Console/Commands/RetryFailedPaypalAbos.php
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
<?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';
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue