From 7ed908f53db4deb1660b13dade5e89a4fc5a3c51 Mon Sep 17 00:00:00 2001 From: pro Date: Thu, 22 Jan 2026 19:33:51 +0900 Subject: [PATCH] =?UTF-8?q?feat:=EC=BF=A0=EC=BD=98=20API=20=EC=8B=A0?= =?UTF-8?q?=EC=9A=A9=ED=8F=89=EA=B0=80=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CooconConfig 모델 및 마이그레이션 추가 - CooconService 클래스 구현 (OA12~OA17 API) - CreditController 확장 (설정 관리, 조회 기능) - 설정 관리 화면 추가 (CRUD, 활성화 토글) - 사업자번호 조회 화면 업데이트 (API 연동) Co-Authored-By: Claude Opus 4.5 --- .../Controllers/Credit/CreditController.php | 219 ++++++++- app/Models/Coocon/CooconConfig.php | 107 +++++ app/Services/Coocon/CooconService.php | 311 +++++++++++++ ..._22_192637_create_coocon_configs_table.php | 34 ++ .../views/credit/inquiry/index.blade.php | 368 ++++++++++++++- .../views/credit/settings/edit.blade.php | 12 + .../views/credit/settings/index.blade.php | 429 ++++++++++++++++++ routes/web.php | 12 + 8 files changed, 1466 insertions(+), 26 deletions(-) create mode 100644 app/Models/Coocon/CooconConfig.php create mode 100644 app/Services/Coocon/CooconService.php create mode 100644 database/migrations/2026_01_22_192637_create_coocon_configs_table.php create mode 100644 resources/views/credit/settings/edit.blade.php create mode 100644 resources/views/credit/settings/index.blade.php diff --git a/app/Http/Controllers/Credit/CreditController.php b/app/Http/Controllers/Credit/CreditController.php index 6c3d1ed8..d2d6ef55 100644 --- a/app/Http/Controllers/Credit/CreditController.php +++ b/app/Http/Controllers/Credit/CreditController.php @@ -3,6 +3,9 @@ namespace App\Http\Controllers\Credit; use App\Http\Controllers\Controller; +use App\Models\Coocon\CooconConfig; +use App\Services\Coocon\CooconService; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\View\View; @@ -22,6 +25,220 @@ public function inquiry(Request $request): View|Response return response('', 200)->header('HX-Redirect', route('credit.inquiry.index')); } - return view('credit.inquiry.index'); + $service = new CooconService(); + $hasConfig = $service->hasConfig(); + + return view('credit.inquiry.index', [ + 'hasConfig' => $hasConfig, + 'apiTypes' => CooconService::API_NAMES, + ]); + } + + /** + * 신용정보 조회 API + */ + public function search(Request $request): JsonResponse + { + $request->validate([ + 'company_key' => 'required|string|max:20', + 'api_type' => 'nullable|string|in:OA12,OA13,OA14,OA15,OA16,OA17,all', + ]); + + $companyKey = $request->input('company_key'); + $apiType = $request->input('api_type', 'all'); + + $service = new CooconService(); + + if (!$service->hasConfig()) { + return response()->json([ + 'success' => false, + 'error' => '쿠콘 API 설정이 없습니다. 설정을 먼저 등록해주세요.', + ], 400); + } + + $result = match ($apiType) { + 'OA12' => ['summary' => $service->getCreditSummary($companyKey)], + 'OA13' => ['shortTermOverdue' => $service->getShortTermOverdueInfo($companyKey)], + 'OA14' => ['negativeInfoKCI' => $service->getNegativeInfoKCI($companyKey)], + 'OA15' => ['negativeInfoCB' => $service->getNegativeInfoCB($companyKey)], + 'OA16' => ['suspensionInfo' => $service->getSuspensionInfo($companyKey)], + 'OA17' => ['workoutInfo' => $service->getWorkoutInfo($companyKey)], + default => $service->getAllCreditInfo($companyKey), + }; + + return response()->json([ + 'success' => true, + 'data' => $result, + 'company_key' => $companyKey, + 'api_type' => $apiType, + ]); + } + + /** + * 설정 관리 페이지 + */ + public function settings(Request $request): View|Response + { + if ($request->header('HX-Request')) { + return response('', 200)->header('HX-Redirect', route('credit.settings.index')); + } + + $configs = CooconConfig::orderBy('environment') + ->orderBy('is_active', 'desc') + ->orderBy('created_at', 'desc') + ->get(); + + return view('credit.settings.index', [ + 'configs' => $configs, + ]); + } + + /** + * 설정 생성 폼 + */ + public function createConfig(): View + { + return view('credit.settings.create'); + } + + /** + * 설정 저장 + */ + public function storeConfig(Request $request): JsonResponse + { + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'environment' => 'required|in:test,production', + 'api_key' => 'required|string|max:100', + 'base_url' => 'required|url|max:255', + 'description' => 'nullable|string', + 'is_active' => 'boolean', + ]); + + // 같은 환경에서 활성화된 설정이 이미 있으면 비활성화 + if ($validated['is_active'] ?? false) { + CooconConfig::where('environment', $validated['environment']) + ->where('is_active', true) + ->update(['is_active' => false]); + } + + $config = CooconConfig::create($validated); + + return response()->json([ + 'success' => true, + 'message' => '설정이 저장되었습니다.', + 'data' => $config, + ]); + } + + /** + * 설정 수정 폼 + */ + public function editConfig(int $id): View + { + $config = CooconConfig::findOrFail($id); + + return view('credit.settings.edit', [ + 'config' => $config, + ]); + } + + /** + * 설정 업데이트 + */ + public function updateConfig(Request $request, int $id): JsonResponse + { + $config = CooconConfig::findOrFail($id); + + $validated = $request->validate([ + 'name' => 'required|string|max:100', + 'environment' => 'required|in:test,production', + 'api_key' => 'required|string|max:100', + 'base_url' => 'required|url|max:255', + 'description' => 'nullable|string', + 'is_active' => 'boolean', + ]); + + // 같은 환경에서 활성화된 설정이 이미 있으면 비활성화 (자기 자신 제외) + if ($validated['is_active'] ?? false) { + CooconConfig::where('environment', $validated['environment']) + ->where('is_active', true) + ->where('id', '!=', $id) + ->update(['is_active' => false]); + } + + $config->update($validated); + + return response()->json([ + 'success' => true, + 'message' => '설정이 수정되었습니다.', + 'data' => $config, + ]); + } + + /** + * 설정 삭제 + */ + public function deleteConfig(int $id): JsonResponse + { + $config = CooconConfig::findOrFail($id); + $config->delete(); + + return response()->json([ + 'success' => true, + 'message' => '설정이 삭제되었습니다.', + ]); + } + + /** + * 설정 활성화/비활성화 토글 + */ + public function toggleConfig(int $id): JsonResponse + { + $config = CooconConfig::findOrFail($id); + + if (!$config->is_active) { + // 같은 환경에서 활성화된 설정이 이미 있으면 비활성화 + CooconConfig::where('environment', $config->environment) + ->where('is_active', true) + ->update(['is_active' => false]); + } + + $config->update(['is_active' => !$config->is_active]); + + return response()->json([ + 'success' => true, + 'message' => $config->is_active ? '설정이 활성화되었습니다.' : '설정이 비활성화되었습니다.', + 'is_active' => $config->is_active, + ]); + } + + /** + * API 연결 테스트 + */ + public function testConnection(Request $request): JsonResponse + { + $request->validate([ + 'company_key' => 'required|string|max:20', + ]); + + $companyKey = $request->input('company_key'); + $service = new CooconService(); + + if (!$service->hasConfig()) { + return response()->json([ + 'success' => false, + 'error' => '쿠콘 API 설정이 없습니다.', + ], 400); + } + + // 신용요약정보 API로 테스트 + $result = $service->getCreditSummary($companyKey); + + return response()->json([ + 'success' => $result['success'], + 'message' => $result['success'] ? 'API 연결 테스트 성공' : 'API 연결 테스트 실패', + 'result' => $result, + ]); } } diff --git a/app/Models/Coocon/CooconConfig.php b/app/Models/Coocon/CooconConfig.php new file mode 100644 index 00000000..e62c607e --- /dev/null +++ b/app/Models/Coocon/CooconConfig.php @@ -0,0 +1,107 @@ + 'boolean', + ]; + + /** + * 활성화된 테스트 서버 설정 조회 + */ + public static function getActiveTest(): ?self + { + return self::where('environment', 'test') + ->where('is_active', true) + ->first(); + } + + /** + * 활성화된 운영 서버 설정 조회 + */ + public static function getActiveProduction(): ?self + { + return self::where('environment', 'production') + ->where('is_active', true) + ->first(); + } + + /** + * 현재 환경에 맞는 활성 설정 조회 + */ + public static function getActive(bool $isTestMode = false): ?self + { + return $isTestMode ? self::getActiveTest() : self::getActiveProduction(); + } + + /** + * 환경 라벨 + */ + public function getEnvironmentLabelAttribute(): string + { + return match ($this->environment) { + 'test' => '테스트서버', + 'production' => '운영서버', + default => $this->environment, + }; + } + + /** + * 상태 라벨 + */ + public function getStatusLabelAttribute(): string + { + return $this->is_active ? '활성' : '비활성'; + } + + /** + * 상태 색상 (Tailwind) + */ + public function getStatusColorAttribute(): string + { + return $this->is_active ? 'green' : 'gray'; + } + + /** + * 마스킹된 API 키 (앞 8자리만 표시) + */ + public function getMaskedApiKeyAttribute(): string + { + if (strlen($this->api_key) <= 8) { + return $this->api_key; + } + + return substr($this->api_key, 0, 8) . str_repeat('*', 8) . '...'; + } +} diff --git a/app/Services/Coocon/CooconService.php b/app/Services/Coocon/CooconService.php new file mode 100644 index 00000000..bfea0bd3 --- /dev/null +++ b/app/Services/Coocon/CooconService.php @@ -0,0 +1,311 @@ + '신용요약정보', + self::API_SHORT_TERM_OVERDUE => '단기연체정보 (한국신용정보원)', + self::API_NEGATIVE_INFO_KCI => '신용도판단정보 (한국신용정보원)', + self::API_NEGATIVE_INFO_CB => '신용도판단정보 (신용정보사)', + self::API_SUSPENSION_INFO => '당좌거래정지정보 (금융결제원)', + self::API_WORKOUT_INFO => '법정관리/워크아웃정보', + ]; + + /** + * 기본 URL + */ + private const BASE_URL_TEST = 'https://dev2.coocon.co.kr:8443/sol/gateway/oapi_relay.jsp'; + private const BASE_URL_PRODUCTION = 'https://sgw.coocon.co.kr/sol/gateway/oapi_relay.jsp'; + + public function __construct(bool $isTestMode = true) + { + $this->isTestMode = $isTestMode; + $this->loadConfig(); + } + + /** + * 설정 로드 + */ + private function loadConfig(): void + { + $this->config = CooconConfig::getActive($this->isTestMode); + } + + /** + * 설정 재로드 + */ + public function reloadConfig(): self + { + $this->loadConfig(); + return $this; + } + + /** + * 테스트 모드 설정 + */ + public function setTestMode(bool $isTestMode): self + { + $this->isTestMode = $isTestMode; + $this->loadConfig(); + return $this; + } + + /** + * 설정 존재 여부 + */ + public function hasConfig(): bool + { + return $this->config !== null; + } + + /** + * 현재 설정 조회 + */ + public function getConfig(): ?CooconConfig + { + return $this->config; + } + + /** + * API URL 조회 + */ + private function getBaseUrl(): string + { + if ($this->config && $this->config->base_url) { + return $this->config->base_url; + } + + return $this->isTestMode ? self::BASE_URL_TEST : self::BASE_URL_PRODUCTION; + } + + /** + * API 호출 공통 메서드 + */ + private function callApi(string $apiId, array $params = []): array + { + if (!$this->config) { + return [ + 'success' => false, + 'error' => '쿠콘 API 설정이 없습니다. 설정을 먼저 등록해주세요.', + 'code' => 'NO_CONFIG', + ]; + } + + $url = $this->getBaseUrl(); + $trSeq = $this->generateTransactionSequence(); + + $requestData = array_merge([ + 'API_KEY' => $this->config->api_key, + 'API_ID' => $apiId, + 'TR_SEQ' => $trSeq, + ], $params); + + Log::info('쿠콘 API 호출', [ + 'api_id' => $apiId, + 'tr_seq' => $trSeq, + 'url' => $url, + 'params' => array_merge($params, ['API_KEY' => '***masked***']), + ]); + + try { + $response = Http::timeout(30) + ->withHeaders([ + 'Content-Type' => 'application/json', + ]) + ->post($url, $requestData); + + $result = $response->json(); + + Log::info('쿠콘 API 응답', [ + 'api_id' => $apiId, + 'tr_seq' => $trSeq, + 'rslt_cd' => $result['RSLT_CD'] ?? 'N/A', + 'rslt_msg' => $result['RSLT_MSG'] ?? 'N/A', + ]); + + if (!$response->successful()) { + return [ + 'success' => false, + 'error' => 'HTTP 오류: ' . $response->status(), + 'code' => 'HTTP_ERROR', + 'http_status' => $response->status(), + ]; + } + + $rsltCode = $result['RSLT_CD'] ?? ''; + if ($rsltCode === '00000000') { + return [ + 'success' => true, + 'data' => $result['RSLT_DATA'] ?? [], + 'tr_seq' => $result['TR_SEQ'] ?? $trSeq, + 'message' => $result['RSLT_MSG'] ?? '정상처리되었습니다.', + 'raw' => $result, + ]; + } + + return [ + 'success' => false, + 'error' => $result['RSLT_MSG'] ?? '알 수 없는 오류', + 'code' => $rsltCode, + 'raw' => $result, + ]; + } catch (\Exception $e) { + Log::error('쿠콘 API 호출 실패', [ + 'api_id' => $apiId, + 'tr_seq' => $trSeq, + 'error' => $e->getMessage(), + ]); + + return [ + 'success' => false, + 'error' => '쿠콘 API 호출 중 오류가 발생했습니다: ' . $e->getMessage(), + 'code' => 'EXCEPTION', + ]; + } + } + + /** + * 거래일련번호 생성 + */ + private function generateTransactionSequence(): string + { + return date('YmdHis') . substr(microtime(), 2, 6); + } + + /** + * 신용요약정보 조회 (OA12) + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + */ + public function getCreditSummary(string $companyKey): array + { + return $this->callApi(self::API_CREDIT_SUMMARY, [ + 'Companykey' => $companyKey, + ]); + } + + /** + * 단기연체정보 조회 (OA13) - 한국신용정보원 + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param string|null $reqDate 기준일자 (YYYYMMDD), 미입력시 현재 날짜 + */ + public function getShortTermOverdueInfo(string $companyKey, ?string $reqDate = null): array + { + $params = ['Companykey' => $companyKey]; + + if ($reqDate) { + $params['reqDate'] = $reqDate; + } + + return $this->callApi(self::API_SHORT_TERM_OVERDUE, $params); + } + + /** + * 신용도판단정보 조회 (OA14) - 한국신용정보원 (공공정보 포함) + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + */ + public function getNegativeInfoKCI(string $companyKey): array + { + return $this->callApi(self::API_NEGATIVE_INFO_KCI, [ + 'Companykey' => $companyKey, + ]); + } + + /** + * 신용도판단정보 조회 (OA15) - 신용정보사 + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + */ + public function getNegativeInfoCB(string $companyKey): array + { + return $this->callApi(self::API_NEGATIVE_INFO_CB, [ + 'Companykey' => $companyKey, + ]); + } + + /** + * 당좌거래정지정보 조회 (OA16) - 금융결제원 + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + */ + public function getSuspensionInfo(string $companyKey): array + { + return $this->callApi(self::API_SUSPENSION_INFO, [ + 'Companykey' => $companyKey, + ]); + } + + /** + * 법정관리/워크아웃정보 조회 (OA17) + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + * @param int $pageNo 페이지 번호 + * @param int $pageSize 페이지 사이즈 + */ + public function getWorkoutInfo(string $companyKey, int $pageNo = 1, int $pageSize = 10): array + { + return $this->callApi(self::API_WORKOUT_INFO, [ + 'Companykey' => $companyKey, + 'pageNo' => (string) $pageNo, + 'pageSize' => (string) $pageSize, + ]); + } + + /** + * 전체 신용정보 조회 (모든 API 호출) + * + * @param string $companyKey 사업자번호, 법인번호, 업체코드 중 하나 + */ + public function getAllCreditInfo(string $companyKey): array + { + $results = []; + + // 신용요약정보 + $results['summary'] = $this->getCreditSummary($companyKey); + + // 단기연체정보 + $results['shortTermOverdue'] = $this->getShortTermOverdueInfo($companyKey); + + // 신용도판단정보 (한국신용정보원) + $results['negativeInfoKCI'] = $this->getNegativeInfoKCI($companyKey); + + // 신용도판단정보 (신용정보사) + $results['negativeInfoCB'] = $this->getNegativeInfoCB($companyKey); + + // 당좌거래정지정보 + $results['suspensionInfo'] = $this->getSuspensionInfo($companyKey); + + // 법정관리/워크아웃정보 + $results['workoutInfo'] = $this->getWorkoutInfo($companyKey); + + return $results; + } +} diff --git a/database/migrations/2026_01_22_192637_create_coocon_configs_table.php b/database/migrations/2026_01_22_192637_create_coocon_configs_table.php new file mode 100644 index 00000000..8fa30504 --- /dev/null +++ b/database/migrations/2026_01_22_192637_create_coocon_configs_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 100)->comment('설정 이름'); + $table->enum('environment', ['test', 'production'])->default('test')->comment('환경'); + $table->string('api_key', 100)->comment('API 키'); + $table->string('base_url', 255)->comment('API 기본 URL'); + $table->text('description')->nullable()->comment('설명'); + $table->boolean('is_active')->default(false)->comment('활성화 여부'); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('coocon_configs'); + } +}; diff --git a/resources/views/credit/inquiry/index.blade.php b/resources/views/credit/inquiry/index.blade.php index 240c4a7c..ac25440b 100644 --- a/resources/views/credit/inquiry/index.blade.php +++ b/resources/views/credit/inquiry/index.blade.php @@ -8,40 +8,75 @@

