# 바로빌 카드 사용내역 조회 - 멀티테넌시 개발 문서

## 목차
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
<?php
/**
 * 바로빌 카드 API 설정 파일 (멀티테넌시 지원)
 */

require_once($_SERVER['DOCUMENT_ROOT'] . '/lib/mydb.php');

/**
 * 업체별 바로빌 인증 정보 조회
 * 
 * @param int $company_id 업체 ID
 * @return array 인증 정보
 */
function getBarobillCredentials($company_id) {
    global $pdo, $DB;
    
    $stmt = $pdo->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
<?php
/**
 * 등록된 카드 목록 조회 API (멀티테넌시 지원)
 */
header('Content-Type: application/json; charset=utf-8');

require_once('barobill_card_config.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/session.php');

try {
    // 업체 ID 확인 (세션 또는 파라미터에서)
    $company_id = $_SESSION['company_id'] ?? $_GET['company_id'] ?? null;
    
    if (!$company_id) {
        echo json_encode([
            'success' => 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
<?php
/**
 * 카드 사용내역 조회 API (멀티테넌시 지원)
 */
header('Content-Type: application/json; charset=utf-8');

require_once('barobill_card_config.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/session.php');

try {
    // 업체 ID 확인
    $company_id = $_SESSION['company_id'] ?? $_GET['company_id'] ?? null;
    
    if (!$company_id) {
        echo json_encode([
            'success' => 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