toDateString(); DB::table('mem_auth')->updateOrInsert( ['mem_no' => $memNo, 'auth_type' => $authType], ['auth_state' => $authState, 'auth_date' => $authDate] ); } public function markRequested(int $memNo, string $authType, array $logInfo = []): void { $this->setStateWithLog($memNo, $authType, MemAuth::STATE_R, MemAuthLog::STATE_P, $logInfo); } public function markProcessing(int $memNo, string $authType, array $logInfo = []): void { $this->setStateWithLog($memNo, $authType, MemAuth::STATE_P, MemAuthLog::STATE_P, $logInfo); } public function markSuccess(int $memNo, string $authType, array $logInfo = []): void { $this->setStateWithLog($memNo, $authType, MemAuth::STATE_Y, MemAuthLog::STATE_S, $logInfo); } public function markFail(int $memNo, string $authType, array $logInfo = []): void { $this->setStateWithLog($memNo, $authType, MemAuth::STATE_N, MemAuthLog::STATE_F, $logInfo); } public function mergeAuthInfo(int $memNo, string $authType, array $payload): void { DB::transaction(function () use ($memNo, $authType, $payload) { $row = MemAuthInfo::query()->find($memNo); if (!$row) { $row = new MemAuthInfo(); $row->mem_no = $memNo; $row->auth_info = []; } $data = $row->auth_info ?: []; $data[$authType] = array_merge($data[$authType] ?? [], $payload); $row->auth_info = $data; $row->save(); }); } private function setStateWithLog( int $memNo, string $authType, string $authState, string $logState, array $logInfo ): void { DB::transaction(function () use ($memNo, $authType, $authState, $logState, $logInfo) { $this->upsertState($memNo, $authType, $authState); MemAuthLog::query()->create([ 'mem_no' => $memNo, 'type' => $authType, 'state' => $logState, 'info' => $logInfo, 'rgdate' => Carbon::now()->toDateTimeString(), ]); }); } public function getState(int $memNo, string $authType): ?string { return DB::table('mem_auth') ->where('mem_no', $memNo) ->where('auth_type', $authType) ->value('auth_state'); } public function isVerified(int $memNo, string $authType): bool { return $this->getState($memNo, $authType) === MemAuth::STATE_Y; } /* ========================================================= * Step0: phone check + join_filter + join_log * ========================================================= */ public function normalizeKoreanPhone(string $raw): ?string { $digits = preg_replace('/\D+/', '', $raw ?? ''); if (!$digits) return null; // 82 국제형 → 0 시작으로 변환 if (str_starts_with($digits, '82')) { $digits = '0' . substr($digits, 2); } // 010/011/016/017/018/019 + 10~11자리 if (!preg_match('/^01[016789]\d{7,8}$/', $digits)) { return null; } return $digits; } public function ipToCClass(string $ip): string { if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return ''; } $p = explode('.', $ip); return count($p) === 4 ? ($p[0] . '.' . $p[1] . '.' . $p[2] . '.0') : ''; } public function isAlreadyMemberByPhone(string $phone): bool { return MemInfo::query() ->where('cell_phone', $phone) ->where('dt_out', '0000-00-00 00:00:00') ->exists(); } /** * filter 컬럼에 phone/ip/ip_c가 들어있다는 전제의 기본 구현. * - join_block: A 차단 / S 주의(알림) / N 비활성 */ public function checkJoinFilter(string $phone, string $ip4 = '', string $ip4c = ''): ?array { $targets = array_values(array_filter([$phone, $ip4, $ip4c])); if (!$targets) return null; $rows = MemJoinFilter::query() ->whereIn('filter', $targets) ->where(function ($q) { $q->whereNull('join_block')->orWhere('join_block', '!=', 'N'); }) ->orderByDesc('seq') ->get(); if ($rows->isEmpty()) return null; foreach ($rows as $r) { if ((string)$r->join_block === 'A') { return ['hit' => true, 'block' => true, 'gubun' => $r->gubun ?? 'filter_block', 'row' => $r]; } } foreach ($rows as $r) { if ((string)$r->join_block === 'S') { return ['hit' => true, 'block' => false, 'gubun' => $r->gubun ?? 'filter_notice', 'row' => $r]; } } $r = $rows->first(); return ['hit' => true, 'block' => false, 'gubun' => $r->gubun ?? 'filter_hit', 'row' => $r]; } public function writeJoinLog(array $data): void { MemJoinLog::query()->create([ 'gubun' => $data['gubun'] ?? null, 'mem_no' => (int)($data['mem_no'] ?? 0), 'cell_corp' => $data['cell_corp'] ?? 'n', 'cell_phone' => $data['cell_phone'] ?? '', 'email' => $data['email'] ?? null, 'ip4' => $data['ip4'] ?? '', 'ip4_c' => $data['ip4_c'] ?? '', 'error_code' => $data['error_code'] ?? '', 'dt_reg' => Carbon::now()->toDateTimeString(), ]); } /** * Step0 통합 처리 */ public function step0PhoneCheck(string $rawPhone, string $ip4 = ''): array { $phone = $this->normalizeKoreanPhone($rawPhone); if (!$phone) { return [ 'ok' => false, 'reason' => 'invalid_phone', 'message' => '휴대폰 번호 형식이 올바르지 않습니다.', ]; } $ip4c = $this->ipToCClass($ip4); // already member if ($this->isAlreadyMemberByPhone($phone)) { $this->writeJoinLog([ 'gubun' => 'already_member', 'mem_no' => 0, 'cell_phone' => $phone, 'ip4' => $ip4, 'ip4_c' => $ip4c, 'error_code' => 'J2', ]); return ['ok' => true, 'reason' => 'already_member', 'phone' => $phone]; } // join filter $filter = $this->checkJoinFilter($phone, $ip4, $ip4c); if ($filter && ($filter['block'] ?? false) === true) { $this->writeJoinLog([ 'gubun' => $filter['gubun'] ?? 'filter_block', 'mem_no' => 0, 'cell_phone' => $phone, 'ip4' => $ip4, 'ip4_c' => $ip4c, 'error_code' => 'J1', ]); return [ 'ok' => false, 'reason' => 'blocked', 'phone' => $phone, 'filter' => $filter, 'message' => '현재 가입이 제한된 정보입니다. 고객센터로 문의해 주세요.', ]; } // pass $this->writeJoinLog([ 'gubun' => $filter['gubun'] ?? 'ok', 'mem_no' => 0, 'cell_phone' => $phone, 'ip4' => $ip4, 'ip4_c' => $ip4c, 'error_code' => 'J0', ]); return ['ok' => true, 'reason' => 'ok', 'phone' => $phone, 'filter' => $filter]; } }