# 정적 지갑

> 특정 주문 또는 사용자에 연결되는 영구 입금 주소 — 정기 결제와 장기 결제에 적합합니다.

정적 지갑은 암호화폐 결제를 수신하기 위한 영구 주소입니다. 특정 `order_id`에 연결되며 `project_id + order_id + currency + network` 조합으로 고유하게 식별됩니다.

정적 지갑의 활용 사례:

- 동일 사용자로부터의 반복 입금
- 사용자 프로필에 표시되는 장기 결제 주소
- 사용자별 안정적인 주소를 제공해야 하는 대량 입금 흐름

## 정적 지갑 생성

`POST /v1/static-wallet`

### 요청 매개변수

| 필드 | 타입 | 필수 | 설명 |
|-------|------|----------|-------------|
| `currency` | string | yes | 암호화폐 (USDT, BTC, ETH 등) |
| `network` | string | yes | 네트워크 코드 |
| `order_id` | string | yes | 가맹점 측 주문/사용자 ID (최대 255자) |
| `label` | string | no | 지갑 라벨 (최대 255자) |
| `url_callback` | string | yes | webhook 알림용 URL |
| `invite_code` | string | no | 추천인 코드 |

### 요청 예시

```json
{
  "currency": "USDT",
  "network": "TRX-TRC20",
  "order_id": "USER-123",
  "label": "User deposit #123",
  "url_callback": "https://your-site.com/webhook/static"
}
```

### 응답 예시

```json
{
  "state": 0,
  "result": {
    "uuid": "019b2265-34d8-7001-a230-8f97de90d481",
    "address": "TXYZabc123...",
    "currency": "USDT",
    "network": "TRX-TRC20",
    "label": "User deposit #123",
    "order_id": "USER-123",
    "status": "active",
    "url": "https://go.2328.io/static/019b2265-34d8-7001-a230-8f97de90d481",
    "created_at": "2026-01-20T12:00:00Z",
    "qr": "data:image/png;base64,iVBORw0..."
  }
}
```

## 지갑 정보

`uuid` 또는 `address`로 정적 지갑 정보를 조회합니다.

`POST /v1/static-wallet/info`

### 요청 매개변수

| 필드 | 타입 | 필수 | 설명 |
|-------|------|----------|-------------|
| `uuid` | string | yes* | 정적 지갑 UUID |
| `address` | string | yes* | 블록체인 지갑 주소 |

> **INFO:** `uuid` 또는 `address` 중 최소 하나는 반드시 지정해야 합니다.

### 응답 예시

```json
{
  "state": 0,
  "result": {
    "uuid": "019b2265-34d8-7001-a230-8f97de90d481",
    "address": "TXYZabc123...",
    "currency": "USDT",
    "network": "TRX-TRC20",
    "status": "active",
    "total_received": "1250.50",
    "transactions_count": 3,
    "created_at": "2026-01-20T12:00:00Z",
    "qr": "data:image/png;base64,iVBORw0..."
  }
}
```

- `total_received` — 이 지갑이 수신한 모든 입금의 합계, `currency` 단위.
- `transactions_count` — 지금까지 수신한 입금 횟수.
- `qr` — 입금 주소의 base64 인코딩된 QR data URI (정적 지갑은 생성 시 주소가 할당되므로 항상 제공됩니다).

## 지갑 목록

`POST /v1/static-wallet/list`

### 요청 매개변수

| 필드 | 타입 | 필수 | 설명 |
|-------|------|----------|-------------|
| `status` | string | no | 상태로 필터링 (`active`, `inactive`) |
| `currency` | string | no | 통화로 필터링 |
| `network` | string | no | 네트워크로 필터링 |
| `order_id` | string | no | order_id로 필터링 |
| `page` | int | no | 페이지 번호 (기본값: 1) |
| `per_page` | int | no | 페이지당 항목 수 (기본값: 20, 최댓값: 100) |

### 응답 예시

```json
{
  "state": 0,
  "result": {
    "items": [
      {
        "uuid": "019b2265-...",
        "address": "TXYZabc123...",
        "currency": "USDT",
        "network": "TRX-TRC20",
        "status": "active",
        "total_received": "1250.50",
        "transactions_count": 3
      }
    ],
    "paginate": {
      "count": 1,
      "current_page": 1,
      "per_page": 20,
      "total": 1,
      "total_pages": 1,
      "has_more": false
    }
  }
}
```

## 지갑 활성화 / 비활성화

정적 지갑이 새로운 결제를 수락할지 여부를 전환합니다.

`POST /v1/static-wallet/disable`

`POST /v1/static-wallet/enable`

### 요청

두 endpoint 모두 단일 매개변수를 받습니다:

```json
{
  "uuid": "019b2265-34d8-7001-a230-8f97de90d481"
}
```

### 응답 예시

```json
{
  "state": 0,
  "result": {
    "uuid": "019b2265-34d8-7001-a230-8f97de90d481",
    "status": "inactive",
    "message": "Static wallet disabled successfully"
  }
}
```

