subMinutes($staleMinutes); $orders = TicketOrder::query() ->where('status', 'pending_confirm') ->where('updated_at', '<=', $cutoff) ->orderBy('id') ->limit($limit) ->get(); $summary = ['scanned' => 0, 'confirmed' => 0, 'refunded' => 0]; foreach ($orders as $order) { $result = DB::transaction(function () use ($order): string { $lockedOrder = TicketOrder::query() ->whereKey($order->id) ->lockForUpdate() ->first(); if ($lockedOrder === null || $lockedOrder->status !== 'pending_confirm') { return 'skipped'; } $hasPostedDeduct = WalletTxn::query() ->where('biz_type', 'bet_deduct') ->where('biz_no', $lockedOrder->order_no) ->where('status', 'posted') ->exists(); if ($hasPostedDeduct) { TicketItem::query() ->where('order_id', $lockedOrder->id) ->where('status', 'pending_confirm') ->update([ 'status' => 'success', 'fail_reason_code' => null, 'fail_reason_text' => null, 'updated_at' => now(), ]); $lockedOrder->forceFill(['status' => 'placed'])->save(); return 'confirmed'; } $items = TicketItem::query() ->where('order_id', $lockedOrder->id) ->where('status', 'pending_confirm') ->with('combinations') ->lockForUpdate() ->get(); foreach ($items as $item) { $locks = []; foreach ($item->combinations as $combo) { $locks[] = [ 'number_4d' => (string) $combo->number_4d, 'amount' => (int) $combo->estimated_payout, ]; } if ($locks !== []) { $this->riskPool->release((int) $lockedOrder->draw_id, $item, $locks); } $item->forceFill([ 'status' => 'refunded', 'fail_reason_code' => 'pending_confirm_timeout', 'fail_reason_text' => 'pending_confirm_timeout_refund', 'risk_locked_amount' => 0, ])->save(); } $lockedOrder->forceFill(['status' => 'refunded'])->save(); return 'refunded'; }); if ($result === 'skipped') { continue; } $summary['scanned']++; if ($result === 'confirmed') { $summary['confirmed']++; } if ($result === 'refunded') { $summary['refunded']++; } } return $summary; } }