diff --git a/app/Http/Controllers/Api/Admin/Barobill/BarobillMemberController.php b/app/Http/Controllers/Api/Admin/Barobill/BarobillMemberController.php index 58e982c2..39d103b7 100644 --- a/app/Http/Controllers/Api/Admin/Barobill/BarobillMemberController.php +++ b/app/Http/Controllers/Api/Admin/Barobill/BarobillMemberController.php @@ -24,6 +24,8 @@ public function __construct( /** * 회원사 조회 및 비밀번호 검증 헬퍼 * + * 회원사의 서버 모드에 따라 BarobillService의 서버 모드도 자동 전환합니다. + * * @return BarobillMember|JsonResponse 회원사 객체 또는 에러 응답 */ private function validateMemberForUrlApi(int $id): BarobillMember|JsonResponse @@ -45,6 +47,9 @@ private function validateMemberForUrlApi(int $id): BarobillMember|JsonResponse ], 422); } + // 회원사의 서버 모드로 BarobillService 전환 + $this->barobillService->setServerMode($member->server_mode ?? 'test'); + return $member; } @@ -858,57 +863,89 @@ public function getServiceCodes(): JsonResponse } /** - * 서버 모드 설정 (테스트/운영 서버 전환) + * 회원사별 서버 모드 변경 * - * 세션에 바로빌 서버 모드를 저장합니다. - * BarobillService 생성 시 이 세션 값을 읽어 사용합니다. + * 특정 회원사의 바로빌 서버 모드(테스트/운영)를 변경합니다. + * 주의: 운영 서버로 전환 시 요금이 부과됩니다. */ - public function setServerMode(Request $request): JsonResponse + public function updateServerMode(Request $request, int $id): JsonResponse { + $member = BarobillMember::find($id); + + if (!$member) { + return response()->json([ + 'success' => false, + 'message' => '회원사를 찾을 수 없습니다.', + ], 404); + } + $validated = $request->validate([ - 'mode' => 'required|in:test,production', + 'server_mode' => 'required|in:test,production', + 'confirmed' => 'required|boolean', ], [ - 'mode.required' => '서버 모드를 선택해주세요.', - 'mode.in' => '서버 모드는 test 또는 production 이어야 합니다.', + 'server_mode.required' => '서버 모드를 선택해주세요.', + 'server_mode.in' => '서버 모드는 test 또는 production 이어야 합니다.', + 'confirmed.required' => '경고 확인이 필요합니다.', ]); - $mode = $validated['mode']; - session(['barobill_server_mode' => $mode]); + // 확인 체크 + if (!$validated['confirmed']) { + return response()->json([ + 'success' => false, + 'message' => '서버 변경 경고를 확인해주세요.', + ], 422); + } - Log::info('바로빌 서버 모드 변경', [ - 'mode' => $mode, + $oldMode = $member->server_mode ?? 'test'; + $newMode = $validated['server_mode']; + + // 변경 없으면 바로 반환 + if ($oldMode === $newMode) { + return response()->json([ + 'success' => true, + 'message' => '서버 모드가 이미 ' . ($newMode === 'test' ? '테스트' : '운영') . ' 서버입니다.', + 'data' => $member, + ]); + } + + $member->update(['server_mode' => $newMode]); + + Log::info('바로빌 회원사 서버 모드 변경', [ + 'member_id' => $id, + 'biz_no' => $member->biz_no, + 'corp_name' => $member->corp_name, + 'old_mode' => $oldMode, + 'new_mode' => $newMode, 'user_id' => auth()->id(), ]); return response()->json([ 'success' => true, - 'mode' => $mode, - 'message' => ($mode === 'test' ? '테스트' : '운영') . ' 서버로 전환되었습니다.', + 'message' => ($newMode === 'test' ? '테스트' : '운영') . ' 서버로 변경되었습니다.', + 'data' => $member->fresh(), ]); } /** - * 서버 모드 조회 - * - * 현재 세션에 저장된 바로빌 서버 모드를 반환합니다. - * 세션 값이 없으면 .env 설정값을 반환합니다. + * 회원사별 서버 모드 조회 */ - public function getServerMode(): JsonResponse + public function getServerMode(int $id): JsonResponse { - $sessionMode = session('barobill_server_mode'); + $member = BarobillMember::find($id); - if ($sessionMode) { - $mode = $sessionMode; - } else { - // .env 설정 기준 (BAROBILL_TEST_MODE) - $isTestMode = config('services.barobill.test_mode', true); - $mode = $isTestMode ? 'test' : 'production'; + if (!$member) { + return response()->json([ + 'success' => false, + 'message' => '회원사를 찾을 수 없습니다.', + ], 404); } return response()->json([ 'success' => true, - 'mode' => $mode, - 'source' => $sessionMode ? 'session' : 'env', + 'data' => [ + 'server_mode' => $member->server_mode ?? 'test', + 'server_mode_label' => $member->server_mode_label, + ], ]); } } diff --git a/app/Models/Barobill/BarobillMember.php b/app/Models/Barobill/BarobillMember.php index 7c087565..8c6e14c5 100644 --- a/app/Models/Barobill/BarobillMember.php +++ b/app/Models/Barobill/BarobillMember.php @@ -27,6 +27,7 @@ class BarobillMember extends Model 'manager_email', 'manager_hp', 'status', + 'server_mode', ]; protected $casts = [ @@ -85,4 +86,36 @@ public function getStatusColorAttribute(): string default => 'bg-gray-100 text-gray-800', }; } + + /** + * 서버 모드 라벨 + */ + public function getServerModeLabelAttribute(): string + { + return match ($this->server_mode) { + 'test' => '테스트', + 'production' => '운영', + default => '테스트', + }; + } + + /** + * 서버 모드별 색상 클래스 + */ + public function getServerModeColorAttribute(): string + { + return match ($this->server_mode) { + 'test' => 'bg-amber-100 text-amber-800', + 'production' => 'bg-green-100 text-green-800', + default => 'bg-amber-100 text-amber-800', + }; + } + + /** + * 테스트 모드 여부 + */ + public function isTestMode(): bool + { + return $this->server_mode !== 'production'; + } } diff --git a/app/Services/Barobill/BarobillService.php b/app/Services/Barobill/BarobillService.php index 9b065bf8..39b82202 100644 --- a/app/Services/Barobill/BarobillService.php +++ b/app/Services/Barobill/BarobillService.php @@ -96,15 +96,55 @@ class BarobillService public function __construct() { - // 1. 세션에서 서버 모드 가져오기 (최우선) - $sessionMode = session('barobill_server_mode'); - if ($sessionMode) { - $this->isTestMode = ($sessionMode === 'test'); - } else { - // 2. .env에서 테스트 모드 설정 가져오기 (기본값: true = 테스트 모드) - $this->isTestMode = config('services.barobill.test_mode', true); + // 기본값: .env 설정 사용 + $this->isTestMode = config('services.barobill.test_mode', true); + $this->initializeConfig(); + } + + /** + * 서버 모드 전환 (회원사별 설정 적용) + * + * @param bool $isTestMode true: 테스트서버, false: 운영서버 + */ + public function switchServerMode(bool $isTestMode): self + { + if ($this->isTestMode !== $isTestMode) { + $this->isTestMode = $isTestMode; + // SOAP 클라이언트 초기화 (새 서버로 재연결) + $this->corpStateClient = null; + $this->tiClient = null; + $this->bankAccountClient = null; + $this->cardClient = null; + // 설정 재로드 + $this->initializeConfig(); } + return $this; + } + + /** + * 서버 모드 문자열로 전환 + * + * @param string $mode 'test' 또는 'production' + */ + public function setServerMode(string $mode): self + { + return $this->switchServerMode($mode === 'test'); + } + + /** + * 현재 서버 모드 조회 + */ + public function getServerMode(): string + { + return $this->isTestMode ? 'test' : 'production'; + } + + /** + * 설정 초기화 (서버 모드에 따른 설정 로드) + */ + protected function initializeConfig(): void + { // DB에서 활성화된 설정 가져오기 (우선순위) $dbConfig = $this->loadConfigFromDatabase(); diff --git a/resources/views/barobill/members/index.blade.php b/resources/views/barobill/members/index.blade.php index dc132333..c85551a8 100644 --- a/resources/views/barobill/members/index.blade.php +++ b/resources/views/barobill/members/index.blade.php @@ -10,30 +10,16 @@