`enable`의 경우 `status`는 `"active"`이며 `message`는 `"Static wallet enabled successfully"`로 표시됩니다.

## 지갑 거래 내역

정적 지갑이 수신한 모든 입금 목록을 조회합니다.

`POST /v1/static-wallet/transactions`

### 요청 매개변수

| 필드 | 타입 | 필수 | 설명 |
|-------|------|----------|-------------|
| `uuid` | string | yes | 정적 지갑 UUID |
| `date_from` | date | no | 시작 일자 (YYYY-MM-DD) |
| `date_to` | date | no | 종료 일자 (YYYY-MM-DD) |
| `page` | int | no | 페이지 번호 (기본값: 1) |
| `per_page` | int | no | 페이지당 항목 수 (기본값: 15, 최댓값: 5000) |

### 응답 예시

```json
{
  "state": 0,
  "result": {
    "items": [
      {
        "uuid": "abc123-def456-...",
        "order_id": "USER-123",
        "amount": "100.00",
        "currency": "USDT",
        "payment_status": "paid",
        "txid": "0xabc123def456...",
        "fee_amount": "3.00",
        "net_amount": "97.00",
        "created_at": "2026-01-20T15:30:00Z"
      }
    ],
    "paginate": {
      "count": 1,
      "hasPages": true,
      "perPage": 15,
      "page": 1
    }
  }
}
```

- `fee_amount` — 이 입금에서 차감된 플랫폼 수수료, `currency` 단위.
- `net_amount` — 수수료 차감 후 가맹점 잔액에 반영된 금액.

## 정적 지갑 webhook

정적 지갑에서 결제가 수신되면 시스템은 `url_callback`으로 webhook을 전송합니다.

> **WARNING:** 정적 지갑의 webhook 형식은 일반 결제 webhook과 다릅니다. 특히 정적 지갑 webhook에는 잔액 반영에 사용해야 하는 `merchant_amount` 필드가 포함됩니다.

### Webhook payload

```json
{
  "uuid": "a28b293f-5c76-4053-8062-ae9ca4ab784b",
  "order_id": "USER-7666308594",
  "amount": "10.00000000",
  "currency": "USDT",
  "amount_usd": "10.00000000",
  "exchange_rate": "1.00000000",
  "payer_currency": "USDT",
  "payer_amount": "10.00000000",
  "network": "TRX-TRC20",
  "address": "TMU9Tgpchvgbywkbj5SdC8KJS73t5m3M7G",
  "payment_status": "paid",
  "txid": "8369ede26a0da05b1bae154b4bb4072eb2453db30ba86b21831902670929454f",
  "tx_explorer_url": "https://tronscan.org/#/transaction/8369ede26a0da05b1bae154b4bb4072eb2453db30ba86b21831902670929454f",
  "payment_amount": "10.00000000",
  "merchant_amount": "9.920000000000000000",
  "created_at": "2026-05-09T16:13:04+03:00",
  "sign": "dd958d1405febce670a9a196e9141784b9f2a5f39cd6d1832d6f3f68d0de1e10"
}
```

> **INFO:** 정적 지갑 webhook에는 `url`이나 `expires_at`이 **포함되지 않습니다**(주소가 영구적이며 세션이 아니기 때문). 다만 `exchange_rate`와 `created_at`은 **포함됩니다**.

### 필드 레퍼런스

| 필드 | 타입 | 설명 |
|-------|------|-------------|
| `uuid` | string | 이 입금에 해당하는 트랜잭션(청구) UUID |
| `order_id` | string | 정적 지갑의 `order_id` |
| `amount` | decimal (8 dp) | 수신한 암호화폐 금액 |
| `currency` | string | 수신 암호화폐 (지갑의 `currency`와 동일) |
| `amount_usd` | decimal (8 dp) | 수신 시점의 USD 환산 금액 |
| `exchange_rate` | decimal | 적용된 암호화폐 / USD 환율 |
| `payer_currency` | string | 정적 지갑에서는 `currency`와 동일 |
| `payer_amount` | decimal (8 dp) | 정적 지갑에서는 `amount`와 동일 |
| `network` | string | 블록체인 네트워크 |
| `address` | string | 정적 지갑 주소 |
| `payment_status` | string | ?? ?? ?????. ????? `paid`??? AML ??? ?? ???? ? ?? `aml_lock`? ? ? ???? |
| `txid` | string | 블록체인 트랜잭션 해시 |
| `tx_explorer_url` | string \| null | 블록체인 탐색기의 트랜잭션 URL입니다. `txid`가 없거나 내부 P2P 전송인 경우 `null`입니다. |
| `payment_amount` | decimal (8 dp) | `amount`와 동일 |
| `merchant_amount` | decimal (18 dp) | **수수료 차감 후 금액** — 잔액 반영에 사용하세요 |
| `created_at` | string (ISO 8601) | 입금 수신 시각 |
| `sign` | string (hex) | payload의 HMAC-SHA256 서명 |

