subMinutes(30)->toDateTimeString(); $affectedTimeout = DB::update(" UPDATE gc_payment_attempts a JOIN gc_pin_order o ON o.oid = a.oid SET a.status='cancelled', a.return_code='TIMEOUT', a.return_msg='결제시간초과', a.returned_at=NOW(), a.updated_at=NOW(), o.stat_pay='c', o.ret_code='TIMEOUT', o.ret_msg='결제시간초과', o.cancelled_at=NOW(), o.updated_at=NOW() WHERE a.status='redirected' AND a.redirected_at IS NOT NULL AND a.redirected_at < ? AND o.stat_pay='ready' ", [$cutoffRedirect]); // 2) 아카이브 대상 attempt 뽑기 $rows = DB::select(" SELECT a.id, a.oid FROM gc_payment_attempts a WHERE a.status IN ('cancelled','failed') OR ( a.status = 'redirected' AND a.redirected_at IS NOT NULL AND a.redirected_at < ? ) ORDER BY a.id LIMIT {$batch} ", [$cutoffRedirect]); if (!$rows) { return [ 'timeout_updated' => $affectedTimeout, 'archived_attempts' => 0, 'archived_orders' => 0, 'deleted_orders' => 0, ]; } $ids = array_map(fn($r) => (int)$r->id, $rows); $oids = array_values(array_unique(array_map(fn($r) => (string)$r->oid, $rows))); $placeIds = implode(',', array_fill(0, count($ids), '?')); $placeOids = implode(',', array_fill(0, count($oids), '?')); // 3) attempts → attempts_bak DB::insert(" INSERT IGNORE INTO gc_payment_attempts_bak SELECT a.*, NOW(), ?, CASE WHEN a.return_code='TIMEOUT' THEN 'timeout' ELSE 'old' END FROM gc_payment_attempts a WHERE a.id IN ($placeIds) ", array_merge([$batchId], $ids)); // 4) orders → orders_bak (oid 기준) DB::insert(" INSERT IGNORE INTO gc_pin_order_bak SELECT o.*, NOW(), ?, 'linked' FROM gc_pin_order o WHERE o.oid IN ($placeOids) ", array_merge([$batchId], $oids)); // 5) items → items_bak (order_id 기준, oid로 join) DB::insert(" INSERT IGNORE INTO gc_pin_order_items_bak SELECT i.*, NOW(), ?, 'linked' FROM gc_pin_order_items i JOIN gc_pin_order o ON o.id = i.order_id WHERE o.oid IN ($placeOids) ", array_merge([$batchId], $oids)); // 6) 원본 attempts 삭제 DB::delete("DELETE FROM gc_payment_attempts WHERE id IN ($placeIds)", $ids); // 7) 원본 order/items 삭제는 “해당 oid에 남은 attempt가 없을 때만” 삭제(중요) $orderRows = DB::select(" SELECT o.id FROM gc_pin_order o WHERE o.oid IN ($placeOids) AND NOT EXISTS (SELECT 1 FROM gc_payment_attempts a WHERE a.oid=o.oid) ", $oids); $orderIds = array_map(fn($r) => (int)$r->id, $orderRows); $deletedOrders = 0; if ($orderIds) { $placeOrderIds = implode(',', array_fill(0, count($orderIds), '?')); DB::delete("DELETE FROM gc_pin_order_items WHERE order_id IN ($placeOrderIds)", $orderIds); $deletedOrders = DB::delete("DELETE FROM gc_pin_order WHERE id IN ($placeOrderIds)", $orderIds); } Log::info('[payments:archive] done', [ 'batch' => $batchId, 'timeout_updated' => $affectedTimeout, 'attempts' => count($ids), 'oids' => count($oids), 'deleted_orders' => $deletedOrders, ]); return [ 'timeout_updated' => $affectedTimeout, 'archived_attempts' => count($ids), 'archived_orders' => count($oids), 'deleted_orders' => $deletedOrders, ]; }); } }