회원사관리

바로빌 연동 회원사를 관리합니다

-
- -
- 서버: - - -
- -
+ @@ -137,6 +123,78 @@ class="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg + + + @endsection @push('scripts') @@ -635,49 +693,111 @@ function closeBarobillDropdown() { } }); - // 바로빌 서버 모드 관리 - const BarobillServer = { - currentMode: 'test', + // 회원사별 서버 모드 변경 관리 + const ServerModeManager = { + pendingMemberId: null, + pendingMode: null, - async init() { - try { - const res = await fetch('/api/admin/barobill/members/server-mode', { - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}', - 'Accept': 'application/json' - } - }); - const data = await res.json(); - if (data.success) { - this.currentMode = data.mode; - this.updateUI(); - } - } catch (error) { - console.error('서버 모드 조회 실패:', error); + // 서버 모드 변경 요청 (경고 모달 표시) + requestChange(memberId, memberName, currentMode) { + this.pendingMemberId = memberId; + const newMode = currentMode === 'test' ? 'production' : 'test'; + this.pendingMode = newMode; + + const modal = document.getElementById('serverModeConfirmModal'); + const memberNameEl = document.getElementById('serverModeModalMemberName'); + const currentModeEl = document.getElementById('serverModeModalCurrentMode'); + const newModeEl = document.getElementById('serverModeModalNewMode'); + const warningEl = document.getElementById('serverModeWarning'); + const confirmCheckbox = document.getElementById('serverModeConfirmCheckbox'); + + memberNameEl.textContent = memberName; + currentModeEl.textContent = currentMode === 'test' ? '테스트 서버' : '운영 서버'; + currentModeEl.className = currentMode === 'test' + ? 'font-semibold text-amber-600' + : 'font-semibold text-green-600'; + newModeEl.textContent = newMode === 'test' ? '테스트 서버' : '운영 서버'; + newModeEl.className = newMode === 'test' + ? 'font-semibold text-amber-600' + : 'font-semibold text-green-600'; + + // 운영 서버로 전환 시 추가 경고 + if (newMode === 'production') { + warningEl.innerHTML = ` +
+
+ + + +
+

