# 바로빌 카드 사용내역 조회 - 멀티테넌시 개발 문서 ## 목차 1. [개요](#개요) 2. [시스템 아키텍처](#시스템-아키텍처) 3. [데이터베이스 설계](#데이터베이스-설계) 4. [API 구조](#api-구조) 5. [보안 고려사항](#보안-고려사항) 6. [구현 가이드](#구현-가이드) --- ## 개요 ### 목적 멀티테넌시 환경에서 각 업체(테넌트)별로 독립적인 바로빌 카드 사용내역 조회 서비스를 제공하기 위한 개발 문서입니다. ### 주요 기능 - 업체별 바로빌 인증 정보 관리 - 업체별 카드 목록 조회 - 업체별 카드 사용내역 조회 - 데이터 격리 및 보안 관리 ### 기술 스택 - **백엔드**: PHP 7.3+ - **데이터베이스**: MySQL/MariaDB - **외부 API**: 바로빌 SOAP 웹서비스 - **프론트엔드**: React (ecard/index.php) --- ## 시스템 아키텍처 ### 데이터 흐름도 ``` [업체 사용자] ↓ [ecard/index.php] (프론트엔드) ↓ (업체 ID 전달) [API Layer] ├─ cards.php (카드 목록 조회) └─ usage.php (사용내역 조회) ↓ (업체별 인증 정보 조회) [Database] ├─ companies (업체 정보) └─ barobill_credentials (바로빌 인증 정보) ↓ (바로빌 API 호출) [바로빌 SOAP API] ├─ GetCardEx2 (카드 목록) └─ GetPeriodCardApprovalLog (사용내역) ↓ (응답) [API Layer] → [프론트엔드] → [사용자] ``` ### 멀티테넌시 구조 ``` ┌─────────────────────────────────────────┐ │ 업체 A (Company A) │ │ ┌───────────────────────────────────┐ │ │ │ 바로빌 인증 정보 │ │ │ │ - CERTKEY: AAAAA │ │ │ │ - 사업자번호: 123-45-67890 │ │ │ │ - 사용자 ID: user_a │ │ │ └───────────────────────────────────┘ │ │ ┌───────────────────────────────────┐ │ │ │ 카드 목록 (바로빌에서 조회) │ │ │ │ - 카드1, 카드2, 카드3 │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘ ┌─────────────────────────────────────────┐ │ 업체 B (Company B) │ │ ┌───────────────────────────────────┐ │ │ │ 바로빌 인증 정보 │ │ │ │ - CERTKEY: BBBBB │ │ │ │ - 사업자번호: 987-65-43210 │ │ │ │ - 사용자 ID: user_b │ │ │ └───────────────────────────────────┘ │ │ ┌───────────────────────────────────┐ │ │ │ 카드 목록 (바로빌에서 조회) │ │ │ │ - 카드4, 카드5 │ │ │ └───────────────────────────────────┘ │ └─────────────────────────────────────────┘ ``` --- ## 데이터베이스 설계 ### 1. companies (업체 기본 정보 테이블) 업체의 기본 정보를 저장하는 테이블입니다. ```sql CREATE TABLE companies ( id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '업체 ID', company_name VARCHAR(255) NOT NULL COMMENT '업체명', business_number VARCHAR(20) NOT NULL COMMENT '사업자번호 (하이픈 포함)', business_number_clean VARCHAR(20) NOT NULL COMMENT '사업자번호 (하이픈 제거)', status ENUM('active', 'inactive', 'suspended') DEFAULT 'active' COMMENT '상태', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시', updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시', deleted_at DATETIME NULL COMMENT '삭제일시 (소프트 삭제)', UNIQUE KEY uk_business_number (business_number_clean), INDEX idx_status (status), INDEX idx_deleted_at (deleted_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='업체 기본 정보'; ``` **주요 필드 설명:** - `id`: 업체 고유 ID (다른 테이블에서 외래키로 사용) - `company_name`: 업체명 - `business_number`: 사업자번호 (표시용, 하이픈 포함) - `business_number_clean`: 사업자번호 (검색용, 하이픈 제거) - `status`: 업체 상태 (active=활성, inactive=비활성, suspended=정지) --- ### 2. barobill_credentials (바로빌 인증 정보 테이블) 각 업체별 바로빌 API 인증 정보를 저장하는 테이블입니다. ```sql CREATE TABLE barobill_credentials ( id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '인증 정보 ID', company_id INT(11) UNSIGNED NOT NULL COMMENT '업체 ID', cert_key VARCHAR(500) NOT NULL COMMENT '바로빌 CERTKEY (암호화 권장)', corp_num VARCHAR(20) NOT NULL COMMENT '사업자번호 (하이픈 제거)', user_id VARCHAR(100) NULL COMMENT '바로빌 사용자 ID (선택사항, 빈값이면 전체 카드 조회)', test_mode TINYINT(1) DEFAULT 0 COMMENT '테스트 모드 (0=운영, 1=테스트)', status ENUM('active', 'inactive') DEFAULT 'active' COMMENT '상태', last_api_call DATETIME NULL COMMENT '마지막 API 호출 일시', last_error_message TEXT NULL COMMENT '마지막 에러 메시지', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시', updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시', UNIQUE KEY uk_company_id (company_id), INDEX idx_status (status), INDEX idx_company_id (company_id), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='바로빌 인증 정보'; ``` **주요 필드 설명:** - `company_id`: 업체 ID (companies 테이블 참조) - `cert_key`: 바로빌 CERTKEY (⚠️ 민감정보, 암호화 권장) - `corp_num`: 사업자번호 (하이픈 제거) - `user_id`: 바로빌 사용자 ID (특정 사용자 카드만 조회 시 사용, NULL이면 전체) - `test_mode`: 테스트 모드 여부 (0=운영, 1=테스트) - `status`: 인증 정보 상태 - `last_api_call`: 마지막 API 호출 일시 (모니터링용) - `last_error_message`: 마지막 에러 메시지 (디버깅용) **보안 고려사항:** - `cert_key`는 민감정보이므로 암호화 저장 권장 - 데이터베이스 접근 권한 최소화 - 로그에 민감정보 출력 금지 --- ### 3. barobill_cards (카드 정보 캐시 테이블) 바로빌에서 조회한 카드 정보를 캐싱하는 테이블입니다. (선택사항) ```sql CREATE TABLE barobill_cards ( id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '카드 ID', company_id INT(11) UNSIGNED NOT NULL COMMENT '업체 ID', card_num VARCHAR(50) NOT NULL COMMENT '카드번호', card_company_code VARCHAR(10) NULL COMMENT '카드사 코드', card_company_name VARCHAR(50) NULL COMMENT '카드사 이름', card_brand VARCHAR(20) NULL COMMENT '카드 브랜드 (비자, 마스터카드 등)', alias VARCHAR(100) NULL COMMENT '카드 별칭', card_type TINYINT(1) NULL COMMENT '카드 종류 (1=개인, 2=법인)', status TINYINT(1) NULL COMMENT '카드 상태 (0=대기중, 1=정상, 2=해지, 3=수집오류, 4=일시중지)', collect_cycle TINYINT(1) NULL COMMENT '수집주기 (1=1일1회, 2=1일2회, 3=1일3회)', last_collect_date DATE NULL COMMENT '마지막 수집일', last_collect_result TINYINT(1) NULL COMMENT '마지막 수집결과', regist_date DATE NULL COMMENT '등록일', cached_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '캐시 일시', updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '수정일시', UNIQUE KEY uk_company_card (company_id, card_num), INDEX idx_company_id (company_id), INDEX idx_status (status), INDEX idx_cached_at (cached_at), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='바로빌 카드 정보 캐시'; ``` **주요 필드 설명:** - `company_id`: 업체 ID - `card_num`: 카드번호 (바로빌에서 조회한 카드번호) - `card_company_code`: 카드사 코드 (01=BC, 02=KB, 04=삼성 등) - `card_company_name`: 카드사 이름 - `card_brand`: 카드 브랜드 (비자, 마스터카드 등) - `alias`: 카드 별칭 - `status`: 카드 상태 - `cached_at`: 캐시 일시 (캐시 만료 판단용) **사용 목적:** - 바로빌 API 호출 최소화 (성능 향상) - 오프라인 조회 가능 - 카드 목록 변경 이력 추적 **캐시 전략:** - 카드 목록은 1시간마다 갱신 권장 - 실시간 조회가 필요한 경우 캐시 사용 안 함 --- ### 4. barobill_card_usage_logs (카드 사용내역 캐시 테이블) 바로빌에서 조회한 카드 사용내역을 캐싱하는 테이블입니다. (선택사항) ```sql CREATE TABLE barobill_card_usage_logs ( id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '사용내역 ID', company_id INT(11) UNSIGNED NOT NULL COMMENT '업체 ID', card_num VARCHAR(50) NOT NULL COMMENT '카드번호', use_dt DATETIME NOT NULL COMMENT '사용일시', use_key VARCHAR(100) NULL COMMENT '사용 키 (바로빌 고유값)', approval_num VARCHAR(50) NULL COMMENT '승인번호', approval_amount INT(11) DEFAULT 0 COMMENT '승인금액', tax INT(11) DEFAULT 0 COMMENT '부가세', service_charge INT(11) DEFAULT 0 COMMENT '봉사료', total_amount INT(11) DEFAULT 0 COMMENT '총 금액', approval_type TINYINT(1) NULL COMMENT '승인유형 (1=승인, 2=취소)', payment_plan VARCHAR(10) NULL COMMENT '할부개월 (0=일시불)', currency_code VARCHAR(3) DEFAULT 'KRW' COMMENT '통화코드', use_store_name VARCHAR(255) NULL COMMENT '가맹점명', use_store_corp_num VARCHAR(20) NULL COMMENT '가맹점 사업자번호', use_store_addr TEXT NULL COMMENT '가맹점 주소', use_store_ceo VARCHAR(100) NULL COMMENT '가맹점 대표자명', use_store_biz_type VARCHAR(100) NULL COMMENT '가맹점 업종', use_store_tel VARCHAR(20) NULL COMMENT '가맹점 전화번호', memo TEXT NULL COMMENT '메모', card_company VARCHAR(10) NULL COMMENT '카드사 코드', cached_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '캐시 일시', UNIQUE KEY uk_use_key (company_id, use_key), INDEX idx_company_id (company_id), INDEX idx_card_num (card_num), INDEX idx_use_dt (use_dt), INDEX idx_company_use_dt (company_id, use_dt), INDEX idx_approval_type (approval_type), INDEX idx_cached_at (cached_at), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='바로빌 카드 사용내역 캐시'; ``` **주요 필드 설명:** - `company_id`: 업체 ID - `card_num`: 카드번호 - `use_dt`: 사용일시 - `use_key`: 바로빌 고유 사용 키 (중복 방지용) - `approval_amount`: 승인금액 - `approval_type`: 승인유형 (1=승인, 2=취소) - `use_store_name`: 가맹점명 - `cached_at`: 캐시 일시 **인덱스 전략:** - `idx_company_use_dt`: 업체별 기간 조회 최적화 - `idx_use_dt`: 전체 기간 조회 최적화 - `uk_use_key`: 중복 데이터 방지 **사용 목적:** - 바로빌 API 호출 최소화 - 빠른 조회 성능 - 데이터 분석 및 리포트 생성 **캐시 전략:** - 최근 3개월 데이터는 캐시 유지 - 오래된 데이터는 주기적으로 정리 - 실시간 조회가 필요한 경우 바로빌 API 직접 호출 --- ### 5. barobill_api_logs (API 호출 로그 테이블) 바로빌 API 호출 이력을 기록하는 테이블입니다. (선택사항, 모니터링용) ```sql CREATE TABLE barobill_api_logs ( id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '로그 ID', company_id INT(11) UNSIGNED NOT NULL COMMENT '업체 ID', api_method VARCHAR(50) NOT NULL COMMENT 'API 메서드명', request_params TEXT NULL COMMENT '요청 파라미터 (JSON)', response_status VARCHAR(20) NULL COMMENT '응답 상태 (success/failure)', response_data TEXT NULL COMMENT '응답 데이터 (JSON, 일부만 저장)', error_message TEXT NULL COMMENT '에러 메시지', execution_time INT(11) NULL COMMENT '실행 시간 (밀리초)', ip_address VARCHAR(45) NULL COMMENT '요청 IP 주소', user_agent VARCHAR(255) NULL COMMENT '사용자 에이전트', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '생성일시', INDEX idx_company_id (company_id), INDEX idx_api_method (api_method), INDEX idx_response_status (response_status), INDEX idx_created_at (created_at), INDEX idx_company_created (company_id, created_at), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='바로빌 API 호출 로그'; ``` **주요 필드 설명:** - `company_id`: 업체 ID - `api_method`: API 메서드명 (GetCardEx2, GetPeriodCardApprovalLog 등) - `request_params`: 요청 파라미터 (JSON 형식) - `response_status`: 응답 상태 (success/failure) - `error_message`: 에러 메시지 - `execution_time`: 실행 시간 (성능 모니터링용) **사용 목적:** - API 호출 이력 추적 - 에러 디버깅 - 성능 모니터링 - 사용량 통계 **데이터 보관 정책:** - 최근 6개월 데이터 보관 - 오래된 데이터는 주기적으로 아카이빙 또는 삭제 --- ## 테이블 관계도 ``` companies (업체) │ ├── 1:1 ── barobill_credentials (바로빌 인증 정보) │ ├── 1:N ── barobill_cards (카드 정보 캐시) │ ├── 1:N ── barobill_card_usage_logs (사용내역 캐시) │ └── 1:N ── barobill_api_logs (API 호출 로그) ``` --- ## API 구조 ### 1. 카드 목록 조회 API **엔드포인트**: `GET /ecard/api/cards.php` **요청 파라미터:** ```php [ 'company_id' => 1, // 업체 ID (필수) 'availOnly' => 0 // 0=전체, 1=사용가능한 카드만 ] ``` **응답 예시:** ```json { "success": true, "cards": [ { "cardNum": "1234567890123456", "cardNumMasked": "1234-****-****-3456", "cardCompany": "04", "cardCompanyName": "삼성카드", "cardBrand": "비자", "alias": "법인카드1", "status": "1", "statusName": "정상" } ], "count": 1 } ``` **구현 로직:** 1. `company_id`로 `barobill_credentials` 테이블에서 인증 정보 조회 2. 바로빌 SOAP API 호출 (GetCardEx2) 3. 응답 데이터 변환 및 반환 --- ### 2. 카드 사용내역 조회 API **엔드포인트**: `GET /ecard/api/usage.php` **요청 파라미터:** ```php [ 'company_id' => 1, // 업체 ID (필수) 'type' => 'period', // daily/monthly/period 'cardNum' => '', // 카드번호 (빈값이면 전체) 'startDate' => '20240101', // 시작일 (YYYYMMDD) 'endDate' => '20240131', // 종료일 (YYYYMMDD) 'page' => 1, // 페이지 번호 'limit' => 50 // 페이지당 건수 ] ``` **응답 예시:** ```json { "success": true, "data": { "logs": [ { "cardNum": "1234-****-****-3456", "approvalDateTime": "2024-01-15 14:30:00", "merchantName": "스타벅스 강남점", "merchantBizNum": "123-45-67890", "amount": 5000, "approvalType": "1", "approvalTypeName": "승인" } ], "pagination": { "currentPage": 1, "maxPageNum": 10, "totalCount": 500 }, "summary": { "totalAmount": 1000000, "count": 500, "approvalCount": 480, "cancelCount": 20 } } } ``` **구현 로직:** 1. `company_id`로 `barobill_credentials` 테이블에서 인증 정보 조회 2. 바로빌 SOAP API 호출 (GetPeriodCardApprovalLog) 3. 응답 데이터 변환 및 반환 4. (선택) 캐시 테이블에 저장 --- ## 보안 고려사항 ### 1. 데이터 격리 - **업체별 데이터 격리**: 모든 쿼리에 `company_id` 조건 필수 - **권한 검증**: 세션에서 `company_id` 확인 후 접근 허용 - **SQL Injection 방지**: Prepared Statement 사용 ```php // 올바른 예시 $stmt = $pdo->prepare("SELECT * FROM barobill_credentials WHERE company_id = ?"); $stmt->execute([$company_id]); // 잘못된 예시 (SQL Injection 취약) $sql = "SELECT * FROM barobill_credentials WHERE company_id = $company_id"; ``` ### 2. 인증 정보 보호 - **CERTKEY 암호화**: 데이터베이스에 저장 시 암호화 - **접근 로그**: 인증 정보 조회 시 로그 기록 - **최소 권한 원칙**: 필요한 최소한의 정보만 조회 ```php // CERTKEY 암호화 예시 (간단한 방법) function encryptCertKey($certKey) { // 실제 운영 환경에서는 더 강력한 암호화 사용 권장 return base64_encode(openssl_encrypt($certKey, 'AES-256-CBC', $encryptionKey)); } function decryptCertKey($encryptedCertKey) { return openssl_decrypt(base64_decode($encryptedCertKey), 'AES-256-CBC', $encryptionKey); } ``` ### 3. API 호출 제한 - **Rate Limiting**: 업체별 API 호출 횟수 제한 - **에러 처리**: 에러 발생 시 민감정보 노출 금지 - **타임아웃 설정**: API 호출 타임아웃 설정 ```php // Rate Limiting 예시 function checkRateLimit($company_id) { $stmt = $pdo->prepare(" SELECT COUNT(*) as count FROM barobill_api_logs WHERE company_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR) "); $stmt->execute([$company_id]); $result = $stmt->fetch(PDO::FETCH_ASSOC); if ($result['count'] > 1000) { // 시간당 1000회 제한 throw new Exception('API 호출 한도 초과'); } } ``` --- ## 구현 가이드 ### 1. barobill_card_config.php 수정 멀티테넌시를 지원하도록 수정합니다. ```php prepare(" SELECT bc.cert_key, bc.corp_num, bc.user_id, bc.test_mode, bc.status FROM {$DB}.barobill_credentials bc WHERE bc.company_id = ? AND bc.status = 'active' "); $stmt->execute([$company_id]); $credentials = $stmt->fetch(PDO::FETCH_ASSOC); if (!$credentials) { return [ 'success' => false, 'error' => '바로빌 인증 정보가 등록되지 않았습니다.' ]; } // CERTKEY 복호화 (암호화된 경우) if (function_exists('decryptCertKey')) { $credentials['cert_key'] = decryptCertKey($credentials['cert_key']); } return [ 'success' => true, 'data' => $credentials ]; } /** * 바로빌 카드 SOAP 웹서비스 호출 함수 (멀티테넌시 지원) * * @param int $company_id 업체 ID * @param string $method SOAP 메서드명 * @param array $params SOAP 메서드 파라미터 * @return array 응답 데이터 */ function callBarobillCardSOAPForCompany($company_id, $method, $params = []) { // 인증 정보 조회 $credentials = getBarobillCredentials($company_id); if (!$credentials['success']) { return $credentials; } $certKey = $credentials['data']['cert_key']; $corpNum = $credentials['data']['corp_num']; $isTestMode = $credentials['data']['test_mode'] == 1; // SOAP URL 설정 $soapUrl = $isTestMode ? 'https://testws.baroservice.com/CARD.asmx?WSDL' : 'https://ws.baroservice.com/CARD.asmx?WSDL'; // SOAP 클라이언트 생성 try { $soapClient = new SoapClient($soapUrl, [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30 ]); } catch (Exception $e) { return [ 'success' => false, 'error' => 'SOAP 클라이언트 생성 실패: ' . $e->getMessage() ]; } // CERTKEY와 CorpNum 자동 추가 if (!isset($params['CERTKEY'])) { $params['CERTKEY'] = $certKey; } if (!isset($params['CorpNum'])) { $params['CorpNum'] = $corpNum; } // API 호출 로그 기록 $startTime = microtime(true); try { $result = $soapClient->$method($params); $executionTime = (microtime(true) - $startTime) * 1000; // 밀리초 // API 호출 로그 저장 logBarobillApiCall($company_id, $method, $params, 'success', null, $executionTime); $resultProperty = $method . 'Result'; if (isset($result->$resultProperty)) { $resultData = $result->$resultProperty; // 에러 코드 체크 if (is_numeric($resultData) && $resultData < 0) { logBarobillApiCall($company_id, $method, $params, 'failure', '에러 코드: ' . $resultData, $executionTime); return [ 'success' => false, 'error' => '바로빌 카드 API 오류 코드: ' . $resultData, 'error_code' => $resultData ]; } return [ 'success' => true, 'data' => $resultData ]; } return [ 'success' => true, 'data' => $result ]; } catch (SoapFault $e) { $executionTime = (microtime(true) - $startTime) * 1000; logBarobillApiCall($company_id, $method, $params, 'failure', $e->getMessage(), $executionTime); return [ 'success' => false, 'error' => 'SOAP 오류: ' . $e->getMessage(), 'error_code' => $e->getCode() ]; } catch (Exception $e) { $executionTime = (microtime(true) - $startTime) * 1000; logBarobillApiCall($company_id, $method, $params, 'failure', $e->getMessage(), $executionTime); return [ 'success' => false, 'error' => 'API 호출 오류: ' . $e->getMessage() ]; } } /** * API 호출 로그 저장 */ function logBarobillApiCall($company_id, $method, $params, $status, $error_message = null, $execution_time = null) { global $pdo, $DB; try { $stmt = $pdo->prepare(" INSERT INTO {$DB}.barobill_api_logs (company_id, api_method, request_params, response_status, error_message, execution_time, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $company_id, $method, json_encode($params, JSON_UNESCAPED_UNICODE), $status, $error_message, $execution_time, $_SERVER['REMOTE_ADDR'] ?? null, $_SERVER['HTTP_USER_AGENT'] ?? null ]); } catch (Exception $e) { // 로그 저장 실패는 무시 (시스템 오류 방지) error_log('API 로그 저장 실패: ' . $e->getMessage()); } } /** * 업체별 카드 목록 조회 (멀티테넌시 지원) */ function getCardListForCompany($company_id, $availOnly = 0) { $result = callBarobillCardSOAPForCompany($company_id, 'GetCardEx2', [ 'AvailOnly' => $availOnly ]); if (!$result['success']) { return $result; } $cards = []; $data = $result['data']; if (!isset($data->CardEx)) { return ['success' => true, 'data' => []]; } if (!is_array($data->CardEx)) { $cards = [$data->CardEx]; } else { $cards = $data->CardEx; } // 에러 체크 if (count($cards) == 1 && isset($cards[0]->CardNum) && $cards[0]->CardNum < 0) { return [ 'success' => false, 'error' => '카드 목록 조회 실패', 'error_code' => $cards[0]->CardNum ]; } // (선택) 캐시 테이블에 저장 // saveCardsToCache($company_id, $cards); return ['success' => true, 'data' => $cards]; } /** * 업체별 기간별 카드 사용내역 조회 (멀티테넌시 지원) */ function getPeriodCardUsageForCompany($company_id, $cardNum = '', $startDate = '', $endDate = '', $countPerPage = 50, $currentPage = 1, $orderDirection = 2, $userId = '') { // 인증 정보 조회 $credentials = getBarobillCredentials($company_id); if (!$credentials['success']) { return $credentials; } $barobillUserId = $credentials['data']['user_id'] ?? ''; if (!empty($userId)) { $barobillUserId = $userId; } $result = callBarobillCardSOAPForCompany($company_id, 'GetPeriodCardApprovalLog', [ 'ID' => $barobillUserId, 'CardNum' => $cardNum, 'StartDate' => $startDate, 'EndDate' => $endDate, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => $orderDirection ]); if (!$result['success']) { return $result; } return parseCardUsageResult($result['data']); } // 기존 parseCardUsageResult 함수는 그대로 사용 // ... (기존 코드 유지) ?> ``` ### 2. cards.php 수정 멀티테넌시를 지원하도록 수정합니다. ```php false, 'error' => '업체 ID가 필요합니다.' ], JSON_UNESCAPED_UNICODE); exit; } $availOnly = isset($_GET['availOnly']) ? intval($_GET['availOnly']) : 0; $result = getCardListForCompany($company_id, $availOnly); if ($result['success']) { $cards = []; foreach ($result['data'] as $card) { // ... (기존 변환 로직) } echo json_encode([ 'success' => true, 'cards' => $cards, 'count' => count($cards) ], JSON_UNESCAPED_UNICODE); } else { echo json_encode([ 'success' => false, 'error' => $result['error'], 'error_code' => $result['error_code'] ?? null ], JSON_UNESCAPED_UNICODE); } } catch (Exception $e) { echo json_encode([ 'success' => false, 'error' => '서버 오류: ' . $e->getMessage() ], JSON_UNESCAPED_UNICODE); } ?> ``` ### 3. usage.php 수정 멀티테넌시를 지원하도록 수정합니다. ```php false, 'error' => '업체 ID가 필요합니다.' ], JSON_UNESCAPED_UNICODE); exit; } $type = $_GET['type'] ?? 'period'; $cardNum = $_GET['cardNum'] ?? ''; $page = max(1, intval($_GET['page'] ?? 1)); $limit = min(100, max(10, intval($_GET['limit'] ?? 50))); $orderDirection = intval($_GET['order'] ?? 2); // ... (기존 로직을 getPeriodCardUsageForCompany로 변경) $result = getPeriodCardUsageForCompany( $company_id, $cardNum, $startDate, $endDate, $limit, $page, $orderDirection ); // ... (기존 응답 로직) } catch (Exception $e) { echo json_encode([ 'success' => false, 'error' => '서버 오류: ' . $e->getMessage() ], JSON_UNESCAPED_UNICODE); } ?> ``` --- ## 마이그레이션 가이드 ### 기존 단일 테넌트에서 멀티테넌트로 전환 1. **데이터베이스 마이그레이션** ```sql -- 1. companies 테이블 생성 -- 2. barobill_credentials 테이블 생성 -- 3. 기존 파일 기반 설정을 DB로 마이그레이션 INSERT INTO companies (company_name, business_number, business_number_clean, status) VALUES ('기본 업체', '123-45-67890', '1234567890', 'active'); INSERT INTO barobill_credentials (company_id, cert_key, corp_num, user_id, test_mode, status) VALUES ( 1, (SELECT cert_key FROM file), -- 파일에서 읽은 CERTKEY (SELECT corp_num FROM file), -- 파일에서 읽은 사업자번호 NULL, 0, 'active' ); ``` 2. **코드 수정** - `barobill_card_config.php`: 파일 기반 → DB 기반으로 변경 - `cards.php`, `usage.php`: `company_id` 파라미터 추가 - 세션에 `company_id` 저장 3. **테스트** - 각 업체별로 독립적인 카드 조회 확인 - 데이터 격리 확인 - 권한 검증 확인 --- ## 모니터링 및 유지보수 ### 1. 주요 모니터링 지표 - API 호출 성공률 - API 호출 응답 시간 - 에러 발생 빈도 - 캐시 적중률 (캐시 사용 시) ### 2. 정기 점검 사항 - 인증 정보 만료 확인 - 캐시 데이터 정리 - API 로그 분석 - 성능 최적화 --- ## 참고 자료 - [바로빌 개발자 문서](https://dev.barobill.co.kr/) - [바로빌 카드 API 레퍼런스](https://dev.barobill.co.kr/docs/references/카드조회-API) - PHP SOAP 클라이언트 문서 --- ## 변경 이력 | 버전 | 날짜 | 변경 내용 | 작성자 | |------|------|----------|--------| | 1.0 | 2025-12-08 | 초기 문서 작성 | - | --- **문서 작성일**: 2025년 12월 **최종 수정일**: 2025년 12월 **문서 버전**: 1.0