Files
sam-sales/etax/api/barobill_config.php
aweso 2ab3534278 바로빌 전자세금계산서 솔루션 연결
🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

419 lines
18 KiB
PHP

<?php
/**
* 바로빌 API 설정 파일
*
* ⚠️ 중요: 바로빌은 SOAP 웹서비스를 사용합니다 (REST API가 아님)
*
* 사용 방법:
* 1. apikey/barobill_cert_key.txt 파일에 CERTKEY(인증서 키)를 저장하세요
* - 바로빌 개발자센터에서 인증서 등록 후 발급받은 CERTKEY
*
* 2. apikey/barobill_corp_num.txt 파일에 사업자번호를 저장하세요
* - 세금계산서를 발행할 회사의 사업자번호 (하이픈 제외)
*
* 3. 테스트 환경인 경우 apikey/barobill_test_mode.txt 파일에 "test" 또는 "true"를 저장하세요
*/
// load .env file
require_once __DIR__ . '/../../lib/DotEnv.php';
(new DotEnv(__DIR__ . '/../../.env'))->load();
// 인증서 키(CERTKEY) 파일 경로
$documentRoot = getenv('DOCUMENT_ROOT');
$certKeyFile = $documentRoot . '/apikey/barobill_cert_key.txt';
$legacyApiKeyFile = $documentRoot . '/apikey/barobill_api_key.txt'; // 기존 호환성
$corpNumFile = $documentRoot . '/apikey/barobill_corp_num.txt';
$testModeFile = $documentRoot . '/apikey/barobill_test_mode.txt';
// CERTKEY 읽기 (인증서 키)
// 우선순위: barobill_cert_key.txt > barobill_api_key.txt (기존 호환성)
$barobillCertKey = '';
if (file_exists($certKeyFile)) {
$content = trim(file_get_contents($certKeyFile));
// 설명 텍스트가 아닌 실제 키만 추출 (대괄호 안의 내용 제외, =로 시작하는 경우 제외)
if (!empty($content) && !preg_match('/^\[여기에/', $content) && !preg_match('/^=/', $content) && strpos($content, '바로빌 CERTKEY') === false) {
$barobillCertKey = $content;
}
}
// 기존 barobill_api_key.txt도 CERTKEY로 사용 (호환성)
if (empty($barobillCertKey) && file_exists($legacyApiKeyFile)) {
$barobillCertKey = trim(file_get_contents($legacyApiKeyFile));
}
// 사업자번호 읽기
$barobillCorpNum = '';
if (file_exists($corpNumFile)) {
$content = trim(file_get_contents($corpNumFile));
// 설명 텍스트가 아닌 실제 사업자번호만 추출 (대괄호 안의 내용 제외)
if (!empty($content) && !preg_match('/^\[여기에/', $content)) {
$barobillCorpNum = $content;
// 하이픈 제거
$barobillCorpNum = str_replace('-', '', $barobillCorpNum);
}
}
// 테스트 모드 확인
$isTestMode = false;
if (file_exists($testModeFile)) {
$testMode = trim(file_get_contents($testModeFile));
$isTestMode = (strtolower($testMode) === 'test' || strtolower($testMode) === 'true');
}
// 바로빌 SOAP 웹서비스 URL
// 문서 참고: etax/docs/barobill-api-doc/_lib/BaroService_TI.php
$barobillSoapUrl = $isTestMode
? 'https://testws.baroservice.com/TI.asmx?WSDL' // 테스트 환경
: 'https://ws.baroservice.com/TI.asmx?WSDL'; // 운영 환경
// SOAP 클라이언트 초기화
$barobillSoapClient = null;
// 테스트 모드에서는 CERTKEY 없이도 SOAP 클라이언트 초기화 시도
if (!empty($barobillCertKey) || $isTestMode) {
try {
$barobillSoapClient = new SoapClient($barobillSoapUrl, [
'trace' => true,
'encoding' => 'UTF-8',
'exceptions' => true,
'connection_timeout' => 30
]);
} catch (Throwable $e) {
// SOAP 클라이언트 생성 실패 시 null 유지 (Class not found 등 Fatal Error 포함)
error_log('바로빌 SOAP 클라이언트 생성 실패: ' . $e->getMessage());
}
}
/**
* 바로빌 SOAP 웹서비스 호출 함수
*
* @param string $method SOAP 메서드명 (예: 'RegistAndIssueTaxInvoice')
* @param array $params SOAP 메서드 파라미터
* @return array 응답 데이터
*/
function callBarobillSOAP($method, $params = []) {
global $barobillSoapClient, $barobillCertKey, $barobillCorpNum, $isTestMode;
if (!$barobillSoapClient) {
return [
'success' => false,
'error' => '바로빌 SOAP 클라이언트가 초기화되지 않았습니다. CERTKEY를 확인하세요.',
'error_detail' => [
'cert_key_file' => getenv('DOCUMENT_ROOT') . '/apikey/barobill_cert_key.txt',
'soap_url' => $isTestMode ? 'https://testws.baroservice.com/TI.asmx?WSDL' : 'https://ws.baroservice.com/TI.asmx?WSDL'
]
];
}
// 테스트 모드가 아닌 경우 CERTKEY 필수
if (empty($barobillCertKey) && !$isTestMode) {
return [
'success' => false,
'error' => 'CERTKEY가 설정되지 않았습니다. apikey/barobill_cert_key.txt 파일을 확인하세요.'
];
}
// 테스트 모드에서 CERTKEY가 없으면 빈 문자열로 처리 (바로빌 테스트 API가 허용할 수 있음)
if (empty($barobillCertKey) && $isTestMode) {
$barobillCertKey = ''; // 빈 문자열로 시도
}
// 테스트 모드에서 사업자번호가 없으면 더미 사업자번호 사용
if (empty($barobillCorpNum)) {
if ($isTestMode) {
// 테스트 모드: 더미 사업자번호 사용 (바로빌 테스트 API가 허용할 수 있음)
$barobillCorpNum = '1234567890'; // 테스트용 더미 사업자번호
error_log('바로빌 테스트 모드: 사업자번호가 없어서 더미 사업자번호를 사용합니다.');
} else {
// 운영 모드: 사업자번호 필수
return [
'success' => false,
'error' => '사업자번호가 설정되지 않았습니다. apikey/barobill_corp_num.txt 파일을 확인하세요.'
];
}
}
try {
// CERTKEY와 CorpNum을 파라미터에 자동 추가
if (!isset($params['CERTKEY'])) {
$params['CERTKEY'] = $barobillCertKey;
}
if (!isset($params['CorpNum']) && !isset($params['CorpNum'])) {
// CorpNum이 파라미터에 없으면 추가 (일부 메서드는 Invoice 내부에 있음)
if (!isset($params['Invoice']['InvoicerParty']['CorpNum'])) {
// Invoice 구조가 없으면 최상위에 추가
if (!isset($params['CorpNum'])) {
$params['CorpNum'] = $barobillCorpNum;
}
}
}
// 디버깅: 전달되는 파라미터 로그 (민감 정보는 마스킹)
error_log('바로빌 API 호출 - Method: ' . $method . ', CorpNum: ' . $barobillCorpNum . ', CERTKEY: ' . (empty($barobillCertKey) ? '(없음)' : substr($barobillCertKey, 0, 10) . '...'));
// SOAP 메서드 호출
$result = $barobillSoapClient->$method($params);
// 결과에서 Result 속성 추출
$resultProperty = $method . 'Result';
if (isset($result->$resultProperty)) {
$resultData = $result->$resultProperty;
// 결과가 음수면 오류 코드
if (is_numeric($resultData) && $resultData < 0) {
// 오류 코드에 따른 메시지 매핑
$errorMessages = [
-11101 => '사업자번호가 설정되지 않았거나 유효하지 않습니다. apikey/barobill_corp_num.txt 파일에 올바른 사업자번호를 입력하세요.',
-11102 => 'CERTKEY가 유효하지 않습니다. 바로빌 개발자센터에서 발급받은 CERTKEY를 확인하세요.',
-11103 => '인증서가 만료되었거나 유효하지 않습니다.',
-26001 => '발행에 필요한 공동인증서가 등록되어 있지 않습니다. 바로빌 웹사이트(https://www.barobill.co.kr)에 로그인하여 공동인증서를 등록하고, CERTKEY와 연결되어 있는지 확인하세요.',
-32000 => '알 수 없는 오류가 발생했습니다.',
];
$errorMessage = isset($errorMessages[$resultData])
? $errorMessages[$resultData]
: '바로빌 API 오류 코드: ' . $resultData;
// GetErrString API로 상세 오류 메시지 조회 시도 (CERTKEY가 있는 경우)
$detailedError = null;
if (!empty($barobillCertKey) && $barobillSoapClient) {
try {
$errStringResult = $barobillSoapClient->GetErrString([
'CERTKEY' => $barobillCertKey,
'ErrCode' => $resultData
]);
if (isset($errStringResult->GetErrStringResult) && $errStringResult->GetErrStringResult >= 0) {
$detailedError = $errStringResult->GetErrStringResult;
}
} catch (Exception $e) {
// GetErrString 호출 실패 시 무시
}
}
return [
'success' => false,
'error' => $errorMessage,
'error_code' => $resultData,
'error_detail' => $detailedError ? "상세 오류: " . $detailedError : null,
'soap_request' => $barobillSoapClient->__getLastRequest(),
'soap_response' => $barobillSoapClient->__getLastResponse()
];
}
return [
'success' => true,
'data' => $resultData,
'soap_request' => $barobillSoapClient->__getLastRequest(),
'soap_response' => $barobillSoapClient->__getLastResponse()
];
}
return [
'success' => true,
'data' => $result,
'soap_request' => $barobillSoapClient->__getLastRequest(),
'soap_response' => $barobillSoapClient->__getLastResponse()
];
} catch (SoapFault $e) {
return [
'success' => false,
'error' => 'SOAP 오류: ' . $e->getMessage(),
'error_code' => $e->getCode(),
'error_detail' => [
'fault_code' => $e->faultcode ?? null,
'fault_string' => $e->faultstring ?? null,
'soap_request' => $barobillSoapClient ? $barobillSoapClient->__getLastRequest() : null,
'soap_response' => $barobillSoapClient ? $barobillSoapClient->__getLastResponse() : null
]
];
} catch (Throwable $e) {
return [
'success' => false,
'error' => 'API 호출 오류 (치명적): ' . $e->getMessage(),
'error_detail' => [
'exception_type' => get_class($e),
'soap_request' => $barobillSoapClient ? $barobillSoapClient->__getLastRequest() : null,
'soap_response' => $barobillSoapClient ? $barobillSoapClient->__getLastResponse() : null
]
];
}
}
/**
* 바로빌 세금계산서 발행 (저장 + 발급)
*
* 문서 참고: etax/docs/barobill-api-doc/TAXINVOICE/RegistAndIssueTaxInvoice.php
*
* @param array $invoiceData 세금계산서 데이터
* @return array 응답 데이터
*/
function issueTaxInvoice($invoiceData) {
global $barobillCorpNum;
// MgtKey 생성 (관리번호) - 유니크한 키 생성
$mgtKey = $invoiceData['issueKey'] ?? 'MGT' . date('YmdHis') . rand(1000, 9999);
// 공급가액, 부가세, 합계 계산
$supplyAmt = 0;
$vat = 0;
foreach ($invoiceData['items'] ?? [] as $item) {
$itemSupplyAmt = floatval($item['supplyAmt'] ?? 0);
$itemVat = floatval($item['vat'] ?? 0);
$supplyAmt += $itemSupplyAmt;
$vat += $itemVat;
}
$total = $supplyAmt + $vat;
// TaxType 결정: 부가세가 0원이면 영세(2) 또는 면세(3)로 설정
// 과세(1)는 부가세가 0원 이상이어야 함
$taxType = 1; // 기본값: 과세
if ($vat == 0) {
// 부가세가 0원이면 영세로 설정 (또는 면세로 설정 가능)
$taxType = 2; // 2: 영세
}
// 바로빌 SOAP API 스펙에 맞게 데이터 변환
// 문서 참고: etax/docs/barobill-api-doc/TAXINVOICE/RegistAndIssueTaxInvoice.php
$taxInvoice = [
'IssueDirection' => 1, // 1: 정발행, 2: 역발행
'TaxInvoiceType' => 1, // 1: 세금계산서, 2: 계산서
'ModifyCode' => '', // 수정사유코드 (신규발행시 빈값)
'TaxType' => $taxType, // 1: 과세, 2: 영세, 3: 면세 (부가세가 0이면 영세로 설정)
'TaxCalcType' => 1, // 1: 소계합계, 2: 항목합계
'PurposeType' => 2, // 1: 영수, 2: 청구, 3: 없음
'WriteDate' => date('Ymd', strtotime($invoiceData['writeDate'] ?? date('Y-m-d'))), // 작성일자 (YYYYMMDD)
'AmountTotal' => number_format($supplyAmt, 0, '', ''), // 공급가액 합계
'TaxTotal' => number_format($vat, 0, '', ''), // 부가세 합계
'TotalAmount' => number_format($total, 0, '', ''), // 합계금액
'Cash' => '0', // 현금
'ChkBill' => '0', // 어음
'Note' => '0', // 외상
'Credit' => number_format($total, 0, '', ''), // 외상미수금 (합계금액과 일치해야 함)
'Remark1' => $invoiceData['memo'] ?? '', // 비고1
'Remark2' => '', // 비고2
'Remark3' => '', // 비고3
'Kwon' => '', // 권
'Ho' => '', // 호
'SerialNum' => '', // 일련번호
'InvoicerParty' => [
'MgtNum' => $mgtKey, // 관리번호
'CorpNum' => $barobillCorpNum, // 발행자 사업자번호 (CERTKEY와 연결된 사업자번호 사용)
'TaxRegID' => '', // 종사업장번호
'CorpName' => $invoiceData['supplierName'] ?? '', // 상호
'CEOName' => $invoiceData['supplierCeo'] ?? '', // 대표자명
'Addr' => $invoiceData['supplierAddr'] ?? '', // 주소
'BizType' => '', // 업태
'BizClass' => '', // 종목
'ContactID' => $invoiceData['supplierContactId'] ?? 'cbx0913', // 담당자 아이디 (바로빌 웹페이지 ID)
'ContactName' => $invoiceData['supplierContact'] ?? '', // 담당자명
'TEL' => $invoiceData['supplierTel'] ?? '', // 전화번호
'HP' => '', // 휴대폰
'Email' => $invoiceData['supplierEmail'] ?? '', // 이메일
],
'InvoiceeParty' => [
'MgtNum' => '', // 관리번호
'CorpNum' => str_replace('-', '', $invoiceData['recipientBizno'] ?? ''), // 사업자번호
'TaxRegID' => '', // 종사업장번호
'CorpName' => $invoiceData['recipientName'] ?? '', // 상호
'CEOName' => $invoiceData['recipientCeo'] ?? '', // 대표자명
'Addr' => $invoiceData['recipientAddr'] ?? '', // 주소
'BizType' => '', // 업태
'BizClass' => '', // 종목
'ContactID' => '', // 담당자 아이디
'ContactName' => $invoiceData['recipientContact'] ?? '', // 담당자명
'TEL' => $invoiceData['recipientTel'] ?? '', // 전화번호
'HP' => '', // 휴대폰
'Email' => $invoiceData['recipientEmail'] ?? '', // 이메일
],
'BrokerParty' => [], // 위수탁 거래시에만 사용
'TaxInvoiceTradeLineItems' => [
'TaxInvoiceTradeLineItem' => []
]
];
// 품목 데이터 변환
foreach ($invoiceData['items'] ?? [] as $item) {
$taxInvoice['TaxInvoiceTradeLineItems']['TaxInvoiceTradeLineItem'][] = [
'PurchaseExpiry' => '', // 공제기한
'Name' => $item['name'] ?? '', // 품명
'Information' => $item['spec'] ?? '', // 규격
'ChargeableUnit' => $item['qty'] ?? '1', // 수량
'UnitPrice' => number_format(floatval($item['unitPrice'] ?? 0), 0, '', ''), // 단가
'Amount' => number_format(floatval($item['supplyAmt'] ?? 0), 0, '', ''), // 공급가액
'Tax' => number_format(floatval($item['vat'] ?? 0), 0, '', ''), // 부가세
'Description' => $item['description'] ?? '', // 비고
];
}
// SOAP 메서드 호출
$params = [
'CorpNum' => $barobillCorpNum, // 발행자 사업자번호
'Invoice' => $taxInvoice,
'SendSMS' => false, // SMS 발송 여부
'ForceIssue' => false, // 강제발행 여부
'MailTitle' => '', // 이메일 제목
];
return callBarobillSOAP('RegistAndIssueTaxInvoice', $params);
}
/**
* 바로빌 세금계산서 조회
*
* 문서 참고: etax/docs/barobill-api-doc/TAXINVOICE/GetTaxInvoice.php
*
* @param string $mgtKey 관리번호 (MgtKey)
* @return array 응답 데이터
*/
function getTaxInvoice($mgtKey) {
global $barobillCorpNum;
$params = [
'CorpNum' => $barobillCorpNum,
'MgtKey' => $mgtKey
];
return callBarobillSOAP('GetTaxInvoice', $params);
}
/**
* 바로빌 세금계산서 상태 조회
*
* 문서 참고: etax/docs/barobill-api-doc/TAXINVOICE/GetTaxInvoiceStateEX.php
*
* @param string $mgtKey 관리번호 (MgtKey)
* @return array 응답 데이터
*/
function getTaxInvoiceState($mgtKey) {
global $barobillCorpNum;
$params = [
'CorpNum' => $barobillCorpNum,
'MgtKey' => $mgtKey
];
return callBarobillSOAP('GetTaxInvoiceStateEX', $params);
}
/**
* 바로빌 세금계산서 국세청 전송
*
* 문서 참고: etax/docs/barobill-api-doc/TAXINVOICE/SendToNTS.php
*
* @param string $mgtKey 관리번호 (MgtKey)
* @return array 응답 데이터
*/
function sendToNTS($mgtKey) {
global $barobillCorpNum;
$params = [
'CorpNum' => $barobillCorpNum,
'MgtKey' => $mgtKey
];
return callBarobillSOAP('SendToNTS', $params);
}
?>