## 모범 사례

- **고유한 `order_id`** — 사용자 또는 주문마다 고유한 `order_id`를 사용하세요
- **멱등성** — 중복 적립을 방지하기 위해 처리 전에 `txid`를 확인하세요
- **서명 검증** — 자금을 반영하기 전 반드시 `sign` 서명을 검증하세요
- **`merchant_amount` 사용** — 사용자 잔액에는 `payment_amount`가 아닌 `merchant_amount`를 기준으로 반영하세요

## 라이프사이클 및 멱등성

정적 지갑은 재사용 가능한 입금 식별자이며, 인보이스가 아닙니다. 예상 금액도 없고 만료일도 없습니다. 하나의 주소는 수명 동안 무수히 많은 입금 거래를 생성할 수 있습니다.

동일한 상인 프로젝트에 대해 생성은 멱등적입니다. `order_id`, `currency`, `network`: 기존 지갑이 반환됩니다. 그 튜플을 안정적으로 유지하고 반환된 지갑 `uuid`을 보존하십시오; 동일한 고객이 입금 화면을 열 때마다 새로운 `order_id`를 사용하지 마십시오.

입금 멱등성은 지갑 멱등성과 다릅니다:

- `order_id`는 재사용 가능한 지갑/고객 매핑을 식별합니다;
- 지갑 `uuid`는 영구 지갑 기록을 식별합니다;
- 웹후크 `uuid`는 감지된 입금 거래 하나를 식별합니다;
- `txid`는 온체인 전송을 식별하며 고객 계정에 입금하기 위한 기본 중복 제거 키입니다.

처리된 체인/네트워크/트랜잭션 ID 정체성을 위한 데이터베이스 고유 제약조건을 사용하고, 고객 내부 잔액에 입금하는 동일 트랜잭션에서 이를 주장합니다.

## 사용 및 사용 중지 의미

지갑을 비활성화하면 애플리케이션이 이를 활성 입금 대상으로 처리하는 것을 방지하지만, 주소나 그 기록을 삭제하지 않으며 사용자가 이미 전송한 블록체인 거래를 중지할 수 없습니다.

> **DANGER:** 사용자에게 비활성 주소로 보낸 자금이 자동으로 반환된다고 절대 말하지 마십시오. 블록체인 전송은 되돌릴 수 없습니다. UI에서 주소를 제거한 후에만 비활성화하고, 늦은 입금을 위한 운영 복구 절차를 유지하십시오.

재활성화하면 동일한 지갑 ID와 주소가 유지됩니다. 단순히 라벨을 변경하기 위해 대체 지갑을 생성하지 마십시오; 라벨은 결제 식별자가 아닙니다.

## 정적 지갑 엣지 케이스

| 상황 | 정확한 처리 |
|-----------|------------------|
| 중복 생성 요청 | 새 주소를 기대하지 말고 반환된 기존 지갑을 수락하고 저장된 튜플을 확인하십시오. |
| 하나의 주소로 여러 입금 | 각 거래마다 별도의 로컬 예치금 행을 생성하십시오 `uuid`/`txid`; 지갑 자체를 '지급 완료'로 표시하지 마십시오. |
| 중복 웹훅 | 이미 커밋된 txid를 찾은 후 HTTP 200 반환; 다시 크레딧하지 마십시오. |
| 확인 지연 또는 체인 재관찰 | 처리를 멱등하게 유지하고 `/v1/static-wallet/transactions`에서 조정하십시오. |
| 자동 변환 최소값 이하의 예치금 | 완료된 `convert` 블록 없이 소스 통화 크레딧 예상 |
| 자동 변환 성공 | 소스 결제 값과 대상 `convert` 결과를 별도로 저장하십시오. |
| 잘못된 토큰 또는 잘못된 네트워크 | 신용을 조작하지 마십시오. 증거를 기록하고 지원/회수 팀에 보고하십시오. 회수 가능성은 체인별로 다릅니다. |
| 메모/태그 기반 체인 | 플랫폼에서 반환된 모든 목적지 필드를 표시하고 검증하십시오. 메모가 필요한 경우 단순한 주소만으로는 충분하지 않을 수 있습니다. |
| AML 잠금 | 권한 있는 상태가 컴플라이언스 절차를 통해 해제될 때까지 최종 사용자에게 신용을 주지 마십시오. |
| 주소 표시 후 지갑 비활성화 | UI에서 즉시 제거하되, 늦은 전송에 대한 운영 알림 모니터링은 계속하십시오. |

## 조정 모델

주기적인 작업을 실행하여 `/v1/static-wallet/transactions`를 페이지 단위로 조회하고, txid로 입금을 업서트하며, `merchant_amount`, 상태 및 선택적 변환 결과를 내부 원장과 비교합니다. 웹훅 전달은 조정을 빠르게 할 수 있게 하지만, 조정은 반드시 완전해야 합니다.