⚠️ 요금 부과 안내

+
    +
  • • 운영 서버 사용 시 실제 요금이 부과됩니다.
  • +
  • • 회원사 등록, 세금계산서 발행 등 모든 API 호출에 과금됩니다.
  • +
  • • 테스트 목적이라면 테스트 서버를 사용해 주세요.
  • +
+
+
+
+ `; + } else { + warningEl.innerHTML = ` +
+
+ + + +
+

테스트 서버 안내

+
    +
  • • 테스트 서버는 개발/테스트 용도로만 사용됩니다.
  • +
  • • 테스트 데이터는 실제 국세청에 전송되지 않습니다.
  • +
  • • 운영 환경에서는 반드시 운영 서버로 전환해 주세요.
  • +
+
+
+
+ `; } + + confirmCheckbox.checked = false; + document.getElementById('serverModeConfirmBtn').disabled = true; + modal.classList.remove('hidden'); }, - async setMode(mode) { - if (mode === this.currentMode) return; + // 확인 체크박스 상태 변경 + onConfirmCheckChange(checked) { + document.getElementById('serverModeConfirmBtn').disabled = !checked; + }, + + // 서버 모드 변경 실행 + async confirmChange() { + if (!this.pendingMemberId || !this.pendingMode) return; + + const confirmBtn = document.getElementById('serverModeConfirmBtn'); + const originalText = confirmBtn.textContent; + confirmBtn.disabled = true; + confirmBtn.textContent = '변경 중...'; try { - const res = await fetch('/api/admin/barobill/members/server-mode', { + const res = await fetch(`/api/admin/barobill/members/${this.pendingMemberId}/server-mode`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' }, - body: JSON.stringify({ mode }) + body: JSON.stringify({ + server_mode: this.pendingMode, + confirmed: true + }) }); const data = await res.json(); if (data.success) { - this.currentMode = mode; - this.updateUI(); showToast(data.message, 'success'); - // 목록 새로고침 + this.closeModal(); htmx.trigger(document.body, 'memberUpdated'); } else { showToast(data.message || '서버 모드 변경 실패', 'error'); @@ -685,31 +805,23 @@ function closeBarobillDropdown() { } catch (error) { console.error('서버 모드 변경 실패:', error); showToast('서버 모드 변경 중 오류가 발생했습니다.', 'error'); + } finally { + confirmBtn.disabled = false; + confirmBtn.textContent = originalText; } }, - updateUI() { - const testBtn = document.getElementById('testServerBtn'); - const prodBtn = document.getElementById('prodServerBtn'); - - // 버튼 스타일 초기화 - testBtn.classList.remove('bg-amber-500', 'text-white', 'bg-gray-200', 'text-gray-600'); - prodBtn.classList.remove('bg-green-500', 'text-white', 'bg-gray-200', 'text-gray-600'); - - if (this.currentMode === 'test') { - testBtn.classList.add('bg-amber-500', 'text-white'); - prodBtn.classList.add('bg-gray-200', 'text-gray-600'); - } else { - testBtn.classList.add('bg-gray-200', 'text-gray-600'); - prodBtn.classList.add('bg-green-500', 'text-white'); - } + // 모달 닫기 + closeModal() { + document.getElementById('serverModeConfirmModal').classList.add('hidden'); + this.pendingMemberId = null; + this.pendingMode = null; } }; // 초기화 document.addEventListener('DOMContentLoaded', function() { MemberModal.init(); - BarobillServer.init(); }); @endpush diff --git a/resources/views/barobill/members/partials/table.blade.php b/resources/views/barobill/members/partials/table.blade.php index 13789aee..25079915 100644 --- a/resources/views/barobill/members/partials/table.blade.php +++ b/resources/views/barobill/members/partials/table.blade.php @@ -46,6 +46,9 @@ class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm te 상태 + + 서버 + 바로빌 서비스 @@ -88,6 +91,25 @@ class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm te {{ $member->status_label }} + + +