diff --git a/apikey/barobill_api_key.txt b/apikey/barobill_api_key.txt
new file mode 100644
index 0000000..af2f171
--- /dev/null
+++ b/apikey/barobill_api_key.txt
@@ -0,0 +1 @@
+2DD6C76C-04DB-44F7-B6E9-3FC0B2211826
\ No newline at end of file
diff --git a/apikey/barobill_api_key.txt.example b/apikey/barobill_api_key.txt.example
new file mode 100644
index 0000000..af2f171
--- /dev/null
+++ b/apikey/barobill_api_key.txt.example
@@ -0,0 +1 @@
+2DD6C76C-04DB-44F7-B6E9-3FC0B2211826
\ No newline at end of file
diff --git a/apikey/barobill_cert_id.txt b/apikey/barobill_cert_id.txt
new file mode 100644
index 0000000..61a4c04
--- /dev/null
+++ b/apikey/barobill_cert_id.txt
@@ -0,0 +1,39 @@
+===========================================
+바로빌 인증서 ID (선택사항)
+===========================================
+
+⚠️ 주의: 이것은 로그인 이메일이 아닙니다!
+
+이 파일에는 바로빌 개발자센터에서 등록한
+공동인증서 또는 금융인증서의 ID를 입력하세요.
+
+===========================================
+설정 방법:
+===========================================
+
+1. 바로빌 개발자센터(https://dev.barobill.co.kr) 로그인
+ - 로그인 이메일: admin@codebridge-x.com (이것 아님!)
+
+2. 개발자센터 메뉴에서 "인증서 관리" 또는 "인증서 등록" 메뉴 찾기
+
+3. 공동인증서 또는 금융인증서 등록
+ - 인증서 파일(.pfx 또는 .p12) 업로드
+ - 인증서 비밀번호 입력
+
+4. 등록 후 발급받은 "인증서 ID" 복사
+ - 예: "CERT-2024-ABC123-XYZ789"
+ - 또는 숫자/문자 조합의 고유 ID
+
+5. 아래에 인증서 ID 입력:
+
+[여기에 인증서 ID 입력]
+
+===========================================
+참고:
+===========================================
+
+- 운영 환경: 세금계산서 발행 시 필수입니다
+- 테스트 환경: 인증서 ID 없이도 테스트 가능할 수 있습니다
+ (barobill_test_mode.txt에 "true"가 설정되어 있으면 선택사항)
+- 인증서가 없으면 실제 세금계산서를 발행할 수 없지만,
+ 테스트 API에서는 가상의 인증서를 사용할 수 있습니다
diff --git a/apikey/barobill_cert_id.txt.example b/apikey/barobill_cert_id.txt.example
new file mode 100644
index 0000000..a2061e7
--- /dev/null
+++ b/apikey/barobill_cert_id.txt.example
@@ -0,0 +1,3 @@
+여기에 바로빌 인증서 ID를 입력하세요 (선택사항)
+예: cert-id-here
+
diff --git a/apikey/barobill_cert_key.txt b/apikey/barobill_cert_key.txt
new file mode 100644
index 0000000..7e57088
--- /dev/null
+++ b/apikey/barobill_cert_key.txt
@@ -0,0 +1,37 @@
+===========================================
+바로빌 CERTKEY (인증서 키) - 필수
+===========================================
+
+⚠️ 중요: 이것은 로그인 이메일이 아닙니다!
+
+이 파일에는 바로빌 개발자센터에서 등록한
+공동인증서 또는 금융인증서의 CERTKEY를 입력하세요.
+
+===========================================
+설정 방법:
+===========================================
+
+1. 바로빌 개발자센터(https://dev.barobill.co.kr) 로그인
+
+2. 개발자센터 메뉴에서 "인증서 관리" 또는 "인증서 등록" 메뉴 찾기
+
+3. 공동인증서 또는 금융인증서 등록
+ - 인증서 파일(.pfx 또는 .p12) 업로드
+ - 인증서 비밀번호 입력
+
+4. 등록 후 발급받은 "CERTKEY" 복사
+ - 예: "CERT-2024-ABC123-XYZ789"
+ - 또는 숫자/문자 조합의 고유 키
+
+5. 아래에 CERTKEY 입력:
+
+[여기에 CERTKEY 입력]
+
+===========================================
+참고:
+===========================================
+
+- 세금계산서 발행에 필수입니다
+- CERTKEY가 없으면 실제 API를 호출할 수 없습니다
+- 테스트 환경에서도 CERTKEY가 필요할 수 있습니다
+
diff --git a/apikey/barobill_corp_num.txt b/apikey/barobill_corp_num.txt
new file mode 100644
index 0000000..7c0a821
--- /dev/null
+++ b/apikey/barobill_corp_num.txt
@@ -0,0 +1 @@
+6648603713
\ No newline at end of file
diff --git a/apikey/barobill_test_mode.txt b/apikey/barobill_test_mode.txt
new file mode 100644
index 0000000..f32a580
--- /dev/null
+++ b/apikey/barobill_test_mode.txt
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/apikey/barobill_test_mode.txt.example b/apikey/barobill_test_mode.txt.example
new file mode 100644
index 0000000..f32a580
--- /dev/null
+++ b/apikey/barobill_test_mode.txt.example
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/apikey/barobill_user_id.txt b/apikey/barobill_user_id.txt
new file mode 100644
index 0000000..70bfd2d
--- /dev/null
+++ b/apikey/barobill_user_id.txt
@@ -0,0 +1 @@
+cbx0913
diff --git a/apikey/claude_api.txt b/apikey/claude_api.txt
new file mode 100644
index 0000000..48370e8
--- /dev/null
+++ b/apikey/claude_api.txt
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/apikey/gcs_config.txt b/apikey/gcs_config.txt
new file mode 100644
index 0000000..9667bbc
--- /dev/null
+++ b/apikey/gcs_config.txt
@@ -0,0 +1,2 @@
+bucket_name=codebridge-speech-audio-files
+
diff --git a/apikey/gemini_api_key.txt b/apikey/gemini_api_key.txt
new file mode 100644
index 0000000..85fa17f
--- /dev/null
+++ b/apikey/gemini_api_key.txt
@@ -0,0 +1 @@
+AIzaSyAS3bAzmXlhhZHgO3buFiTGzavXZ6ubYq8
\ No newline at end of file
diff --git a/apikey/google_api.txt b/apikey/google_api.txt
new file mode 100644
index 0000000..39f4a79
--- /dev/null
+++ b/apikey/google_api.txt
@@ -0,0 +1 @@
+f7d58533aa1dba0db19d799d85f22686684521d2
\ No newline at end of file
diff --git a/apikey/google_service_account.json b/apikey/google_service_account.json
new file mode 100644
index 0000000..227553c
--- /dev/null
+++ b/apikey/google_service_account.json
@@ -0,0 +1,13 @@
+{
+ "type": "service_account",
+ "project_id": "codebridge-chatbot",
+ "private_key_id": "bc6e2c8d65ac9567824b1c3bae245f2249c52aed",
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDddbnvIMgXEul\nQKi2hY+LS1zJLHDLlVzlhJ3WU8XJP38z2BeINSSwoUXnLLiCrhideDBoY7p6sD//\nFG6/3dNarsj+UwBDUPcbD8aoxjRUM637BNprb0dOexfLifnKxdDYrsGf1ZwaGvk1\nVtyzvZ0FKsmvMmZJhTwPQD1Qng0ewtRs63U2kV/K/2CMB/Vgw2vh2KzbaJtOncBB\nZ++ZnRAlQntnmvQON2hP+Q/N43oHCOXz6TshphN2EoEgncKkuQxkSHFAPBKtAR0B\nf4sfQ6jFBGJaYcLCm1vIioirw6zNam5CvkbJnuhG6DfSiczynOKH71Y5XvldKAn9\nQY9LDQWnAgMBAAECggEAP9hbBUEHV+et8eGn+k6wL6DDQxcYFPfJ61KhN+QPRAhQ\npX+dWCl7vZJAQh3SeyUg9zbOIRsKS48MqGZlMpjjs8hJ8QDtog9tV9KUYoazixmC\nZ+8S5Wro0NLWV+7OwBRTfqO+rVXZ8pEC/BBOcQuroYdzb5232aYCkzy5in7F7FjH\nMCAxUDJhCM4yv05zM/8SV1Ypn6/f1+ZhIpkzCxFlA/Gx85l7pwV6LCaZjYgDiNAY\nDbVEccshrdKXBVKhkyuuGToDLsiRCdish5zdyA8COPr4tflnXvkvQk0MwjZTEygC\nJ8Ceh7oTY44A/rzSqo2kXaCCByGVnFAXJ3z1JoRbCQKBgQDsANCgHZHSzyvgGlyk\nFST7lgS36r1zNEwyGMIZC/edglXqXGeDnelTiSii3MQcq3fqXvXgVr6Pk/vLHf11\nz8zPVg/lJeYhrz6EF2loMN8rhp0yjzYRMT7AZ8kITtCV5HEgmnU+/o4mNwJfN6qK\n26HTSjjmujAjejDL7my+8naSHQKBgQDUBZop2CZdrEALVp3DIw07ZeFEbnG8j5U9\nvJveaz9wZWZnMlxJgc+dk6I1bZHqlIGU3vJ95xJ6JjnoRhPSi1yceaUFEU9UkQLY\n0XuQwsgaFRJLqHcTwZi1Z//IGagtPkAYXDsJDoRr2CEz2lzwd5RV0XCmDoZN9xV6\n+wnOpGlrkwKBgAE3l9vbiy79JorHWAb4nPI3OdsA+O0pLeNsQUQDzckgLPVCeL4z\nCEsIAA+m99P2Bm5NAxOfHuh6qOfJRc9fvPyswvQ5l9BAqR/hRwfkiKIe1Zy3JF4+\nVMaFQoIqdeTwAq1aXpRul6kWy4pWLSj+LP17+oMmHq1wKeRDXIg3k+j5AoGAQGfD\nQNrMLMBaZBdXrSNErbpxB5yVKDZlm29j2diyWK40wTxnFF0+eBuUtq4mGSArjNF8\n0AoVbs2V4Z0IAHkdFNtO6Y8sjf/O4ZYg9wR0TJgCCsGOCo5QmSqSZHKGx9eVGNFL\njaC/URNCYsH+YX2xrbAFjCv1WFGqUMVZYVBIRckCgYEA3yPZlawHMbHtFXFPL+by\nANKqtgX5mKO7E90kqTTumhOpkuywe3okR30wfs/kzX24cL/+brXs7naYyPDYRZz5\nxAMPGa5D4BvVchz1EBQh4lygA6gRlX2Yy/B3grpyZyCaSANX8sReML3wzn+AQze4\nfVaQ2YB6//fwv/5s95n1FAM=\n-----END PRIVATE KEY-----\n",
+ "client_email": "vertex-ai-client@codebridge-chatbot.iam.gserviceaccount.com",
+ "client_id": "114574435593304532521",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/vertex-ai-client%40codebridge-chatbot.iam.gserviceaccount.com",
+ "universe_domain": "googleapis.com"
+ }
\ No newline at end of file
diff --git a/apikey/google_vertex_api.txt b/apikey/google_vertex_api.txt
new file mode 100644
index 0000000..85fa17f
--- /dev/null
+++ b/apikey/google_vertex_api.txt
@@ -0,0 +1 @@
+AIzaSyAS3bAzmXlhhZHgO3buFiTGzavXZ6ubYq8
\ No newline at end of file
diff --git a/apikey/notion.txt b/apikey/notion.txt
new file mode 100644
index 0000000..9e9d582
--- /dev/null
+++ b/apikey/notion.txt
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/apikey/opendart.txt b/apikey/opendart.txt
new file mode 100644
index 0000000..a4a0890
--- /dev/null
+++ b/apikey/opendart.txt
@@ -0,0 +1 @@
+ad5002bf2c7d2f93eac3c6f9ff1d4b63bf3027bb
\ No newline at end of file
diff --git a/barobill_registration/index.php b/barobill_registration/index.php
index a7a1bd5..439dbf6 100644
--- a/barobill_registration/index.php
+++ b/barobill_registration/index.php
@@ -60,7 +60,7 @@
현황
-
+
세금계산서
diff --git a/etax/README_DB.md b/etax/README_DB.md
new file mode 100644
index 0000000..a8e34ed
--- /dev/null
+++ b/etax/README_DB.md
@@ -0,0 +1,132 @@
+# 전자세금계산서 데이터베이스 스키마
+
+## 테이블 구조
+
+### 1. etax_tenants (테넌트 관리)
+멀티테넌시 환경에서 각 고객사(테넌트)의 기본 정보를 저장합니다.
+
+**주요 필드:**
+- `id`: 테넌트 고유 ID
+- `name`: 기업명
+- `bizno`: 사업자번호 (UNIQUE)
+- `barobill_api_key`: 바로빌 API 키
+- `barobill_cert_id`: 바로빌 인증서 ID
+- `status`: 활성 상태 (active/inactive/suspended)
+
+### 2. etax_certificates (인증서 관리)
+각 테넌트의 공동인증서 또는 금융인증서 정보를 관리합니다.
+
+**주요 필드:**
+- `id`: 인증서 고유 ID
+- `tenant_id`: 테넌트 ID (FK)
+- `type`: 인증서 유형 (public/financial)
+- `valid_from`, `valid_to`: 인증서 유효기간
+- `barobill_cert_id`: 바로빌 인증서 ID
+
+### 3. etax_tax_invoices (세금계산서 메인)
+전자세금계산서의 기본 정보를 저장합니다.
+
+**주요 필드:**
+- `id`: 세금계산서 고유 ID
+- `tenant_id`: 테넌트 ID (FK)
+- `issue_key`: 바로빌 발행 키 (UNIQUE)
+- `supply_amt`, `vat`, `total`: 금액 정보
+- `write_date`, `supply_date`: 작성일자, 공급일자
+- `state`: 상태 (draft/issued/sent/cancelled)
+- `nts_receipt_no`: 국세청 접수번호
+
+### 4. etax_parties (공급자/수취자 정보)
+세금계산서의 공급자와 수취자 정보를 저장합니다.
+
+**주요 필드:**
+- `id`: 고유 ID
+- `invoice_id`: 세금계산서 ID (FK)
+- `role`: 역할 (supplier/recipient/trustee)
+- `bizno`, `corp_name`, `ceo`: 사업자 정보
+- `addr`, `email`, `tel`: 연락처 정보
+
+### 5. etax_line_items (품목 상세)
+세금계산서의 품목별 상세 정보를 저장합니다.
+
+**주요 필드:**
+- `id`: 고유 ID
+- `invoice_id`: 세금계산서 ID (FK)
+- `item_name`: 품목명
+- `qty`, `unit_price`: 수량, 단가
+- `supply_amt`, `vat`: 공급가액, 부가세
+- `vat_type`: 부가세 유형 (vat/zero/exempt)
+
+### 6. etax_transmission_logs (전송 로그)
+바로빌 API 호출 및 국세청 전송 로그를 기록합니다.
+
+**주요 필드:**
+- `id`: 로그 고유 ID
+- `invoice_id`: 세금계산서 ID (FK)
+- `transmission_type`: 전송 유형 (issue/cancel/nts_send)
+- `status`: 상태 (success/failed/pending)
+- `response_data`: API 응답 데이터
+- `error_message`: 에러 메시지
+
+## 설치 방법
+
+### 1. 데이터베이스 생성 (선택사항)
+```sql
+CREATE DATABASE IF NOT EXISTS `chandj` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE `chandj`;
+```
+
+### 2. 스키마 실행
+```bash
+# MySQL/MariaDB 접속
+mysql -u chandj -p chandj < etax/db_schema.sql
+```
+
+또는 phpMyAdmin이나 다른 DB 관리 도구에서 `etax/db_schema.sql` 파일을 실행하세요.
+
+### 3. 권한 확인
+테이블이 정상적으로 생성되었는지 확인:
+```sql
+SHOW TABLES LIKE 'etax_%';
+```
+
+## 사용 방법
+
+### PHP에서 데이터베이스 연결
+```php
+require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
+$pdo = db_connect();
+
+// 테넌트 조회 예시
+$stmt = $pdo->prepare("SELECT * FROM etax_tenants WHERE id = ?");
+$stmt->execute([$tenant_id]);
+$tenant = $stmt->fetch(PDO::FETCH_ASSOC);
+```
+
+### 멀티테넌시 쿼리 예시
+```php
+// 항상 tenant_id로 필터링
+$stmt = $pdo->prepare("
+ SELECT * FROM etax_tax_invoices
+ WHERE tenant_id = ? AND state = 'issued'
+ ORDER BY created_at DESC
+");
+$stmt->execute([$tenant_id]);
+$invoices = $stmt->fetchAll(PDO::FETCH_ASSOC);
+```
+
+## 주의사항
+
+1. **외래키 제약조건**: 모든 외래키는 CASCADE 삭제로 설정되어 있어, 부모 레코드 삭제 시 자식 레코드도 자동 삭제됩니다.
+
+2. **인덱스**: 자주 조회되는 필드에 인덱스를 설정했습니다. 추가 인덱스가 필요하면 성능 모니터링 후 추가하세요.
+
+3. **문자셋**: utf8mb4를 사용하여 이모지 등 모든 유니코드 문자를 지원합니다.
+
+4. **보안**:
+ - `barobill_api_key`는 암호화하여 저장하는 것을 권장합니다.
+ - `kms_key_ref`는 암호화된 인증서 참조를 저장합니다.
+
+## 마이그레이션
+
+기존 JSON 파일(`invoices_data.json`)에서 데이터를 마이그레이션하려면 별도의 마이그레이션 스크립트를 작성해야 합니다.
+
diff --git a/etax/api/API_URL_GUIDE.md b/etax/api/API_URL_GUIDE.md
new file mode 100644
index 0000000..2c92e24
--- /dev/null
+++ b/etax/api/API_URL_GUIDE.md
@@ -0,0 +1,73 @@
+# 바로빌 API 엔드포인트 URL 설정 가이드
+
+## 현재 오류
+
+```
+Could not resolve host: test-api.barobill.co.kr
+```
+
+이 오류는 DNS 해결 실패로, API 엔드포인트 URL이 잘못되었을 가능성이 높습니다.
+
+## 해결 방법
+
+### 1. 바로빌 개발자센터에서 정확한 URL 확인
+
+1. [바로빌 개발자센터](https://dev.barobill.co.kr) 로그인
+2. API 문서 또는 샘플 코드 확인
+3. 실제 API 엔드포인트 URL 확인
+
+### 2. 일반적인 바로빌 API URL 패턴
+
+바로빌 API는 다음과 같은 URL 패턴을 사용할 수 있습니다:
+
+#### 운영 환경
+- `https://api.barobill.co.kr`
+- `https://www.barobill.co.kr/api`
+- `https://barobill.co.kr/api`
+
+#### 테스트 환경
+- `https://test.barobill.co.kr`
+- `https://test-api.barobill.co.kr` (현재 사용 중 - DNS 오류 발생)
+- `https://dev.barobill.co.kr/api`
+- 개발자센터에서 제공하는 별도 테스트 URL
+
+### 3. 설정 파일 수정
+
+`etax/api/barobill_config.php` 파일의 다음 부분을 수정하세요:
+
+```php
+$barobillApiBaseUrl = $isTestMode
+ ? 'https://실제_테스트_URL' // 바로빌 개발자센터에서 확인한 URL
+ : 'https://실제_운영_URL'; // 바로빌 개발자센터에서 확인한 URL
+```
+
+### 4. API 키 확인
+
+현재 `apikey/barobill_api_key.txt` 파일에 API 키가 제대로 입력되어 있는지 확인하세요.
+
+- API 키가 없으면 시뮬레이션 모드로 동작합니다
+- API 키가 있으면 실제 바로빌 API를 호출합니다
+
+### 5. 네트워크/DNS 확인
+
+만약 URL이 맞는데도 DNS 오류가 발생한다면:
+
+1. **인터넷 연결 확인**
+2. **방화벽 설정 확인**
+3. **DNS 서버 변경** (Google DNS: 8.8.8.8, 8.8.4.4)
+4. **호스트 파일 확인** (Windows: C:\Windows\System32\drivers\etc\hosts)
+
+### 6. 임시 해결책 (시뮬레이션 모드)
+
+실제 API 연동 전까지는:
+
+1. `apikey/barobill_api_key.txt` 파일을 비워두거나
+2. 파일 내용을 주석 처리하면
+3. 시뮬레이션 모드로 동작하여 테스트할 수 있습니다
+
+## 참고 자료
+
+- [바로빌 개발자센터](https://dev.barobill.co.kr)
+- [바로빌 세금계산서 API 문서](https://dev.barobill.co.kr/docs/references/%EC%84%B8%EA%B8%88%EA%B3%84%EC%82%B0%EC%84%9C-API)
+- 바로빌 고객지원: 1544-9256
+
diff --git a/etax/api/README.md b/etax/api/README.md
new file mode 100644
index 0000000..4b3bfbd
--- /dev/null
+++ b/etax/api/README.md
@@ -0,0 +1,147 @@
+# 바로빌 API 연동 가이드
+
+## ⚠️ 중요: 바로빌은 SOAP 웹서비스를 사용합니다
+
+바로빌 API는 REST API가 아닌 **SOAP 웹서비스**를 사용합니다.
+- 테스트 환경: `https://testws.baroservice.com/TI.asmx?WSDL`
+- 운영 환경: `https://ws.baroservice.com/TI.asmx?WSDL`
+
+## API 키 설정
+
+### 1. 필수 파일 생성
+
+`apikey` 폴더에 다음 파일들을 생성하세요:
+
+#### 필수 파일
+- `barobill_cert_key.txt`: CERTKEY (인증서 키)
+ - 바로빌 개발자센터에서 인증서 등록 후 발급받은 CERTKEY
+ - 세금계산서 발행에 필수
+
+- `barobill_corp_num.txt`: 사업자번호
+ - 세금계산서를 발행할 회사의 사업자번호
+ - 하이픈(-) 없이 숫자만 입력
+ - 예: `1234567890`
+
+#### 선택 파일
+- `barobill_test_mode.txt`: 테스트 모드 사용 시 "test" 또는 "true" 입력
+ - 테스트 환경: `https://testws.baroservice.com/TI.asmx?WSDL`
+ - 운영 환경: `https://ws.baroservice.com/TI.asmx?WSDL`
+
+### 2. CERTKEY 발급 방법
+
+1. [바로빌 개발자센터](https://dev.barobill.co.kr) 접속
+2. 회원가입 및 로그인
+3. 개발자센터 메뉴에서 "인증서 관리" 또는 "인증서 등록" 메뉴 찾기
+4. 공동인증서(.pfx 또는 .p12 파일) 또는 금융인증서 업로드
+5. 인증서 비밀번호 입력 및 등록
+6. 등록 완료 후 발급받은 **CERTKEY** 확인
+ - 예: "CERT-2024-ABC123-XYZ789" 또는 숫자/문자 조합의 고유 키
+7. `apikey/barobill_cert_key.txt` 파일에 CERTKEY 저장
+
+### 3. 사업자번호 설정
+
+1. 세금계산서를 발행할 회사의 사업자번호 확인
+2. 하이픈(-) 없이 숫자만 입력
+ - 예: `123-45-67890` → `1234567890`
+3. `apikey/barobill_corp_num.txt` 파일에 저장
+
+### 4. 테스트 모드 설정
+
+테스트 환경을 사용하려면:
+
+1. `apikey/barobill_test_mode.txt` 파일 생성
+2. 파일 내용에 `test` 또는 `true` 입력
+3. 저장
+
+## API 엔드포인트
+
+### 세금계산서 발행 (저장 + 발급)
+- **SOAP 메서드**: `RegistAndIssueTaxInvoice`
+- **파일**: `issue.php`
+- **문서**: `etax/docs/barobill-api-doc/TAXINVOICE/RegistAndIssueTaxInvoice.php`
+
+### 세금계산서 조회
+- **SOAP 메서드**: `GetTaxInvoice`
+- **파일**: `invoices.php`
+- **문서**: `etax/docs/barobill-api-doc/TAXINVOICE/GetTaxInvoice.php`
+
+### 세금계산서 상태 조회
+- **SOAP 메서드**: `GetTaxInvoiceStateEX`
+- **문서**: `etax/docs/barobill-api-doc/TAXINVOICE/GetTaxInvoiceStateEX.php`
+
+### 국세청 전송
+- **SOAP 메서드**: `SendToNTS`
+- **파일**: `status.php`
+- **문서**: `etax/docs/barobill-api-doc/TAXINVOICE/SendToNTS.php`
+
+## SOAP API 사용 예제
+
+### 세금계산서 발행
+
+```php
+require_once(__DIR__ . '/barobill_config.php');
+
+$invoiceData = [
+ 'issueKey' => 'MGT20241201123456', // 관리번호 (MgtKey)
+ 'supplierBizno' => '1234567890',
+ 'supplierName' => '공급자 회사명',
+ 'recipientBizno' => '0987654321',
+ 'recipientName' => '수취인 회사명',
+ 'supplyDate' => '2024-12-01',
+ 'items' => [
+ [
+ 'name' => '품목명',
+ 'spec' => '규격',
+ 'qty' => '1',
+ 'unitPrice' => 10000,
+ 'supplyAmt' => 10000,
+ 'vat' => 1000
+ ]
+ ],
+ 'memo' => '비고'
+];
+
+$result = issueTaxInvoice($invoiceData);
+```
+
+### 국세청 전송
+
+```php
+require_once(__DIR__ . '/barobill_config.php');
+
+$mgtKey = 'MGT20241201123456'; // 관리번호
+$result = sendToNTS($mgtKey);
+```
+
+## 참고 문서
+
+- [바로빌 개발자센터](https://dev.barobill.co.kr)
+- [바로빌 세금계산서 API 문서](https://dev.barobill.co.kr/docs/references/%EC%84%B8%EA%B8%88%EA%B3%84%EC%82%B0%EC%84%9C-API)
+- 로컬 문서: `etax/docs/barobill-api-doc/` 폴더
+
+## 주의사항
+
+1. **CERTKEY와 사업자번호는 절대 공개 저장소에 커밋하지 마세요**
+2. `.gitignore`에 `apikey/*.txt` (예제 파일 제외) 추가 권장
+3. 테스트 환경과 운영 환경의 CERTKEY를 분리하여 관리하세요
+4. SOAP 클라이언트는 PHP의 `SoapClient` 클래스를 사용합니다
+5. PHP 7.3 이상에서 `SoapClient` 확장이 활성화되어 있어야 합니다
+
+## 문제 해결
+
+### SOAP 클라이언트 생성 실패
+
+- PHP `SoapClient` 확장이 설치되어 있는지 확인
+- `php -m | grep soap` 명령으로 확인
+- 설치되지 않았다면 PHP 확장 설치 필요
+
+### CERTKEY 오류
+
+- CERTKEY가 올바르게 입력되었는지 확인
+- 바로빌 개발자센터에서 인증서가 정상적으로 등록되었는지 확인
+- 테스트 모드에서는 CERTKEY가 선택사항일 수 있음
+
+### 사업자번호 오류
+
+- 하이픈(-) 없이 숫자만 입력했는지 확인
+- 발행자 사업자번호가 올바른지 확인
diff --git a/etax/api/barobill_config.php b/etax/api/barobill_config.php
new file mode 100644
index 0000000..c52547f
--- /dev/null
+++ b/etax/api/barobill_config.php
@@ -0,0 +1,418 @@
+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);
+}
+
+?>
+
diff --git a/etax/api/debug_test.php b/etax/api/debug_test.php
new file mode 100644
index 0000000..5c7efc3
--- /dev/null
+++ b/etax/api/debug_test.php
@@ -0,0 +1,34 @@
+load();
+ echo ".env loaded.\n";
+ } catch (Exception $e) {
+ echo "Error loading .env: " . $e->getMessage() . "\n";
+ }
+} else {
+ echo ".env file NOT found.\n";
+}
+
+$root = getenv('DOCUMENT_ROOT');
+echo "DOCUMENT_ROOT from getenv: " . var_export($root, true) . "\n";
+echo "DOCUMENT_ROOT from \$_ENV: " . var_export($_ENV['DOCUMENT_ROOT'] ?? 'unset', true) . "\n";
+echo "DOCUMENT_ROOT from \$_SERVER: " . var_export($_SERVER['DOCUMENT_ROOT'] ?? 'unset', true) . "\n";
diff --git a/etax/api/delete.php b/etax/api/delete.php
new file mode 100644
index 0000000..3d523ed
--- /dev/null
+++ b/etax/api/delete.php
@@ -0,0 +1,163 @@
+ false,
+ "error" => "Method not allowed"
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+// 요청 본문에서 invoiceId 가져오기
+$rawInput = file_get_contents('php://input');
+$input = json_decode($rawInput, true);
+$invoiceId = $input['invoiceId'] ?? $_GET['invoiceId'] ?? null;
+
+// 디버깅: 요청 데이터 로그
+error_log("Delete request - Raw input: " . $rawInput);
+error_log("Delete request - Parsed input: " . print_r($input, true));
+error_log("Delete request - invoiceId: " . var_export($invoiceId, true));
+
+if (!$invoiceId) {
+ http_response_code(400);
+ echo json_encode([
+ "success" => false,
+ "error" => "invoiceId is required",
+ "debug" => [
+ "rawInput" => $rawInput,
+ "parsedInput" => $input,
+ "getParams" => $_GET
+ ]
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+$dataFile = __DIR__ . '/invoices_data.json';
+
+// 파일에서 데이터 읽기
+$existingData = [];
+if (file_exists($dataFile)) {
+ $fileContent = file_get_contents($dataFile);
+ if ($fileContent !== false) {
+ $existingData = json_decode($fileContent, true);
+ if (!is_array($existingData)) {
+ $existingData = [];
+ }
+ } else {
+ error_log("Failed to read invoices_data.json file");
+ }
+} else {
+ error_log("invoices_data.json file does not exist");
+}
+
+if (!isset($existingData['invoices'])) {
+ $existingData['invoices'] = [];
+}
+
+// 디버깅: 현재 파일에 있는 ID 목록
+$currentIds = array_map(function($inv) {
+ return $inv['id'] ?? 'no-id';
+}, $existingData['invoices']);
+error_log("Current invoice IDs in file: " . implode(', ', $currentIds));
+error_log("Looking for invoiceId: " . $invoiceId);
+
+// 삭제된 ID 목록 파일
+$deletedIdsFile = __DIR__ . '/deleted_ids.json';
+$deletedIds = [];
+
+// 삭제된 ID 목록 읽기
+if (file_exists($deletedIdsFile)) {
+ $deletedContent = file_get_contents($deletedIdsFile);
+ if ($deletedContent !== false) {
+ $deletedIds = json_decode($deletedContent, true);
+ if (!is_array($deletedIds)) {
+ $deletedIds = [];
+ }
+ }
+}
+
+// 기본 mock 데이터인 경우, 삭제된 ID 목록에 추가
+$defaultMockIds = ['inv_001', 'inv_002', 'inv_003'];
+if (in_array($invoiceId, $defaultMockIds)) {
+ // 이미 삭제된 경우
+ if (in_array($invoiceId, $deletedIds)) {
+ http_response_code(404);
+ echo json_encode([
+ "success" => false,
+ "error" => "Invoice not found",
+ "message" => "이미 삭제된 항목입니다."
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+ }
+
+ // 삭제된 ID 목록에 추가
+ $deletedIds[] = $invoiceId;
+ $deletedIds = array_unique($deletedIds); // 중복 제거
+ file_put_contents($deletedIdsFile, json_encode($deletedIds, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+
+ echo json_encode([
+ "success" => true,
+ "message" => "Invoice deleted successfully",
+ "deletedId" => $invoiceId
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+// 해당 ID의 인보이스 찾아서 삭제
+$beforeCount = count($existingData['invoices']);
+$foundInvoice = null;
+foreach ($existingData['invoices'] as $index => $invoice) {
+ $currentId = $invoice['id'] ?? '';
+ // 문자열 비교 (타입 변환)
+ if ((string)$currentId === (string)$invoiceId) {
+ $foundInvoice = $invoice;
+ unset($existingData['invoices'][$index]);
+ break;
+ }
+}
+$existingData['invoices'] = array_values($existingData['invoices']); // 인덱스 재정렬
+$afterCount = count($existingData['invoices']);
+
+if ($beforeCount === $afterCount) {
+ // 삭제할 항목이 없었음
+ error_log("Invoice not found - beforeCount: $beforeCount, afterCount: $afterCount");
+ http_response_code(404);
+ echo json_encode([
+ "success" => false,
+ "error" => "Invoice not found",
+ "message" => "해당 세금계산서를 찾을 수 없습니다. 이미 삭제되었거나 존재하지 않는 항목일 수 있습니다.",
+ "debug" => [
+ "invoiceId" => $invoiceId,
+ "invoiceIdType" => gettype($invoiceId),
+ "totalInvoices" => $beforeCount,
+ "availableIds" => $currentIds
+ ]
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+// 파일에 저장
+$jsonData = json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+$saveResult = @file_put_contents($dataFile, $jsonData);
+
+if ($saveResult === false) {
+ http_response_code(500);
+ echo json_encode([
+ "success" => false,
+ "error" => "Failed to save data"
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+echo json_encode([
+ "success" => true,
+ "message" => "Invoice deleted successfully",
+ "deletedId" => $invoiceId
+], JSON_UNESCAPED_UNICODE);
+?>
+
diff --git a/etax/api/deleted_ids.json b/etax/api/deleted_ids.json
new file mode 100644
index 0000000..4723510
--- /dev/null
+++ b/etax/api/deleted_ids.json
@@ -0,0 +1,5 @@
+[
+ "inv_001",
+ "inv_002",
+ "inv_003"
+]
\ No newline at end of file
diff --git a/etax/api/invoices.php b/etax/api/invoices.php
new file mode 100644
index 0000000..89eeabe
--- /dev/null
+++ b/etax/api/invoices.php
@@ -0,0 +1,170 @@
+ "inv_001",
+ "issueKey" => "BARO-2024-001",
+ "supplierBizno" => "123-45-67890",
+ "supplierName" => "(주)건축자재",
+ "recipientBizno" => "987-65-43210",
+ "recipientName" => "대박 건설(주)",
+ "supplyDate" => "2024-11-01",
+ "items" => [
+ [
+ "name" => "시멘트 50kg",
+ "qty" => 100,
+ "unitPrice" => 8000,
+ "supplyAmt" => 800000,
+ "vat" => 80000,
+ "vatType" => "vat"
+ ],
+ [
+ "name" => "철근 10mm",
+ "qty" => 50,
+ "unitPrice" => 12000,
+ "supplyAmt" => 600000,
+ "vat" => 60000,
+ "vatType" => "vat"
+ ]
+ ],
+ "totalSupplyAmt" => 1400000,
+ "totalVat" => 140000,
+ "total" => 1540000,
+ "status" => "sent",
+ "ntsReceiptNo" => "NTS-2024-001234",
+ "memo" => "정기 납품",
+ "createdAt" => "2024-11-01T10:30:00",
+ "sentAt" => "2024-11-01T10:35:00"
+ ],
+ [
+ "id" => "inv_002",
+ "issueKey" => "BARO-2024-002",
+ "supplierBizno" => "123-45-67890",
+ "supplierName" => "(주)건축자재",
+ "recipientBizno" => "111-22-33333",
+ "recipientName" => "강남 부동산(주)",
+ "supplyDate" => "2024-11-05",
+ "items" => [
+ [
+ "name" => "도배지",
+ "qty" => 200,
+ "unitPrice" => 5000,
+ "supplyAmt" => 1000000,
+ "vat" => 100000,
+ "vatType" => "vat"
+ ]
+ ],
+ "totalSupplyAmt" => 1000000,
+ "totalVat" => 100000,
+ "total" => 1100000,
+ "status" => "issued",
+ "memo" => "",
+ "createdAt" => "2024-11-05T14:20:00"
+ ],
+ [
+ "id" => "inv_003",
+ "issueKey" => "BARO-2024-003",
+ "supplierBizno" => "123-45-67890",
+ "supplierName" => "(주)건축자재",
+ "recipientBizno" => "555-66-77777",
+ "recipientName" => "성수 인테리어(주)",
+ "supplyDate" => "2024-11-10",
+ "items" => [
+ [
+ "name" => "타일 30x30",
+ "qty" => 500,
+ "unitPrice" => 3000,
+ "supplyAmt" => 1500000,
+ "vat" => 150000,
+ "vatType" => "vat"
+ ],
+ [
+ "name" => "접착제",
+ "qty" => 20,
+ "unitPrice" => 15000,
+ "supplyAmt" => 300000,
+ "vat" => 30000,
+ "vatType" => "vat"
+ ]
+ ],
+ "totalSupplyAmt" => 1800000,
+ "totalVat" => 180000,
+ "total" => 1980000,
+ "status" => "sent",
+ "ntsReceiptNo" => "NTS-2024-001567",
+ "memo" => "긴급 납품",
+ "createdAt" => "2024-11-10T09:15:00",
+ "sentAt" => "2024-11-10T09:20:00"
+ ]
+];
+
+// 삭제된 ID 목록 읽기
+$deletedIdsFile = __DIR__ . '/deleted_ids.json';
+$deletedIds = [];
+if (file_exists($deletedIdsFile)) {
+ $deletedContent = file_get_contents($deletedIdsFile);
+ if ($deletedContent !== false) {
+ $deletedIds = json_decode($deletedContent, true);
+ if (!is_array($deletedIds)) {
+ $deletedIds = [];
+ }
+ }
+}
+
+// 기본 mock 데이터에서 삭제된 항목 제외
+$invoices = array_filter($invoices, function($invoice) use ($deletedIds) {
+ return !in_array($invoice['id'] ?? '', $deletedIds);
+});
+$invoices = array_values($invoices); // 인덱스 재정렬
+
+// 파일에서 저장된 데이터 읽기 (있는 경우)
+$dataFile = __DIR__ . '/invoices_data.json';
+if (file_exists($dataFile)) {
+ $fileContent = file_get_contents($dataFile);
+ if ($fileContent !== false) {
+ $savedData = json_decode($fileContent, true);
+ if (json_last_error() === JSON_ERROR_NONE && isset($savedData['invoices']) && is_array($savedData['invoices']) && count($savedData['invoices']) > 0) {
+ // 저장된 데이터에서도 삭제된 항목 제외
+ $savedData['invoices'] = array_filter($savedData['invoices'], function($invoice) use ($deletedIds) {
+ return !in_array($invoice['id'] ?? '', $deletedIds);
+ });
+ $savedData['invoices'] = array_values($savedData['invoices']);
+
+ // 저장된 데이터를 우선하고, 기본 mock 데이터와 병합
+ // 저장된 데이터가 최신이므로 먼저 배치
+ $invoices = array_merge($savedData['invoices'], $invoices);
+ // 중복 제거 (id 기준) - 먼저 나온 것이 유지됨 (저장된 데이터 우선)
+ $uniqueInvoices = [];
+ $seenIds = [];
+ foreach ($invoices as $invoice) {
+ if (!in_array($invoice['id'], $seenIds)) {
+ $uniqueInvoices[] = $invoice;
+ $seenIds[] = $invoice['id'];
+ }
+ }
+ $invoices = $uniqueInvoices;
+ } elseif (json_last_error() !== JSON_ERROR_NONE) {
+ error_log("invoices_data.json JSON 파싱 오류: " . json_last_error_msg());
+ }
+ } else {
+ error_log("invoices_data.json 파일 읽기 실패: " . $dataFile);
+ }
+}
+
+// 최신순 정렬
+usort($invoices, function($a, $b) {
+ return strtotime($b['createdAt'] ?? $b['supplyDate']) - strtotime($a['createdAt'] ?? $a['supplyDate']);
+});
+
+$response = [
+ "success" => true,
+ "invoices" => $invoices,
+ "count" => count($invoices)
+];
+
+echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+?>
+
diff --git a/etax/api/invoices_data.json b/etax/api/invoices_data.json
new file mode 100644
index 0000000..a17f63c
--- /dev/null
+++ b/etax/api/invoices_data.json
@@ -0,0 +1,318 @@
+{
+ "invoices": [
+ {
+ "id": "inv_1764734934",
+ "issueKey": "MGT202512031308547398",
+ "mgtKey": "MGT202512031308547398",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "843-22-01859",
+ "recipientName": "조은지게차",
+ "supplyDate": "2025-11-06",
+ "items": [
+ {
+ "name": "시멘트 50kg",
+ "qty": 49,
+ "unitPrice": 37827,
+ "vatType": "zero",
+ "supplyAmt": 1853523,
+ "vat": 0,
+ "total": 1853523
+ },
+ {
+ "name": "접착제",
+ "qty": 32,
+ "unitPrice": 377796,
+ "vatType": "exempt",
+ "supplyAmt": 12089472,
+ "vat": 0,
+ "total": 12089472
+ }
+ ],
+ "totalSupplyAmt": 13942995,
+ "totalVat": 0,
+ "total": 13942995,
+ "status": "issued",
+ "memo": "긴급 납품",
+ "createdAt": "2025-12-03T13:08:54",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1764735037",
+ "issueKey": "MGT202512031310379183",
+ "mgtKey": "MGT202512031310379183",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "311-46-00378",
+ "recipientName": "김인태",
+ "supplyDate": "2025-11-23",
+ "items": [
+ {
+ "name": "타일 30x30",
+ "qty": 19,
+ "unitPrice": 152719,
+ "vatType": "exempt",
+ "supplyAmt": 2901661,
+ "vat": 0,
+ "total": 2901661
+ },
+ {
+ "name": "욕조",
+ "qty": 48,
+ "unitPrice": 202442,
+ "vatType": "exempt",
+ "supplyAmt": 9717216,
+ "vat": 0,
+ "total": 9717216
+ }
+ ],
+ "totalSupplyAmt": 12618877,
+ "totalVat": 0,
+ "total": 12618877,
+ "status": "issued",
+ "memo": "샘플 납품",
+ "createdAt": "2025-12-03T13:10:37",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1765062685",
+ "issueKey": "MGT202512070811254622",
+ "mgtKey": "MGT202512070811254622",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "406-05-25709",
+ "recipientName": "스카이익스프레스",
+ "supplyDate": "2025-11-09",
+ "items": [
+ {
+ "name": "배관자재",
+ "qty": 82,
+ "unitPrice": 283655,
+ "vatType": "vat",
+ "supplyAmt": 23259710,
+ "vat": 2325971,
+ "total": 25585681
+ },
+ {
+ "name": "목재 합판",
+ "qty": 97,
+ "unitPrice": 196766,
+ "vatType": "vat",
+ "supplyAmt": 19086302,
+ "vat": 1908630,
+ "total": 20994932
+ },
+ {
+ "name": "유리 5mm",
+ "qty": 51,
+ "unitPrice": 240496,
+ "vatType": "vat",
+ "supplyAmt": 12265296,
+ "vat": 1226529,
+ "total": 13491825
+ }
+ ],
+ "totalSupplyAmt": 54611308,
+ "totalVat": 5461130,
+ "total": 60072438,
+ "status": "issued",
+ "memo": "계약 납품",
+ "createdAt": "2025-12-07T08:11:25",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1765114676",
+ "issueKey": "MGT202512072237565827",
+ "mgtKey": "MGT202512072237565827",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "107-81-78114",
+ "recipientName": "(주)이상네트웍스",
+ "supplyDate": "2025-11-14",
+ "items": [
+ {
+ "name": "배관자재",
+ "qty": 6,
+ "unitPrice": 238878,
+ "vatType": "vat",
+ "supplyAmt": 1433268,
+ "vat": 143326,
+ "total": 1576594
+ },
+ {
+ "name": "접착제",
+ "qty": 79,
+ "unitPrice": 313735,
+ "vatType": "vat",
+ "supplyAmt": 24785065,
+ "vat": 2478506,
+ "total": 27263571
+ },
+ {
+ "name": "변기",
+ "qty": 80,
+ "unitPrice": 441883,
+ "vatType": "vat",
+ "supplyAmt": 35350640,
+ "vat": 3535064,
+ "total": 38885704
+ }
+ ],
+ "totalSupplyAmt": 61568973,
+ "totalVat": 6156896,
+ "total": 67725869,
+ "status": "issued",
+ "memo": "보수 납품",
+ "createdAt": "2025-12-07T22:37:56",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1765114749",
+ "issueKey": "MGT202512072239096510",
+ "mgtKey": "MGT202512072239096510",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "843-22-01859",
+ "recipientName": "조은지게차",
+ "supplyDate": "2025-11-24",
+ "items": [
+ {
+ "name": "유리 5mm",
+ "qty": 91,
+ "unitPrice": 409294,
+ "vatType": "vat",
+ "supplyAmt": 37245754,
+ "vat": 3724575,
+ "total": 40970329
+ },
+ {
+ "name": "접착제",
+ "qty": 39,
+ "unitPrice": 320606,
+ "vatType": "vat",
+ "supplyAmt": 12503634,
+ "vat": 1250363,
+ "total": 13753997
+ },
+ {
+ "name": "변기",
+ "qty": 33,
+ "unitPrice": 140978,
+ "vatType": "vat",
+ "supplyAmt": 4652274,
+ "vat": 465227,
+ "total": 5117501
+ }
+ ],
+ "totalSupplyAmt": 54401662,
+ "totalVat": 5440165,
+ "total": 59841827,
+ "status": "issued",
+ "memo": "정기 납품",
+ "createdAt": "2025-12-07T22:39:09",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1765120032",
+ "issueKey": "MGT202512080007129289",
+ "mgtKey": "MGT202512080007129289",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "107-81-78114",
+ "recipientName": "(주)이상네트웍스",
+ "supplyDate": "2025-11-13",
+ "items": [
+ {
+ "name": "유리 5mm",
+ "qty": 90,
+ "unitPrice": 122746,
+ "vatType": "vat",
+ "supplyAmt": 11047140,
+ "vat": 1104714,
+ "total": 12151854
+ }
+ ],
+ "totalSupplyAmt": 11047140,
+ "totalVat": 1104714,
+ "total": 12151854,
+ "status": "issued",
+ "memo": "보수 납품",
+ "createdAt": "2025-12-08T00:07:12",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1768349618",
+ "issueKey": "MGT202601140913384780",
+ "mgtKey": "MGT202601140913384780",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "107-81-78114",
+ "recipientName": "(주)이상네트웍스",
+ "supplyDate": "2026-01-10",
+ "items": [
+ {
+ "name": "페인트 18L",
+ "qty": 79,
+ "unitPrice": 90747,
+ "vatType": "vat",
+ "supplyAmt": 7169013,
+ "vat": 716901,
+ "total": 7885914
+ },
+ {
+ "name": "접착제",
+ "qty": 67,
+ "unitPrice": 162463,
+ "vatType": "vat",
+ "supplyAmt": 10885021,
+ "vat": 1088502,
+ "total": 11973523
+ },
+ {
+ "name": "조명기구",
+ "qty": 25,
+ "unitPrice": 408336,
+ "vatType": "vat",
+ "supplyAmt": 10208400,
+ "vat": 1020840,
+ "total": 11229240
+ }
+ ],
+ "totalSupplyAmt": 28262434,
+ "totalVat": 2826243,
+ "total": 31088677,
+ "status": "issued",
+ "memo": "교체 납품",
+ "createdAt": "2026-01-14T09:13:38",
+ "barobillInvoiceId": "1"
+ },
+ {
+ "id": "inv_1768349638",
+ "issueKey": "MGT202601140913583219",
+ "mgtKey": "MGT202601140913583219",
+ "supplierBizno": "664-86-03713",
+ "supplierName": "(주)코드브릿지엑스",
+ "recipientBizno": "311-46-00378",
+ "recipientName": "김인태",
+ "supplyDate": "2026-01-14",
+ "items": [
+ {
+ "name": "변기",
+ "qty": 68,
+ "unitPrice": 136303,
+ "vatType": "vat",
+ "supplyAmt": 9268604,
+ "vat": 926860,
+ "total": 10195464
+ }
+ ],
+ "totalSupplyAmt": 9268604,
+ "totalVat": 926860,
+ "total": 10195464,
+ "status": "issued",
+ "memo": "A\/S 납품",
+ "createdAt": "2026-01-14T09:13:58",
+ "barobillInvoiceId": "1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/etax/api/issue.php b/etax/api/issue.php
new file mode 100644
index 0000000..e576dc7
--- /dev/null
+++ b/etax/api/issue.php
@@ -0,0 +1,226 @@
+ false,
+ "error" => "Invalid request data"
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+// 바로빌 API 호출 또는 시뮬레이션
+// 테스트 모드: SOAP 클라이언트만 있으면 실제 API 호출 시도 (CERTKEY는 선택사항)
+// 운영 모드: SOAP 클라이언트와 CERTKEY 모두 필요
+$useRealAPI = !empty($barobillSoapClient) && ($isTestMode || !empty($barobillCertKey));
+
+// 디버깅 정보: 실제 API 호출 여부 확인
+$debugInfo = [
+ 'hasSoapClient' => !empty($barobillSoapClient),
+ 'hasCertKey' => !empty($barobillCertKey),
+ 'hasCorpNum' => !empty($barobillCorpNum),
+ 'isTestMode' => $isTestMode ?? false,
+ 'soapUrl' => $barobillSoapUrl ?? null,
+ 'willUseRealAPI' => $useRealAPI
+];
+
+if ($useRealAPI) {
+ // 실제 바로빌 SOAP API 호출
+ $apiResult = issueTaxInvoice($input);
+
+ if ($apiResult['success']) {
+ $apiData = $apiResult['data'];
+
+ // SOAP 응답에서 MgtKey 추출 (RegistAndIssueTaxInvoice는 MgtKey를 반환)
+ // 응답이 숫자(양수)면 성공, 그 값이 바로빌 세금계산서 ID일 수 있음
+ // 또는 객체일 경우 MgtKey 속성 확인
+ $mgtKey = '';
+ if (is_object($apiData) && isset($apiData->MgtKey)) {
+ $mgtKey = $apiData->MgtKey;
+ } elseif (is_array($apiData) && isset($apiData['MgtKey'])) {
+ $mgtKey = $apiData['MgtKey'];
+ } else {
+ // MgtKey가 없으면 입력에서 생성한 키 사용
+ $mgtKey = $input['issueKey'] ?? 'MGT' . date('YmdHis') . rand(1000, 9999);
+ }
+
+ // 새 세금계산서 데이터 생성
+ $newInvoice = [
+ "id" => "inv_" . time(),
+ "issueKey" => $mgtKey, // MgtKey를 issueKey로 사용
+ "mgtKey" => $mgtKey, // 바로빌 관리번호
+ "supplierBizno" => $input['supplierBizno'] ?? '',
+ "supplierName" => $input['supplierName'] ?? '',
+ "recipientBizno" => $input['recipientBizno'] ?? '',
+ "recipientName" => $input['recipientName'] ?? '',
+ "supplyDate" => $input['supplyDate'] ?? date('Y-m-d'),
+ "items" => $input['items'] ?? [],
+ "totalSupplyAmt" => $input['totalSupplyAmt'] ?? 0,
+ "totalVat" => $input['totalVat'] ?? 0,
+ "total" => $input['total'] ?? 0,
+ "status" => "issued",
+ "memo" => $input['memo'] ?? '',
+ "createdAt" => date('Y-m-d\TH:i:s'),
+ "barobillInvoiceId" => is_numeric($apiData) ? (string)$apiData : ($apiData->InvoiceID ?? '')
+ ];
+
+ // 파일에 저장
+ $dataFile = __DIR__ . '/invoices_data.json';
+ $existingData = [];
+ if (file_exists($dataFile)) {
+ $fileContent = file_get_contents($dataFile);
+ if ($fileContent !== false) {
+ $existingData = json_decode($fileContent, true);
+ if (!is_array($existingData)) {
+ $existingData = [];
+ }
+ }
+ }
+
+ if (!isset($existingData['invoices'])) {
+ $existingData['invoices'] = [];
+ }
+
+ $existingData['invoices'][] = $newInvoice;
+
+ // 최대 100개까지만 저장
+ if (count($existingData['invoices']) > 100) {
+ $existingData['invoices'] = array_slice($existingData['invoices'], -100);
+ }
+
+ // 파일 저장 시도 및 에러 확인
+ $jsonData = json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ $saveResult = @file_put_contents($dataFile, $jsonData);
+ $saveSuccess = ($saveResult !== false);
+
+ if (!$saveSuccess) {
+ $errorMsg = "파일 저장 실패: " . $dataFile;
+ if (!is_writable(dirname($dataFile))) {
+ $errorMsg .= " (디렉토리 쓰기 권한 없음)";
+ } elseif (!is_writable($dataFile) && file_exists($dataFile)) {
+ $errorMsg .= " (파일 쓰기 권한 없음)";
+ }
+ error_log("세금계산서 저장 실패: " . $errorMsg);
+ }
+
+ $response = [
+ "success" => true,
+ "message" => "세금계산서가 성공적으로 발행되었습니다.",
+ "data" => [
+ "issueKey" => $newInvoice['issueKey'],
+ "mgtKey" => $newInvoice['mgtKey'],
+ "barobillInvoiceId" => $newInvoice['barobillInvoiceId'],
+ "status" => "issued",
+ "issuedAt" => $newInvoice['createdAt']
+ ],
+ "invoice" => $newInvoice,
+ "api_response" => $apiData,
+ "soap_request" => $apiResult['soap_request'] ?? null,
+ "soap_response" => $apiResult['soap_response'] ?? null,
+ "saved" => $saveSuccess,
+ "dataFile" => $saveSuccess ? null : $dataFile,
+ "simulation" => false,
+ "debug" => $debugInfo,
+ "note" => "✅ 실제 바로빌 API를 호출했습니다."
+ ];
+ } else {
+ $response = [
+ "success" => false,
+ "error" => $apiResult['error'] ?? "API 호출 실패",
+ "error_code" => $apiResult['error_code'] ?? null,
+ "error_detail" => $apiResult['error_detail'] ?? null,
+ "soap_request" => $apiResult['soap_request'] ?? null,
+ "soap_response" => $apiResult['soap_response'] ?? null
+ ];
+ }
+} else {
+ // 시뮬레이션 모드 (API 키가 없을 때)
+ $issueKey = "BARO-" . date('Y') . "-" . str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT);
+
+ $newInvoice = [
+ "id" => "inv_" . time(),
+ "issueKey" => $issueKey,
+ "supplierBizno" => $input['supplierBizno'] ?? '',
+ "supplierName" => $input['supplierName'] ?? '',
+ "recipientBizno" => $input['recipientBizno'] ?? '',
+ "recipientName" => $input['recipientName'] ?? '',
+ "supplyDate" => $input['supplyDate'] ?? date('Y-m-d'),
+ "items" => $input['items'] ?? [],
+ "totalSupplyAmt" => $input['totalSupplyAmt'] ?? 0,
+ "totalVat" => $input['totalVat'] ?? 0,
+ "total" => $input['total'] ?? 0,
+ "status" => "issued",
+ "memo" => $input['memo'] ?? '',
+ "createdAt" => date('Y-m-d\TH:i:s'),
+ "barobillInvoiceId" => "BB-" . uniqid()
+ ];
+
+ // 파일에 저장
+ $dataFile = __DIR__ . '/invoices_data.json';
+ $existingData = [];
+ if (file_exists($dataFile)) {
+ $fileContent = file_get_contents($dataFile);
+ if ($fileContent !== false) {
+ $existingData = json_decode($fileContent, true);
+ if (!is_array($existingData)) {
+ $existingData = [];
+ }
+ }
+ }
+
+ if (!isset($existingData['invoices'])) {
+ $existingData['invoices'] = [];
+ }
+
+ $existingData['invoices'][] = $newInvoice;
+
+ if (count($existingData['invoices']) > 100) {
+ $existingData['invoices'] = array_slice($existingData['invoices'], -100);
+ }
+
+ // 파일 저장 시도 및 에러 확인
+ $jsonData = json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ $saveResult = @file_put_contents($dataFile, $jsonData);
+ $saveSuccess = ($saveResult !== false);
+
+ if (!$saveSuccess) {
+ $errorMsg = "파일 저장 실패: " . $dataFile;
+ if (!is_writable(dirname($dataFile))) {
+ $errorMsg .= " (디렉토리 쓰기 권한 없음)";
+ } elseif (!is_writable($dataFile) && file_exists($dataFile)) {
+ $errorMsg .= " (파일 쓰기 권한 없음)";
+ }
+ error_log("세금계산서 저장 실패: " . $errorMsg);
+ }
+
+ $response = [
+ "success" => true,
+ "message" => "세금계산서가 성공적으로 발행되었습니다. (시뮬레이션 모드)",
+ "data" => [
+ "issueKey" => $issueKey,
+ "barobillInvoiceId" => $newInvoice['barobillInvoiceId'],
+ "status" => "issued",
+ "issuedAt" => $newInvoice['createdAt']
+ ],
+ "invoice" => $newInvoice,
+ "simulation" => true,
+ "saved" => $saveSuccess,
+ "dataFile" => $saveSuccess ? null : $dataFile,
+ "debug" => $debugInfo,
+ "warning" => "⚠️ 시뮬레이션 모드입니다. 실제 바로빌 API를 호출하려면 CERTKEY를 설정하세요."
+ ];
+
+ usleep(500000); // 0.5초 지연 시뮬레이션
+}
+
+echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+?>
+
diff --git a/etax/api/status.php b/etax/api/status.php
new file mode 100644
index 0000000..cd8e935
--- /dev/null
+++ b/etax/api/status.php
@@ -0,0 +1,168 @@
+ false,
+ "error" => "Invoice ID is required"
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+$invoiceId = $input['invoiceId'];
+// SOAP 클라이언트가 초기화되었고 CERTKEY가 있으면 실제 API 호출
+$useRealAPI = !empty($barobillSoapClient) && !empty($barobillCertKey);
+
+// 파일에서 데이터 읽기
+$dataFile = __DIR__ . '/invoices_data.json';
+$existingData = [];
+if (file_exists($dataFile)) {
+ $existingData = json_decode(file_get_contents($dataFile), true);
+ if (!is_array($existingData)) {
+ $existingData = [];
+ }
+}
+
+// 기본 데이터에서도 찾기
+$defaultInvoices = [
+ [
+ "id" => "inv_001",
+ "issueKey" => "BARO-2024-001",
+ "status" => "sent"
+ ],
+ [
+ "id" => "inv_002",
+ "issueKey" => "BARO-2024-002",
+ "status" => "issued"
+ ],
+ [
+ "id" => "inv_003",
+ "issueKey" => "BARO-2024-003",
+ "status" => "sent"
+ ]
+];
+
+$allInvoices = array_merge($defaultInvoices, $existingData['invoices'] ?? []);
+
+// 해당 세금계산서 찾기
+$invoice = null;
+foreach ($allInvoices as $inv) {
+ if ($inv['id'] === $invoiceId) {
+ $invoice = $inv;
+ break;
+ }
+}
+
+if (!$invoice) {
+ http_response_code(404);
+ echo json_encode([
+ "success" => false,
+ "error" => "Invoice not found"
+ ], JSON_UNESCAPED_UNICODE);
+ exit;
+}
+
+if ($useRealAPI && isset($invoice['issueKey'])) {
+ // 실제 바로빌 SOAP API 호출 - 국세청 전송
+ // issueKey를 MgtKey로 사용
+ $mgtKey = $invoice['mgtKey'] ?? $invoice['issueKey'];
+ $apiResult = sendToNTS($mgtKey);
+
+ if ($apiResult['success']) {
+ $apiData = $apiResult['data'];
+
+ // SOAP 응답에서 NTS 접수번호 추출
+ $ntsReceiptNo = '';
+ if (is_object($apiData) && isset($apiData->NTSConfirmNum)) {
+ $ntsReceiptNo = $apiData->NTSConfirmNum;
+ } elseif (is_array($apiData) && isset($apiData['NTSConfirmNum'])) {
+ $ntsReceiptNo = $apiData['NTSConfirmNum'];
+ }
+
+ // 상태 업데이트
+ $invoice['status'] = 'sent';
+ $invoice['ntsReceiptNo'] = $ntsReceiptNo;
+ $invoice['sentAt'] = date('Y-m-d\TH:i:s');
+
+ // 파일에 저장된 데이터 업데이트
+ if (isset($existingData['invoices'])) {
+ foreach ($existingData['invoices'] as &$inv) {
+ if ($inv['id'] === $invoiceId) {
+ $inv = $invoice;
+ break;
+ }
+ }
+ file_put_contents($dataFile, json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+ }
+
+ $response = [
+ "success" => true,
+ "message" => "국세청 전송이 완료되었습니다.",
+ "data" => [
+ "issueKey" => $invoice['issueKey'] ?? '',
+ "mgtKey" => $mgtKey,
+ "ntsReceiptNo" => $invoice['ntsReceiptNo'],
+ "status" => "sent",
+ "sentAt" => $invoice['sentAt']
+ ],
+ "invoice" => $invoice,
+ "api_response" => $apiData,
+ "soap_request" => $apiResult['soap_request'] ?? null,
+ "soap_response" => $apiResult['soap_response'] ?? null
+ ];
+ } else {
+ $response = [
+ "success" => false,
+ "error" => $apiResult['error'] ?? "API 호출 실패",
+ "error_code" => $apiResult['error_code'] ?? null,
+ "error_detail" => $apiResult['error_detail'] ?? null,
+ "soap_request" => $apiResult['soap_request'] ?? null,
+ "soap_response" => $apiResult['soap_response'] ?? null
+ ];
+ }
+} else {
+ // 시뮬레이션 모드
+ $ntsReceiptNo = "NTS-" . date('Y') . "-" . str_pad(rand(100000, 999999), 6, '0', STR_PAD_LEFT);
+
+ $invoice['status'] = 'sent';
+ $invoice['ntsReceiptNo'] = $ntsReceiptNo;
+ $invoice['sentAt'] = date('Y-m-d\TH:i:s');
+
+ // 파일에 저장된 데이터 업데이트
+ if (isset($existingData['invoices'])) {
+ foreach ($existingData['invoices'] as &$inv) {
+ if ($inv['id'] === $invoiceId) {
+ $inv = $invoice;
+ break;
+ }
+ }
+ file_put_contents($dataFile, json_encode($existingData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+ }
+
+ usleep(800000); // 0.8초 지연 시뮬레이션
+
+ $response = [
+ "success" => true,
+ "message" => "국세청 전송이 완료되었습니다. (시뮬레이션 모드)",
+ "data" => [
+ "issueKey" => $invoice['issueKey'] ?? '',
+ "ntsReceiptNo" => $ntsReceiptNo,
+ "status" => "sent",
+ "sentAt" => $invoice['sentAt']
+ ],
+ "invoice" => $invoice,
+ "simulation" => true
+ ];
+}
+
+echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+?>
+
diff --git a/etax/barobill_api_info.php b/etax/barobill_api_info.php
new file mode 100644
index 0000000..762f2ad
--- /dev/null
+++ b/etax/barobill_api_info.php
@@ -0,0 +1,433 @@
+load();
+require_once(getenv('DOCUMENT_ROOT') . "/session.php");
+?>
+
+
+
+
+
+
전자세금계산서 솔루션 개발을 위한 바로빌 API 가이드
+바로빌은 기업의 재무회계 디지털 전환을 지원하는 Fin-tech 플랫폼입니다.
+다양한 전자문서 발행 및 관리 API를 제공하여 기업의 업무 자동화를 지원합니다.
+개발자센터 URL: https://dev.barobill.co.kr
+전자세금계산서, 거래명세서, 청구서, 견적서 등 다양한 전자문서의 발행 및 관리 기능을 제공합니다.
+SMS, LMS, MMS 등 다양한 형태의 문자 전송 기능을 제공합니다.
+HWP, DOC, XLS, PDF 등 다양한 문서 파일의 팩스 전송 기능을 제공합니다.
+ +⚠️ 중요: 전자세금계산서 발행을 위해서는 공동인증서 또는 금융인증서가 필요합니다.
+| 기능 | +설명 | +필수 정보 | +
|---|---|---|
| 세금계산서 발행 | +전자세금계산서 발행 및 국세청 전송 | +공급자/수취자 정보, 품목 정보, 금액 정보 | +
| 세금계산서 취소 | +발행된 세금계산서 취소 | +발행 키, 취소 사유 | +
| 세금계산서 조회 | +발행 내역 및 상태 조회 | +발행 키 또는 검색 조건 | +
| 국세청 전송 | +발행된 세금계산서를 국세청으로 전송 | +발행 키 | +
| 전송 상태 확인 | +국세청 전송 상태 및 접수번호 확인 | +발행 키 | +
Headers:
+Request Body (예상):
+바로빌은 파트너 유형에 따라 할인형과 수익형으로 구분하여 연동 서비스를 제공합니다.
+자세한 요금 안내는 바로빌 개발자센터의 파트너/요금 페이지를 참고하시기 바랍니다.
+바로빌 API는 다음과 같은 개발 언어를 지원합니다:
+⚠️ 보안 주의사항:
+