# TORQ 거래명세서 PDF 서비스 — 구현 지침

> **작성일**: 2026-05-17
> **스택**: Node.js (TypeScript) + Puppeteer + Express
> **위치**: `node-service/packages/pdf-service/`
> **배포**: node-01 (blockchain-api와 동일 서버, 포트 3003)

---

## 1. 아키텍처

```
┌─────────────────┐    ┌──────────────┐    ┌──────────────────┐
│  Partner Console │───▶│  partner-api │───▶│   pdf-service    │
│  Admin Console   │    │  admin-api   │    │  (Node.js:3003)  │
└─────────────────┘    └──────────────┘    └────────┬─────────┘
                                                     │
                                           ┌─────────▼─────────┐
                                           │   MySQL (DB)       │
                                           │  torq_trades       │
                                           │  partners          │
                                           └───────────────────┘
```

### 호출 흐름

1. 프론트엔드: "명세서 다운로드" 버튼 클릭
2. Spring Boot (partner-api / admin-api): `GET /api/v1/torq/trades/{tradeId}/statement`
3. Spring Boot → pdf-service: `GET http://node-01:3003/api/pdf/torq-statement/{tradeId}`
4. pdf-service: DB에서 거래 + 파트너 조회 → HTML 렌더링 → Puppeteer PDF 변환 → 바이너리 반환
5. Spring Boot → 프론트: PDF 파일 스트리밍 (Content-Type: application/pdf)

---

## 2. pdf-service 패키지 구조

```
node-service/packages/pdf-service/
├── package.json
├── tsconfig.json
├── src/
│   ├── app.ts                  # Express 엔트리포인트 (포트 3003)
│   ├── routes/
│   │   └── pdf.ts              # GET /api/pdf/torq-statement/:tradeId
│   ├── services/
│   │   └── torqStatementService.ts  # DB 조회 + HTML 생성 + PDF 변환
│   └── templates/
│       └── torq-statement.ts   # HTML 템플릿 (ES template literal)
```

---

## 3. DB 조회 쿼리

```sql
SELECT
    t.id, t.escrow_id, t.status, t.krw_amount, t.usdt_amount,
    t.exchange_rate, t.torq_fee, t.buyer_name, t.buyer_phone,
    t.buyer_bank, t.seller_bank, t.seller_name,
    t.receive_address, t.tx_hash,
    t.created_at, t.accepted_at, t.transferred_at, t.completed_at,
    t.cancelled_at, t.cancel_reason,
    p.name AS partner_name, p.partner_code
FROM torq_trades t
JOIN partners p ON t.partner_id = p.id
WHERE t.id = ?
  AND t.partner_id = ?   -- 파트너 권한 검증 (admin은 이 조건 제거)
```

---

## 4. HTML 템플릿 설계

### 거래명세서 레이아웃

```
┌────────────────────────────────────────────┐
│              거 래 명 세 서                 │
│         TORQ KRW → USDT 온램프             │
│                                            │
│  발행일: 2026-05-17                        │
│  명세서 번호: TXS-2026-000123              │
├────────────────────────────────────────────┤
│  ■ 파트너 정보                              │
│  파트너명: ○○ 주식회사                      │
│  파트너 코드: PARTNER001                    │
├────────────────────────────────────────────┤
│  ■ 거래 정보                                │
│  거래 ID: 123                              │
│  에스크로 ID: 456                           │
│  상태: 완료 (COMPLETED)                     │
│                                            │
│  KRW 입금액:     ₩ 138,000                 │
│  적용 환율:      1,380.00 KRW/USDT         │
│  TORQ 수수료:    ₩ 1,380                   │
│  USDT 수령액:    99.00 USDT                │
├────────────────────────────────────────────┤
│  ■ 매수자 정보                              │
│  이름: 홍길동                               │
│  은행: 신한은행                              │
├────────────────────────────────────────────┤
│  ■ 판매자 정보                              │
│  은행: ○○은행                               │
│  예금주: ○○○                               │
├────────────────────────────────────────────┤
│  ■ USDT 수취                               │
│  수취 주소: TXyz...abc                      │
│  TX Hash: 0xabc...def                      │
├────────────────────────────────────────────┤
│  ■ 타임라인                                 │
│  거래 생성:  2026-05-17 10:00:00           │
│  입금 수락:  2026-05-17 10:01:30           │
│  이체 완료:  2026-05-17 10:05:00           │
│  거래 완료:  2026-05-17 10:07:00           │
├────────────────────────────────────────────┤
│  본 명세서는 Cryptoments 시스템에서         │
│  자동 생성되었습니다.                        │
└────────────────────────────────────────────┘
```