신용평가 조회

-

기업 신용평가 정보를 조회합니다

+

기업 신용평가 정보를 조회합니다 (쿠콘 API)

+
+ + + + + + API 설정 + +
+ + @if(!$hasConfig) + +
+
+ + + +
+

쿠콘 API 설정이 필요합니다

+

신용평가 조회를 위해 먼저 API 설정을 등록해주세요.

+
+ @endif
- + + name="company_key" + id="companyKeyInput" + placeholder="사업자번호 또는 법인번호 입력" + class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + {{ !$hasConfig ? 'disabled' : '' }}>
- -
- - + +
+ +
-
@@ -50,12 +85,17 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
-
+

신용평가 조회

-

사업자번호 또는 상호명을 입력하여 조회하세요

+

사업자번호 또는 법인번호를 입력하여 조회하세요

+
+ + +
@@ -63,19 +103,297 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc @push('scripts') @endpush diff --git a/resources/views/credit/settings/edit.blade.php b/resources/views/credit/settings/edit.blade.php new file mode 100644 index 00000000..f8e89b75 --- /dev/null +++ b/resources/views/credit/settings/edit.blade.php @@ -0,0 +1,12 @@ +{{-- 수정 폼에 사용할 데이터 (JavaScript에서 파싱) --}} + diff --git a/resources/views/credit/settings/index.blade.php b/resources/views/credit/settings/index.blade.php new file mode 100644 index 00000000..134979b8 --- /dev/null +++ b/resources/views/credit/settings/index.blade.php @@ -0,0 +1,429 @@ +@extends('layouts.app') + +@section('title', '쿠콘 API 설정') + +@section('content') +
+ +
+
+

