413 lines
17 KiB
PHP
413 lines
17 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"를 저장하세요
|
|
*/
|
|
|
|
// 인증서 키(CERTKEY) 파일 경로
|
|
$certKeyFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/barobill_cert_key.txt';
|
|
$legacyApiKeyFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/barobill_api_key.txt'; // 기존 호환성
|
|
$corpNumFile = $_SERVER['DOCUMENT_ROOT'] . '/apikey/barobill_corp_num.txt';
|
|
$testModeFile = $_SERVER['DOCUMENT_ROOT'] . '/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 등 포함)
|
|
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' => $_SERVER['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 (Exception $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);
|
|
}
|
|
|
|
|
|
|