### CSS 요구사항

- A4 세로 (210mm × 297mm)
- 한글 폰트: Noto Sans KR (Google Fonts CDN 또는 로컬 임베드)
- 인쇄 최적화: `@media print` + `@page { margin: 15mm; }`
- 테이블 보더: 1px solid #ddd
- 헤더 색상: #08469E (Cryptoments 브랜드)

---

## 5. Puppeteer 설정

```typescript
const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage',    // Docker/서버 환경
    '--font-render-hinting=none', // 한글 폰트 힌팅 최적화
  ],
});

const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' }); // 폰트 로딩 대기

const pdfBuffer = await page.pdf({
  format: 'A4',
  printBackground: true,
  margin: { top: '15mm', bottom: '15mm', left: '15mm', right: '15mm' },
});
```

### 성능 최적화

- **Browser 재사용**: 매 요청마다 launch하지 않고 싱글턴 browser 인스턴스 유지
- **Page pool**: 동시 요청 시 page를 풀링 (최대 3개)
- **타임아웃**: PDF 생성 10초 제한

---

## 6. API 명세

### pdf-service 내부 API

```
GET /api/pdf/torq-statement/:tradeId?partnerId=123
```

| Param | 설명 |
|-------|------|
| tradeId | torq_trades.id |
| partnerId | (query) 파트너 권한 검증용. 없으면 admin 호출로 간주 |

**Response**: `application/pdf` 바이너리

### Spring Boot 프록시 API

**partner-api**:
```
GET /api/v1/torq/trades/{tradeId}/statement
Authorization: Bearer {partnerToken}
→ 내부: pdf-service 호출 + partnerId 주입
```

**admin-api**:
```
GET /api/v1/torq/trades/{tradeId}/statement
Authorization: Bearer {adminToken}
→ 내부: pdf-service 호출 (partnerId 미전달 = 전체 접근)
```

---

## 7. 배포

### ecosystem.config.cjs 추가

```javascript
{
  name: 'pdf-service',
  cwd: './packages/pdf-service',
  script: 'dist/app.js',
  instances: 1,
  exec_mode: 'fork',
  env: { NODE_ENV: 'production' },
  max_memory_restart: '512M',  // Puppeteer Chromium 메모리
  // ...기존 패턴 동일
}
```

### 서버 요구사항 (node-01)

```bash
# Puppeteer용 Chromium 의존성 (Ubuntu)
sudo apt-get install -y \
  libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \
  libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
  libxrandr2 libgbm1 libpango-1.0-0 libasound2

# 한글 폰트
sudo apt-get install -y fonts-noto-cjk
```

### .gitlab-ci.yml 변경

node:build와 node:deploy에 `pdf-service` 추가 필요.

---

## 8. 구현 순서

```
1. [Node.js] pdf-service 패키지 생성 (package.json, tsconfig.json, app.ts)
2. [Node.js] HTML 템플릿 작성 (torq-statement.ts)
3. [Node.js] 서비스 로직 (DB 조회 → HTML → PDF)
4. [Node.js] 라우트 + 에러 처리
5. [인프라] node-01에 Chromium 의존성 + 한글 폰트 설치
6. [Spring] partner-api / admin-api 프록시 엔드포인트
7. [Frontend] 파트너 콘솔 + 어드민 콘솔 다운로드 버튼
8. [배포] ecosystem.config.cjs + .gitlab-ci.yml 업데이트
```

---

## 9. 보안

- pdf-service는 내부 API (외부 노출 안 함) — node-01:3003은 방화벽에서 내부만 허용
- partnerId 검증은 Spring Boot에서 세션 기반으로 수행 후 전달
- admin 호출 시 partnerId 없이 조회 가능
- 민감 정보 마스킹: buyer_phone → `010-****-5678`, seller_account → `****1234`