쿠콘 API 설정

+

신용평가 API 연동을 위한 쿠콘 설정을 관리합니다

+
+ +
+ + +
+ @if($configs->isEmpty()) +
+ + + + +

등록된 설정이 없습니다

+

새 설정을 추가하여 쿠콘 API를 연동하세요

+
+ @else +
+ + + + + + + + + + + + + @foreach($configs as $config) + + + + + + + + + @endforeach + +
설정명환경API Key상태등록일관리
+
+
{{ $config->name }}
+ @if($config->description) +
{{ Str::limit($config->description, 50) }}
+ @endif +
+
+ + {{ $config->environment_label }} + + + {{ $config->masked_api_key }} + + + {{ $config->status_label }} + + + {{ $config->created_at->format('Y-m-d H:i') }} + +
+ + + +
+
+
+ @endif +
+
+ + + + + + +@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index c5f2b11d..45b8a450 100644 --- a/routes/web.php +++ b/routes/web.php @@ -285,7 +285,19 @@ |-------------------------------------------------------------------------- */ Route::prefix('credit')->name('credit.')->group(function () { + // 조회 Route::get('/inquiry', [\App\Http\Controllers\Credit\CreditController::class, 'inquiry'])->name('inquiry.index'); + Route::post('/inquiry/search', [\App\Http\Controllers\Credit\CreditController::class, 'search'])->name('inquiry.search'); + Route::post('/inquiry/test', [\App\Http\Controllers\Credit\CreditController::class, 'testConnection'])->name('inquiry.test'); + + // 설정 관리 + Route::get('/settings', [\App\Http\Controllers\Credit\CreditController::class, 'settings'])->name('settings.index'); + Route::get('/settings/create', [\App\Http\Controllers\Credit\CreditController::class, 'createConfig'])->name('settings.create'); + Route::post('/settings', [\App\Http\Controllers\Credit\CreditController::class, 'storeConfig'])->name('settings.store'); + Route::get('/settings/{id}/edit', [\App\Http\Controllers\Credit\CreditController::class, 'editConfig'])->name('settings.edit'); + Route::put('/settings/{id}', [\App\Http\Controllers\Credit\CreditController::class, 'updateConfig'])->name('settings.update'); + Route::delete('/settings/{id}', [\App\Http\Controllers\Credit\CreditController::class, 'deleteConfig'])->name('settings.destroy'); + Route::post('/settings/{id}/toggle', [\App\Http\Controllers\Credit\CreditController::class, 'toggleConfig'])->name('settings.toggle'); }); // 대시보드