Files
sam-sales/ecard/카드사용내역조회.md
aweso 50308dd340 피플라이프 기업분석 추가
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-14 09:33:23 +09:00

31 KiB

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

목차

  1. 개요
  2. 시스템 아키텍처
  3. 데이터베이스 설계
  4. 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 (업체 기본 정보 테이블)

업체의 기본 정보를 저장하는 테이블입니다.

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 인증 정보를 저장하는 테이블입니다.

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 (카드 정보 캐시 테이블)

바로빌에서 조회한 카드 정보를 캐싱하는 테이블입니다. (선택사항)

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 (카드 사용내역 캐시 테이블)

바로빌에서 조회한 카드 사용내역을 캐싱하는 테이블입니다. (선택사항)

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 호출 이력을 기록하는 테이블입니다. (선택사항, 모니터링용)

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

요청 파라미터:

[
    'company_id' => 1,        // 업체 ID (필수)
    'availOnly' => 0          // 0=전체, 1=사용가능한 카드만
]

응답 예시:

{
    "success": true,
    "cards": [
        {
            "cardNum": "1234567890123456",
            "cardNumMasked": "1234-****-****-3456",
            "cardCompany": "04",
            "cardCompanyName": "삼성카드",
            "cardBrand": "비자",
            "alias": "법인카드1",
            "status": "1",
            "statusName": "정상"
        }
    ],
    "count": 1
}

구현 로직:

  1. company_idbarobill_credentials 테이블에서 인증 정보 조회
  2. 바로빌 SOAP API 호출 (GetCardEx2)
  3. 응답 데이터 변환 및 반환

2. 카드 사용내역 조회 API

엔드포인트: GET /ecard/api/usage.php

요청 파라미터:

[
    'company_id' => 1,         // 업체 ID (필수)
    'type' => 'period',        // daily/monthly/period
    'cardNum' => '',           // 카드번호 (빈값이면 전체)
    'startDate' => '20240101', // 시작일 (YYYYMMDD)
    'endDate' => '20240131',   // 종료일 (YYYYMMDD)
    'page' => 1,               // 페이지 번호
    'limit' => 50              // 페이지당 건수
]

응답 예시:

{
    "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_idbarobill_credentials 테이블에서 인증 정보 조회
  2. 바로빌 SOAP API 호출 (GetPeriodCardApprovalLog)
  3. 응답 데이터 변환 및 반환
  4. (선택) 캐시 테이블에 저장

보안 고려사항

1. 데이터 격리

  • 업체별 데이터 격리: 모든 쿼리에 company_id 조건 필수
  • 권한 검증: 세션에서 company_id 확인 후 접근 허용
  • SQL Injection 방지: Prepared Statement 사용
// 올바른 예시
$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 암호화: 데이터베이스에 저장 시 암호화
  • 접근 로그: 인증 정보 조회 시 로그 기록
  • 최소 권한 원칙: 필요한 최소한의 정보만 조회
// 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 호출 타임아웃 설정
// 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
/**
 * 바로빌 카드 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
/**
 * 등록된 카드 목록 조회 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
/**
 * 카드 사용내역 조회 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. 데이터베이스 마이그레이션

    -- 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 로그 분석
  • 성능 최적화

참고 자료


변경 이력

버전 날짜 변경 내용 작성자
1.0 2025-12-08 초기 문서 작성 -

문서 작성일: 2025년 12월
최종 수정일: 2025년 12월
문서 버전: 1.0