Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReleaseExpiredOnlineOrders
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 1
 handle
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace App\Console\Commands;
4
5use App\Enums\OrderStatus;
6use App\Enums\PaymentStatus;
7use App\Models\Payment;
8use App\Services\Stock\InventoryService;
9use Illuminate\Console\Command;
10use Illuminate\Support\Facades\DB;
11
12class ReleaseExpiredOnlineOrders extends Command
13{
14    protected $signature = 'orders:release-expired-stock';
15
16    protected $description = 'Cancel expired unpaid online orders and return their reserved stock';
17
18    public function handle(InventoryService $inventory): int
19    {
20        $minutes = max(5, (int) settings('checkout.unpaid_order_hold_minutes', 30));
21        $released = 0;
22
23        Payment::query()
24            ->whereNotNull('gateway')
25            ->where('status', PaymentStatus::Pending->value)
26            ->where('created_at', '<=', now()->subMinutes($minutes))
27            ->with('order')
28            ->chunkById(100, function ($payments) use ($inventory, &$released) {
29                foreach ($payments as $payment) {
30                    DB::transaction(function () use ($payment, $inventory, &$released) {
31                        $payment = Payment::lockForUpdate()->find($payment->id);
32                        $order = $payment?->order()->lockForUpdate()->first();
33                        if (! $payment || ! $order || $payment->status !== PaymentStatus::Pending->value
34                            || in_array($order->status, [OrderStatus::Cancelled->value, OrderStatus::Returned->value, OrderStatus::Refunded->value], true)) {
35                            return;
36                        }
37                        $inventory->restoreOrder($order);
38                        $order->update(['status' => OrderStatus::Cancelled->value]);
39                        $payment->update([
40                            'status' => PaymentStatus::Failed->value,
41                            'payment_note' => trim(($payment->payment_note ? $payment->payment_note . '; ' : '') . 'Expired before payment confirmation; stock released.'),
42                        ]);
43                        $released++;
44                    });
45                }
46            });
47
48        $this->info("Released stock for {$released} expired online order(s).");
49
50        return self::SUCCESS;
51    }
52}