$n > 0))); if (empty($ids)) return []; $rows = DB::table('gc_products') ->select(['id', 'pin_check_methods', 'is_buyback_allowed']) ->whereIn('id', $ids) ->get(); $out = []; foreach ($rows as $r) { $raw = $r->pin_check_methods ?? null; $hasPinCheckMethods = !($raw === null || trim((string)$raw) === ''); $out[(int)$r->id] = [ 'pin_check_methods' => $this->decodeJsonArray($raw), 'has_pin_check_methods' => $hasPinCheckMethods, 'is_buyback_allowed' => (bool)($r->is_buyback_allowed ?? false), ]; } return $out; } private function decodeJsonArray($v): array { if ($v === null) return []; if (is_array($v)) return $v; $s = trim((string)$v); if ($s === '') return []; $decoded = json_decode($s, true); return is_array($decoded) ? $decoded : []; } public function findAttemptWithOrder(int $attemptId): ?object { return DB::table('gc_payment_attempts as a') ->leftJoin('gc_pin_order as o', 'o.id', '=', 'a.order_id') ->select([ 'a.id as attempt_id', 'a.provider as attempt_provider', 'a.oid as attempt_oid', 'a.mem_no as attempt_mem_no', 'a.order_id as attempt_order_id', 'a.pay_method as attempt_pay_method', 'a.status as attempt_status', 'a.pg_tid as attempt_pg_tid', 'a.return_code as attempt_return_code', 'a.return_msg as attempt_return_msg', 'a.request_payload as attempt_request_payload', 'a.response_payload as attempt_response_payload', 'a.return_payload as attempt_return_payload', 'a.noti_payload as attempt_noti_payload', 'a.created_at as attempt_created_at', 'a.updated_at as attempt_updated_at', // 추가: cancel_status 필드들 'a.cancel_status as attempt_cancel_status', 'a.cancel_requested_at as attempt_cancel_requested_at', 'a.cancel_done_at as attempt_cancel_done_at', 'a.cancel_last_code as attempt_cancel_last_code', 'a.cancel_last_msg as attempt_cancel_last_msg', 'o.id as order_id', 'o.oid as order_oid', 'o.mem_no as order_mem_no', 'o.stat_pay as order_stat_pay', 'o.products_name as order_product_name', 'o.products_id as order_product_id', 'o.provider as order_provider', 'o.pay_method as order_pay_method', 'o.pg_tid as order_pg_tid', 'o.ret_code as order_ret_code', 'o.ret_msg as order_ret_msg', 'o.subtotal_amount as order_subtotal_amount', 'o.fee_amount as order_fee_amount', 'o.pg_fee_amount as order_pg_fee_amount', 'o.pay_money as order_pay_money', 'o.pay_data as order_pay_data', 'o.ret_data as order_ret_data', 'o.created_at as order_created_at', 'o.updated_at as order_updated_at', // 추가: order cancel_status 필드들 'o.cancel_status as order_cancel_status', 'o.cancel_requested_at as order_cancel_requested_at', 'o.cancel_done_at as order_cancel_done_at', 'o.cancel_last_code as order_cancel_last_code', 'o.cancel_last_msg as order_cancel_last_msg', 'o.cancel_reason as order_cancel_reason', ]) ->where('a.id', $attemptId) ->first(); } /** * 리스트: 검색/페이징 */ public function paginateAttemptsWithOrder(int $memNo, array $filters, int $perPage = 20): LengthAwarePaginator { $q = trim((string)($filters['q'] ?? '')); $method = trim((string)($filters['method'] ?? '')); $status = trim((string)($filters['status'] ?? '')); $from = trim((string)($filters['from'] ?? '')); $to = trim((string)($filters['to'] ?? '')); // order_items 집계 서브쿼리 (group by를 메인 쿼리에서 피해서 paginate 안정) $oiAgg = DB::table('gc_pin_order_items') ->selectRaw('order_id, SUM(qty) as total_qty, MIN(item_name) as first_item_name') ->groupBy('order_id'); $qb = DB::table('gc_payment_attempts as a') ->leftJoin('gc_pin_order as o', 'o.id', '=', 'a.order_id') ->leftJoinSub($oiAgg, 'oi', 'oi.order_id', '=', 'o.id') ->where('a.mem_no', $memNo) // ✅ 중요: OR 조건 전체를 반드시 하나의 where 그룹으로 묶어야 함 ->where(function ($s) { // 1) 취소완료 $s->where(function ($x) { $x->where('a.cancel_status', 'success') ->orWhere('o.cancel_status', 'success'); }) // 2) 결제완료 ->orWhere(function ($x) { $x->where('a.status', 'paid') ->orWhere('o.stat_pay', 'p'); }) // 3) 입금대기(가상계좌) ->orWhere(function ($x) { $x->where('a.status', 'issued') ->orWhere('o.stat_pay', 'w'); }); }) ->select([ 'a.id as attempt_id', 'o.oid as order_oid', DB::raw("COALESCE(o.products_name, oi.first_item_name) as product_name"), 'oi.first_item_name as item_name', DB::raw("COALESCE(oi.total_qty, 0) as total_qty"), 'a.pay_method as pay_method', 'o.pay_money as pay_money', 'a.status as attempt_status', 'o.stat_pay as order_stat_pay', 'a.cancel_status as attempt_cancel_status', 'o.cancel_status as order_cancel_status', 'a.created_at as created_at', ]) ->orderByDesc('a.id'); // ✅ q 검색: 거래번호(o.oid / a.oid) 정확히 일치 if ($q !== '') { $qb->where(function ($w) use ($q) { $w->where('o.oid', $q) ->orWhere('a.oid', $q); }); } // 결제수단 if ($method !== '') { $qb->where('a.pay_method', $method); } // ✅ 상태 필터도 화면 의미에 맞게 묶어서 처리 // (현재 화면 기준: paid / issued / cancel를 의미 상태로 쓰는 경우) if ($status !== '') { if ($status === 'paid') { $qb->where(function ($x) { $x->where('a.status', 'paid') ->orWhere('o.stat_pay', 'p'); }); } elseif ($status === 'issued') { $qb->where(function ($x) { $x->where('a.status', 'issued') ->orWhere('o.stat_pay', 'w'); }); } elseif (in_array($status, ['cancel', 'cancelled', 'canceled'], true)) { $qb->where(function ($x) { $x->where('a.cancel_status', 'success') ->orWhere('o.cancel_status', 'success'); }); } else { // 기타 상태는 attempts 기준으로 그대로 $qb->where('a.status', $status); } } // 날짜 필터 (date 기준) if ($from !== '') { $qb->whereDate('a.created_at', '>=', $from); } if ($to !== '') { $qb->whereDate('a.created_at', '<=', $to); } return $qb->paginate($perPage)->appends($filters); } public function getOrderItems(int $orderId) { return DB::table('gc_pin_order_items') ->where('order_id', $orderId) ->orderBy('id', 'asc') ->get(); } public function countAssignedPins(int $orderId): int { return (int) DB::table('gc_pins') ->where('order_id', $orderId) ->count(); } public function getAssignedPinsStatusSummary(int $orderId): array { $rows = DB::table('gc_pins') ->selectRaw('status, COUNT(*) as cnt') ->where('order_id', $orderId) ->groupBy('status') ->get(); $out = []; foreach ($rows as $r) $out[(string)$r->status] = (int)$r->cnt; return $out; } /** * 핀 목록(오픈 전/후 표시용) — 핀 반납(홀드 해제)은 이번 범위 제외 */ public function getPinsForOrder(int $orderId): array { $rows = DB::table('gc_pins') ->where('order_id', $orderId) ->orderBy('id', 'asc') ->get(); return array_map(fn($r) => (array)$r, $rows->all()); } /** * 취소 로그 조회 */ public function getCancelLogsForAttempt(int $attemptId, int $limit = 20): array { $rows = DB::table('gc_payment_cancel_logs') ->where('attempt_id', $attemptId) ->orderByDesc('id') ->limit($limit) ->get(); return array_map(fn($r) => (array)$r, $rows->all()); } }