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); } ?>