바로빌 전자세금계산서 솔루션 연결
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
1
apikey/barobill_api_key.txt
Normal file
1
apikey/barobill_api_key.txt
Normal file
@@ -0,0 +1 @@
|
||||
2DD6C76C-04DB-44F7-B6E9-3FC0B2211826
|
||||
1
apikey/barobill_api_key.txt.example
Normal file
1
apikey/barobill_api_key.txt.example
Normal file
@@ -0,0 +1 @@
|
||||
2DD6C76C-04DB-44F7-B6E9-3FC0B2211826
|
||||
39
apikey/barobill_cert_id.txt
Normal file
39
apikey/barobill_cert_id.txt
Normal file
@@ -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에서는 가상의 인증서를 사용할 수 있습니다
|
||||
3
apikey/barobill_cert_id.txt.example
Normal file
3
apikey/barobill_cert_id.txt.example
Normal file
@@ -0,0 +1,3 @@
|
||||
여기에 바로빌 인증서 ID를 입력하세요 (선택사항)
|
||||
예: cert-id-here
|
||||
|
||||
37
apikey/barobill_cert_key.txt
Normal file
37
apikey/barobill_cert_key.txt
Normal file
@@ -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가 필요할 수 있습니다
|
||||
|
||||
1
apikey/barobill_corp_num.txt
Normal file
1
apikey/barobill_corp_num.txt
Normal file
@@ -0,0 +1 @@
|
||||
6648603713
|
||||
1
apikey/barobill_test_mode.txt
Normal file
1
apikey/barobill_test_mode.txt
Normal file
@@ -0,0 +1 @@
|
||||
true
|
||||
1
apikey/barobill_test_mode.txt.example
Normal file
1
apikey/barobill_test_mode.txt.example
Normal file
@@ -0,0 +1 @@
|
||||
true
|
||||
1
apikey/barobill_user_id.txt
Normal file
1
apikey/barobill_user_id.txt
Normal file
@@ -0,0 +1 @@
|
||||
cbx0913
|
||||
1
apikey/claude_api.txt
Normal file
1
apikey/claude_api.txt
Normal file
@@ -0,0 +1 @@
|
||||
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
|
||||
2
apikey/gcs_config.txt
Normal file
2
apikey/gcs_config.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
bucket_name=codebridge-speech-audio-files
|
||||
|
||||
1
apikey/gemini_api_key.txt
Normal file
1
apikey/gemini_api_key.txt
Normal file
@@ -0,0 +1 @@
|
||||
AIzaSyAS3bAzmXlhhZHgO3buFiTGzavXZ6ubYq8
|
||||
1
apikey/google_api.txt
Normal file
1
apikey/google_api.txt
Normal file
@@ -0,0 +1 @@
|
||||
f7d58533aa1dba0db19d799d85f22686684521d2
|
||||
13
apikey/google_service_account.json
Normal file
13
apikey/google_service_account.json
Normal file
@@ -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"
|
||||
}
|
||||
1
apikey/google_vertex_api.txt
Normal file
1
apikey/google_vertex_api.txt
Normal file
@@ -0,0 +1 @@
|
||||
AIzaSyAS3bAzmXlhhZHgO3buFiTGzavXZ6ubYq8
|
||||
1
apikey/notion.txt
Normal file
1
apikey/notion.txt
Normal file
@@ -0,0 +1 @@
|
||||
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
|
||||
1
apikey/opendart.txt
Normal file
1
apikey/opendart.txt
Normal file
@@ -0,0 +1 @@
|
||||
ad5002bf2c7d2f93eac3c6f9ff1d4b63bf3027bb
|
||||
@@ -60,7 +60,7 @@
|
||||
<a href="../barobill/index.php" className="hover:text-blue-600 flex items-center gap-1">
|
||||
<i data-lucide="layout-dashboard" className="w-4 h-4"></i> 현황
|
||||
</a>
|
||||
<a href="../../5130/etax/index.php" className="hover:text-blue-600 flex items-center gap-1">
|
||||
<a href="../etax/index.php" className="hover:text-blue-600 flex items-center gap-1">
|
||||
<i data-lucide="file-text" className="w-4 h-4"></i> 세금계산서
|
||||
</a>
|
||||
<a href="../index.php" className="hover:text-blue-600 flex items-center gap-1">
|
||||
|
||||
132
etax/README_DB.md
Normal file
132
etax/README_DB.md
Normal file
@@ -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`)에서 데이터를 마이그레이션하려면 별도의 마이그레이션 스크립트를 작성해야 합니다.
|
||||
|
||||
73
etax/api/API_URL_GUIDE.md
Normal file
73
etax/api/API_URL_GUIDE.md
Normal file
@@ -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
|
||||
|
||||
147
etax/api/README.md
Normal file
147
etax/api/README.md
Normal file
@@ -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가 선택사항일 수 있음
|
||||
|
||||
### 사업자번호 오류
|
||||
|
||||
- 하이픈(-) 없이 숫자만 입력했는지 확인
|
||||
- 발행자 사업자번호가 올바른지 확인
|
||||
418
etax/api/barobill_config.php
Normal file
418
etax/api/barobill_config.php
Normal file
@@ -0,0 +1,418 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
34
etax/api/debug_test.php
Normal file
34
etax/api/debug_test.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: text/plain');
|
||||
|
||||
echo "Current Dir: " . __DIR__ . "\n";
|
||||
echo "DotEnv Path: " . __DIR__ . '/../../lib/DotEnv.php' . "\n";
|
||||
echo "Env File Path: " . __DIR__ . '/../../.env' . "\n";
|
||||
|
||||
if (file_exists(__DIR__ . '/../../lib/DotEnv.php')) {
|
||||
echo "DotEnv file exists.\n";
|
||||
require_once __DIR__ . '/../../lib/DotEnv.php';
|
||||
echo "DotEnv loaded.\n";
|
||||
} else {
|
||||
echo "DotEnv file NOT found.\n";
|
||||
}
|
||||
|
||||
if (file_exists(__DIR__ . '/../../.env')) {
|
||||
echo ".env file exists.\n";
|
||||
try {
|
||||
(new DotEnv(__DIR__ . '/../../.env'))->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";
|
||||
163
etax/api/delete.php
Normal file
163
etax/api/delete.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method !== 'POST' && $method !== 'DELETE') {
|
||||
http_response_code(405);
|
||||
echo json_encode([
|
||||
"success" => 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);
|
||||
?>
|
||||
|
||||
5
etax/api/deleted_ids.json
Normal file
5
etax/api/deleted_ids.json
Normal file
@@ -0,0 +1,5 @@
|
||||
[
|
||||
"inv_001",
|
||||
"inv_002",
|
||||
"inv_003"
|
||||
]
|
||||
170
etax/api/invoices.php
Normal file
170
etax/api/invoices.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// Mock Data - 가상의 전자세금계산서 발행 내역
|
||||
$invoices = [
|
||||
[
|
||||
"id" => "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);
|
||||
?>
|
||||
|
||||
318
etax/api/invoices_data.json
Normal file
318
etax/api/invoices_data.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
226
etax/api/issue.php
Normal file
226
etax/api/issue.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// 바로빌 API 설정 로드
|
||||
require_once(__DIR__ . '/barobill_config.php');
|
||||
|
||||
// POST 데이터 읽기
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
"success" => 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);
|
||||
?>
|
||||
|
||||
168
etax/api/status.php
Normal file
168
etax/api/status.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
// 바로빌 API 설정 로드
|
||||
require_once(__DIR__ . '/barobill_config.php');
|
||||
|
||||
// POST 데이터 읽기
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input || !isset($input['invoiceId'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
"success" => 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);
|
||||
?>
|
||||
|
||||
433
etax/barobill_api_info.php
Normal file
433
etax/barobill_api_info.php
Normal file
@@ -0,0 +1,433 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../lib/DotEnv.php';
|
||||
(new DotEnv(__DIR__ . '/../.env'))->load();
|
||||
require_once(getenv('DOCUMENT_ROOT') . "/session.php");
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>바로빌 API 정보</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 3px solid #43cea2;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #185a9d;
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #666;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
color: #185a9d;
|
||||
font-size: 1.8em;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #43cea2;
|
||||
}
|
||||
|
||||
.section h3 {
|
||||
color: #43cea2;
|
||||
font-size: 1.4em;
|
||||
margin: 20px 0 15px 0;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #43cea2;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.info-box.warning {
|
||||
border-left-color: #f5576c;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.info-box.success {
|
||||
border-left-color: #43cea2;
|
||||
background: #f0fdf4;
|
||||
}
|
||||
|
||||
.api-endpoint {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.api-endpoint .method {
|
||||
color: #4ec9b0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.api-endpoint .url {
|
||||
color: #ce9178;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table th {
|
||||
background: #43cea2;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
table tr:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 30px;
|
||||
background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 25px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
margin: 10px 10px 10px 0;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 30px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #e83e8c;
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: 30px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 25px;
|
||||
border-radius: 50px;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1000;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.home-btn:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.home-btn {
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
padding: 10px 20px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Home Button -->
|
||||
<a href="../index.php" class="home-btn">
|
||||
<span>🏠</span>
|
||||
<span>홈으로</span>
|
||||
</a>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>📋 바로빌 API 정보</h1>
|
||||
<p>전자세금계산서 솔루션 개발을 위한 바로빌 API 가이드</p>
|
||||
</div>
|
||||
|
||||
<!-- 바로빌 개요 -->
|
||||
<div class="section">
|
||||
<h2>1. 바로빌 개요</h2>
|
||||
<div class="info-box success">
|
||||
<p><strong>바로빌</strong>은 기업의 재무회계 디지털 전환을 지원하는 Fin-tech 플랫폼입니다.</p>
|
||||
<p>다양한 전자문서 발행 및 관리 API를 제공하여 기업의 업무 자동화를 지원합니다.</p>
|
||||
</div>
|
||||
<p><strong>개발자센터 URL</strong>: <a href="https://dev.barobill.co.kr" target="_blank">https://dev.barobill.co.kr</a></p>
|
||||
</div>
|
||||
|
||||
<!-- 주요 API 서비스 -->
|
||||
<div class="section">
|
||||
<h2>2. 주요 API 서비스</h2>
|
||||
|
||||
<h3>2.1 전자문서 API</h3>
|
||||
<p>전자세금계산서, 거래명세서, 청구서, 견적서 등 다양한 전자문서의 발행 및 관리 기능을 제공합니다.</p>
|
||||
<ul>
|
||||
<li><strong>전자세금계산서 발행</strong>: 세금계산서 발행 및 국세청 전송</li>
|
||||
<li><strong>전자세금계산서 취소</strong>: 발행된 세금계산서 취소</li>
|
||||
<li><strong>전자세금계산서 조회</strong>: 발행 내역 및 상태 조회</li>
|
||||
<li><strong>거래명세서 발행</strong>: 거래명세서 발행 및 관리</li>
|
||||
<li><strong>청구서/견적서 발행</strong>: 다양한 전자문서 발행</li>
|
||||
</ul>
|
||||
<p><a href="https://dev.barobill.co.kr/services/edoc" target="_blank" class="btn">전자문서 API 상세보기</a></p>
|
||||
|
||||
<h3>2.2 메시징 API</h3>
|
||||
<p>SMS, LMS, MMS 등 다양한 형태의 문자 전송 기능을 제공합니다.</p>
|
||||
<ul>
|
||||
<li>세금계산서 발행 알림</li>
|
||||
<li>인증서 만료 알림</li>
|
||||
<li>시스템 알림</li>
|
||||
</ul>
|
||||
<p><a href="https://dev.barobill.co.kr/services/message" target="_blank" class="btn">메시징 API 상세보기</a></p>
|
||||
|
||||
<h3>2.3 팩스 전송 API</h3>
|
||||
<p>HWP, DOC, XLS, PDF 등 다양한 문서 파일의 팩스 전송 기능을 제공합니다.</p>
|
||||
<p><a href="https://dev.barobill.co.kr/services/fax" target="_blank" class="btn">팩스 API 상세보기</a></p>
|
||||
</div>
|
||||
|
||||
<!-- 전자세금계산서 API 상세 -->
|
||||
<div class="section">
|
||||
<h2>3. 전자세금계산서 API 상세</h2>
|
||||
|
||||
<h3>3.1 필수 요구사항</h3>
|
||||
<div class="info-box warning">
|
||||
<p><strong>⚠️ 중요:</strong> 전자세금계산서 발행을 위해서는 공동인증서 또는 금융인증서가 필요합니다.</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li>공동인증서 또는 금융인증서 등록</li>
|
||||
<li>바로빌 개발자센터 회원가입</li>
|
||||
<li>API 키 발급</li>
|
||||
<li>인증서 등록 및 연동</li>
|
||||
</ul>
|
||||
|
||||
<h3>3.2 주요 API 기능</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>기능</th>
|
||||
<th>설명</th>
|
||||
<th>필수 정보</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>세금계산서 발행</strong></td>
|
||||
<td>전자세금계산서 발행 및 국세청 전송</td>
|
||||
<td>공급자/수취자 정보, 품목 정보, 금액 정보</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>세금계산서 취소</strong></td>
|
||||
<td>발행된 세금계산서 취소</td>
|
||||
<td>발행 키, 취소 사유</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>세금계산서 조회</strong></td>
|
||||
<td>발행 내역 및 상태 조회</td>
|
||||
<td>발행 키 또는 검색 조건</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>국세청 전송</strong></td>
|
||||
<td>발행된 세금계산서를 국세청으로 전송</td>
|
||||
<td>발행 키</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>전송 상태 확인</strong></td>
|
||||
<td>국세청 전송 상태 및 접수번호 확인</td>
|
||||
<td>발행 키</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>3.3 API 요청 예시 (예상)</h3>
|
||||
<div class="api-endpoint">
|
||||
<span class="method">POST</span> <span class="url">/api/taxinvoice/issue</span>
|
||||
</div>
|
||||
<p><strong>Headers:</strong></p>
|
||||
<div class="api-endpoint">
|
||||
Authorization: Bearer {API_KEY}<br>
|
||||
Content-Type: application/json
|
||||
</div>
|
||||
<p><strong>Request Body (예상):</strong></p>
|
||||
<div class="api-endpoint">
|
||||
{<br>
|
||||
"certId": "인증서ID",<br>
|
||||
"supplier": {<br>
|
||||
"bizno": "사업자번호",<br>
|
||||
"corpName": "상호",<br>
|
||||
"ceo": "대표자명"<br>
|
||||
},<br>
|
||||
"recipient": { ... },<br>
|
||||
"items": [ ... ],<br>
|
||||
"writeDate": "2024-01-01",<br>
|
||||
"supplyDate": "2024-01-01"<br>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 개발자 지원 -->
|
||||
<div class="section">
|
||||
<h2>4. 개발자 지원</h2>
|
||||
<ul>
|
||||
<li><strong>개발 가이드</strong>: 상세한 API 문서 및 가이드 제공</li>
|
||||
<li><strong>샘플 코드</strong>: Java, Python, PHP, .NET 등 다양한 언어 지원</li>
|
||||
<li><strong>테스트 환경</strong>: 실제 개발 환경과 동일한 테스트 환경 제공</li>
|
||||
<li><strong>전담 엔지니어 지원</strong>: 개발 상담 및 기술 지원 제공</li>
|
||||
</ul>
|
||||
<p><a href="https://dev.barobill.co.kr" target="_blank" class="btn">개발자센터 바로가기</a></p>
|
||||
</div>
|
||||
|
||||
<!-- 연동 절차 -->
|
||||
<div class="section">
|
||||
<h2>5. API 연동 절차</h2>
|
||||
<ol>
|
||||
<li><strong>회원가입</strong>: 바로빌 개발자센터에서 회원가입</li>
|
||||
<li><strong>API 키 발급</strong>: 개발자센터에서 API 키 발급</li>
|
||||
<li><strong>인증서 등록</strong>: 공동인증서 또는 금융인증서 등록</li>
|
||||
<li><strong>테스트 환경 설정</strong>: 바로빌 테스트 환경에서 연동 테스트</li>
|
||||
<li><strong>개발 및 연동</strong>: API 가이드를 참고하여 연동 및 개발 진행</li>
|
||||
<li><strong>프로덕션 연동</strong>: 실제 운영 환경 구축 후 서비스 오픈</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- 요금 안내 -->
|
||||
<div class="section">
|
||||
<h2>6. 요금 안내</h2>
|
||||
<div class="info-box">
|
||||
<p>바로빌은 파트너 유형에 따라 할인형과 수익형으로 구분하여 연동 서비스를 제공합니다.</p>
|
||||
<p>자세한 요금 안내는 바로빌 개발자센터의 파트너/요금 페이지를 참고하시기 바랍니다.</p>
|
||||
</div>
|
||||
<p><a href="https://dev.barobill.co.kr/partners/cost/partner" target="_blank" class="btn btn-secondary">요금 안내 보기</a></p>
|
||||
</div>
|
||||
|
||||
<!-- 지원 언어 -->
|
||||
<div class="section">
|
||||
<h2>7. 지원 언어</h2>
|
||||
<p>바로빌 API는 다음과 같은 개발 언어를 지원합니다:</p>
|
||||
<ul>
|
||||
<li>Java</li>
|
||||
<li>Python</li>
|
||||
<li>PHP</li>
|
||||
<li>.NET (C#)</li>
|
||||
<li>기타 REST API를 지원하는 모든 언어</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 보안 고려사항 -->
|
||||
<div class="section">
|
||||
<h2>8. 보안 고려사항</h2>
|
||||
<div class="info-box warning">
|
||||
<p><strong>⚠️ 보안 주의사항:</strong></p>
|
||||
<ul>
|
||||
<li>API 키는 절대 공개되지 않도록 주의하세요.</li>
|
||||
<li>인증서는 암호화하여 안전하게 저장하세요.</li>
|
||||
<li>HTTPS를 통해서만 API를 호출하세요.</li>
|
||||
<li>민감한 정보는 로그에 기록하지 마세요.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 유용한 링크 -->
|
||||
<div class="section">
|
||||
<h2>9. 유용한 링크</h2>
|
||||
<ul>
|
||||
<li><a href="https://dev.barobill.co.kr" target="_blank">바로빌 개발자센터</a></li>
|
||||
<li><a href="https://dev.barobill.co.kr/services/edoc" target="_blank">전자문서 API</a></li>
|
||||
<li><a href="https://dev.barobill.co.kr/services/message" target="_blank">메시징 API</a></li>
|
||||
<li><a href="https://dev.barobill.co.kr/services/fax" target="_blank">팩스 API</a></li>
|
||||
<li><a href="https://dev.barobill.co.kr/partners/cost/partner" target="_blank">파트너/요금 안내</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 다음 단계 -->
|
||||
<div class="section">
|
||||
<h2>10. 다음 단계</h2>
|
||||
<ol>
|
||||
<li>바로빌 개발자센터 회원가입 및 API 키 발급</li>
|
||||
<li>개발 가이드 및 샘플 코드 다운로드</li>
|
||||
<li>테스트 환경에서 API 연동 테스트</li>
|
||||
<li>인증서 등록 및 연동</li>
|
||||
<li>실제 프로젝트에 통합</li>
|
||||
</ol>
|
||||
<p><a href="../strategy/electronicTaxInvoice_index.php" class="btn">전략 페이지로 돌아가기</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
27
etax/dev.md
Normal file
27
etax/dev.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Etax 개발 노트
|
||||
|
||||
## API 오류 해결 가이드
|
||||
|
||||
### 바로빌 SOAP 클라이언트 미설치 오류 (500 Error)
|
||||
|
||||
**문제 상황:**
|
||||
서버에 PHP SOAP 확장 모듈이 설치되어 있지 않은 경우(`Class 'SoapClient' not found`), `new SoapClient()` 호출 시 치명적인 오류(Fatal Error)가 발생하여 HTTP 500 상태 코드를 반환합니다.
|
||||
|
||||
**해결 방법:**
|
||||
`soapClient` 생성 로직을 `try-catch` 블록으로 감싸되, `Exception`이 아닌 **`Throwable`**을 catch해야 합니다. PHP 7+에서는 치명적인 오류가 `Error` 객체로 던져지며, 이는 `Exception`이 아닌 `Throwable` 인터페이스를 구현하기 때문입니다.
|
||||
|
||||
**수정 예시:**
|
||||
```php
|
||||
$barobillSoapClient = null;
|
||||
try {
|
||||
$barobillSoapClient = new SoapClient($url, $options);
|
||||
} catch (Throwable $e) {
|
||||
// Class not found 등의 Fatal Error도 여기서 잡힘
|
||||
error_log('SOAP Client 생성 실패: ' . $e->getMessage());
|
||||
// 이후 로직에서 $barobillSoapClient가 null일 경우의 대체 로직(예: 시뮬레이션 모드) 수행
|
||||
}
|
||||
```
|
||||
|
||||
**적용 파일:**
|
||||
- `etax/api/barobill_config.php`
|
||||
- `etax/api/issue.php` (전역 에러 핸들링)
|
||||
1086
etax/index.php
Normal file
1086
etax/index.php
Normal file
File diff suppressed because it is too large
Load Diff
45
lib/DotEnv.php
Normal file
45
lib/DotEnv.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('DotEnv')) {
|
||||
class DotEnv
|
||||
{
|
||||
/**
|
||||
* The directory where the .env file is located.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
public function __construct(string $path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
throw new \InvalidArgumentException(sprintf('%s does not exist', $path));
|
||||
}
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
if (!is_readable($this->path)) {
|
||||
throw new \RuntimeException(sprintf('%s file is not readable', $this->path));
|
||||
}
|
||||
|
||||
$lines = file($this->path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos(trim($line), '#') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($name, $value) = explode('=', $line, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value);
|
||||
|
||||
if (!array_key_exists($name, $_SERVER) && !array_key_exists($name, $_ENV)) {
|
||||
putenv(sprintf('%s=%s', $name, $value));
|
||||
$_ENV[$name] = $value;
|
||||
$_SERVER[$name] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
lib/func.php
Normal file
45
lib/func.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
session_start();
|
||||
?>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="./css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="./css/func.css">
|
||||
</head>
|
||||
|
||||
<?php
|
||||
function latest_article($table,$loop,$char_limit)
|
||||
{
|
||||
require_once("../lib/mydb.php");
|
||||
$pdo=db_connect();
|
||||
|
||||
try{
|
||||
$sql="select * from chandj.$table order by num desc limit $loop";
|
||||
$stmh=$pdo->query($sql);
|
||||
|
||||
While($row=$stmh->fetch(PDO::FETCH_ASSOC))
|
||||
{
|
||||
$num=$row["num"];
|
||||
$len_subject=strlen($row["subject"]);
|
||||
$subject=$row["subject"];
|
||||
if($len_subject>$char_limit)
|
||||
{
|
||||
$subject=mb_substr($row["subject"],0,$char_limit,'utf-8');
|
||||
$subject=$subject . "..."; // 글자수가 초과하면 ...으로 표기됨
|
||||
}
|
||||
$regist_day=substr($row["regist_day"],0,10);
|
||||
$page=1;
|
||||
|
||||
echo("<div class='col1'> <a href='./$table/view.php?num=$num&page=$page'>$subject</a>
|
||||
</div><div class='col2'>$regist_day</div>
|
||||
<div class='clear'></div>");
|
||||
|
||||
}
|
||||
} catch (PDOException $Exception) {
|
||||
print "오류: ". $Exception->getMessage();
|
||||
|
||||
}
|
||||
}
|
||||
?>
|
||||
21
lib/helper.php
Normal file
21
lib/helper.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
function href($url)
|
||||
{
|
||||
print "
|
||||
<script>
|
||||
location.href = '$url';
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
function alert($msg)
|
||||
{
|
||||
print "
|
||||
<script>
|
||||
alert('$msg');
|
||||
</script>
|
||||
";
|
||||
}
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user