# UPBIT 파트너 연동 가이드

**버전:** 1.1
**최종 업데이트:** 2026-04-03
**대상:** 외부 파트너 (카지노, 게임 플랫폼 등)

---

## 📋 목차

1. [개요](#1-개요)
2. [시작하기](#2-시작하기)
3. [게임 론칭 API](#3-게임-론칭-api)
4. [심리스 월렛 연동](#4-심리스-월렛-연동)
5. [콜백 보안](#5-콜백-보안)
6. [테스트](#6-테스트)
7. [에러 코드](#7-에러-코드)
8. [FAQ](#8-faq)

---

## 1. 개요

### 1.1 UPBIT 게임이란?

UPBIT는 비트코인 가격 움직임(UP/DOWN)을 예측하는 바이너리 옵션 게임입니다.

**주요 특징:**
- ⏱️ **30초 게임**: 30초 주기 (하루 2,880 라운드)
- ⏱️ **60초 게임**: 60초 주기 (하루 1,440 라운드)
- 📊 실시간 비트코인 가격 연동 (Binance)
- 💰 심리스 월렛 방식 (파트너가 잔액 관리)
- 🎮 iframe 방식 게임 론칭

**게임 타입:**
| 타입 | 주기 | 베팅 마감 | 결과 테이블 |
|------|------|----------|------------|
| 30s | 30초 | 25초, 55초 | `round_results` |
| 60s | 60초 | 45초 | `round_results_60` |

### 1.2 연동 방식

UPBIT는 **에볼루션 게이밍과 동일한 방식**으로 연동됩니다:

```
파트너 사이트
    ↓
1️⃣ 게임 론칭 API 호출 (토큰 발급)
    ↓
2️⃣ iframe에 게임 삽입 (자동 로그인)
    ↓
3️⃣ 배팅 시 콜백 (잔액 차감/지급)
```

### 1.3 필요한 것

✅ **파트너 계정** - UPBIT 관리자에게 발급 요청
✅ **Secret Key** - API 인증용 키
✅ **콜백 서버** - 잔액 조회/차감/지급 API 4개
✅ **HTTPS 환경** - 보안 통신 필수

---

## 2. 시작하기

### 2.1 파트너 계정 발급

**담당자에게 다음 정보 제공:**

```
회사명: (주)파트너컴퍼니
담당자: 홍길동
이메일: partner@example.com
도메인: https://partner-site.com
연락처: 010-1234-5678

요청 사항:
- 파트너 계정 발급
- Secret Key 발급
- 수수료율 협의
```

**발급 받는 것:**
- ✅ 파트너 ID
- ✅ Secret Key (64자리 hex)
- ✅ 파트너 코드 (예: PARTNER001)
- ✅ 파트너 페이지 로그인 계정

### 2.2 파트너 페이지 설정

**접속:** https://pt.bitupgo.com

#### **Step 1: 로그인**
```
발급받은 계정으로 로그인
```

#### **Step 2: 콜백 URL 설정**

메뉴: **API 콜백 설정** (pages/callback.php)

```
✅ 콜백 활성화: ON

Balance URL:  https://your-domain.com/api/upbit/balance
Debit URL:    https://your-domain.com/api/upbit/debit
Credit URL:   https://your-domain.com/api/upbit/credit
Cancel URL:   https://your-domain.com/api/upbit/cancel

Secret Key: [자동 생성된 키 확인]
```

**⚠️ 중요:**
- Secret Key는 안전하게 보관하세요
- 콜백 URL은 HTTPS여야 합니다
- 테스트 버튼으로 연결 확인하세요

#### **Step 3: 콜백 로그 확인**

메뉴: **콜백 로그** (pages/callback_logs.php)

모든 콜백 요청/응답이 실시간으로 기록됩니다.

---

## 3. 게임 론칭 API

### 3.1 개요

사용자가 UPBIT 게임을 플레이하려면 **로그인 토큰**이 필요합니다.

**플로우:**
```
파트너 서버
    ↓
POST /api/partner.php?action=login
    ↓
토큰 반환 (60초 유효)
    ↓
iframe에 토큰 URL 삽입
    ↓
사용자 자동 로그인
```

### 3.2 API 명세

#### **엔드포인트**
```
POST https://bitupgo.com/api/partner.php?action=login
```

#### **Request Headers**
```
Content-Type: application/json
X-Partner-Key: {YOUR_SECRET_KEY}
```

#### **Request Body**
```json
{
  "user_id": "player001",
  "user_pw": "password123",
  "nickname": "플레이어001"
}
```

| 파라미터 | 타입 | 필수 | 설명 |
|---------|------|------|------|
| user_id | string | ✅ | 사용자 고유 ID (4~20자, 영문/숫자/밑줄) |
| user_pw | string | ✅ | 비밀번호 (6자 이상, 일관되게 유지) |
| nickname | string | ⬜ | 게임 닉네임 (없으면 user_id 사용) |

#### **Response (성공)**
```json
{
  "success": true,
  "login_url": "https://bitupgo.com/auth/token_login.php?token=abc123...",
  "expires_in": 60
}
```

#### **Response (실패)**
```json
{
  "success": false,
  "error": "Invalid partner key"
}
```

### 3.3 자동 회원가입

**✨ 중요 기능:** 사용자가 UPBIT에 없으면 **자동으로 생성**됩니다!

```
user_id가 없음
    ↓
자동 회원가입
    ↓
- 파트너 하부로 등록
- 초기 잔액 100,000원 지급
- 토큰 반환
```

**첫 요청 시:**
- 사용자 생성
- 토큰 반환

**이후 요청 시:**
- 비밀번호 확인
- 토큰 반환

### 3.4 구현 예제

#### **PHP 예제**

```php
<?php
// 파트너 서버: launch_upbit.php

function launchUpbitGame($userId, $nickname) {
    $partnerKey = 'your_secret_key_from_upbit';

    // 일관된 비밀번호 생성 (중요!)
    $password = hash('sha256', $userId . 'your_secret_salt');

    $data = [
        'user_id' => $userId,
        'user_pw' => $password,
        'nickname' => $nickname
    ];

    $ch = curl_init('https://bitupgo.com/api/partner.php?action=login');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode($data),
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'X-Partner-Key: ' . $partnerKey
        ],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => true
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200) {
        return ['success' => false, 'error' => 'HTTP ' . $httpCode];
    }

    return json_decode($response, true);
}

// 사용 예
session_start();
$userId = $_SESSION['user_id'];
$nickname = $_SESSION['nickname'];

$result = launchUpbitGame($userId, $nickname);

if ($result['success']) {
    $loginUrl = $result['login_url'];
    ?>
    <!DOCTYPE html>
    <html>
    <body>
        <iframe src="<?= htmlspecialchars($loginUrl) ?>"
                width="100%"
                height="800"
                frameborder="0"
                allowfullscreen></iframe>
    </body>
    </html>
    <?php
} else {
    echo "게임 론칭 실패: " . $result['error'];
}
?>
```

#### **JavaScript (Node.js) 예제**

```javascript
// 파트너 서버: routes/upbit.js

const axios = require('axios');
const crypto = require('crypto');

async function launchUpbitGame(userId, nickname) {
    const partnerKey = process.env.UPBIT_SECRET_KEY;

    // 일관된 비밀번호 생성
    const password = crypto
        .createHash('sha256')
        .update(userId + 'your_secret_salt')
        .digest('hex');

    try {
        const response = await axios.post(
            'https://bitupgo.com/api/partner.php?action=login',
            {
                user_id: userId,
                user_pw: password,
                nickname: nickname
            },
            {
                headers: {
                    'Content-Type': 'application/json',
                    'X-Partner-Key': partnerKey
                }
            }
        );

        return response.data;
    } catch (error) {
        console.error('UPBIT API Error:', error.message);
        return { success: false, error: error.message };
    }
}

// Express 라우트 예제
app.get('/play/upbit', async (req, res) => {
    const userId = req.session.userId;
    const nickname = req.session.nickname;

    const result = await launchUpbitGame(userId, nickname);

    if (result.success) {
        res.render('upbit_game', { loginUrl: result.login_url });
    } else {
        res.status(500).send('게임 론칭 실패: ' + result.error);
    }
});
```

#### **JavaScript (브라우저) 예제**

```html
<!DOCTYPE html>
<html>
<head>
    <title>UPBIT Game</title>
</head>
<body>
    <div id="game-container"></div>

    <script>
    async function launchGame(userId, nickname) {
        const partnerKey = 'your_secret_key';

        // ⚠️ 주의: 브라우저에서는 Secret Key가 노출됨
        // 반드시 서버 사이드에서 호출하세요!

        const password = await hashPassword(userId);

        const response = await fetch('https://bitupgo.com/api/partner.php?action=login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Partner-Key': partnerKey
            },
            body: JSON.stringify({
                user_id: userId,
                user_pw: password,
                nickname: nickname
            })
        });

        const result = await response.json();

        if (result.success) {
            // iframe 생성
            const iframe = document.createElement('iframe');
            iframe.src = result.login_url;
            iframe.width = '100%';
            iframe.height = '800';
            iframe.frameBorder = '0';

            document.getElementById('game-container').appendChild(iframe);
        } else {
            alert('게임 론칭 실패: ' + result.error);
        }
    }

    async function hashPassword(userId) {
        const encoder = new TextEncoder();
        const data = encoder.encode(userId + 'your_secret_salt');
        const hashBuffer = await crypto.subtle.digest('SHA-256', data);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    }

    // 사용 예 (서버에서 사용자 정보 전달받음)
    launchGame('player001', '플레이어001');
    </script>
</body>
</html>
```

### 3.5 비밀번호 관리 전략

**⚠️ 매우 중요:** 같은 사용자는 **항상 같은 비밀번호**를 사용해야 합니다!

#### **방법 1: 해시 기반 (권장)**

```php
// 일관된 비밀번호 생성
$password = hash('sha256', $userId . 'fixed_secret_salt_12345');
```

**장점:**
- 간단함
- 일관성 보장
- DB 저장 불필요

**단점:**
- Salt가 노출되면 모든 비밀번호 유추 가능
- Salt를 안전하게 보관해야 함

#### **방법 2: DB 저장**

```sql
CREATE TABLE upbit_users (
    local_user_id INT PRIMARY KEY,
    upbit_user_id VARCHAR(50),
    upbit_password VARCHAR(255),
    created_at TIMESTAMP
);
```

```php
// 첫 생성 시
$password = bin2hex(random_bytes(16));
// DB에 저장

// 이후 사용 시
// DB에서 조회
```

**장점:**
- 더 안전함
- 비밀번호 재설정 가능

**단점:**
- DB 관리 필요
- 복잡함

---

## 4. 심리스 월렛 연동

### 4.1 개요

UPBIT는 **파트너가 잔액을 관리**합니다. 배팅/당첨 시 **콜백 API**로 요청합니다.

**플로우:**
```
사용자 배팅
    ↓
UPBIT → 파트너: balance 콜백 (잔액 확인)
    ↓
UPBIT → 파트너: debit 콜백 (배팅 차감)
    ↓
게임 종료 (WIN)
    ↓
UPBIT → 파트너: credit 콜백 (당첨금 지급)
```

### 4.2 구현해야 할 API

파트너 서버에 **4개의 API**를 구현해야 합니다:

| API | 용도 | 호출 시점 |
|-----|------|----------|
| **balance** | 잔액 조회 | 배팅 시도 시 |
| **debit** | 차감 | 라운드 마감 (T+25초) |
| **credit** | 지급 | 정산 완료 (WIN 시) |
| **cancel** | 취소 | 라운드 취소 시 |

### 4.3 콜백 요청 형식

모든 콜백 요청은 **동일한 형식**입니다:

**Headers:**
```
Content-Type: application/json
X-Signature: {HMAC-SHA256 서명}
X-Partner-Id: {파트너 ID}
```

**Body 예시:**
```json
{
  "action": "balance",
  "user_id": "player001",
  "timestamp": 1770380000
}
```

### 4.4 Balance API (잔액 조회)

#### **Request**
```json
{
  "action": "balance",
  "user_id": "player001",
  "timestamp": 1770380000
}
```

#### **Response (성공)**
```json
{
  "success": true,
  "balance": 1000000
}
```

#### **Response (실패)**
```json
{
  "success": false,
  "error": "User not found",
  "error_code": "USER_NOT_FOUND"
}
```

#### **구현 예제 (PHP)**

```php
<?php
// 파트너 서버: api/upbit/balance.php

header('Content-Type: application/json');

// 1. 서명 검증 (중요!)
$secretKey = 'your_callback_secret_key_from_upbit';
$requestBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

$expectedSignature = hash_hmac('sha256', $requestBody, $secretKey);

if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    exit(json_encode(['success' => false, 'error' => 'Invalid signature']));
}

// 2. 요청 파싱
$data = json_decode($requestBody, true);
$userId = $data['user_id'] ?? '';

if (!$userId) {
    exit(json_encode(['success' => false, 'error' => 'Missing user_id']));
}

// 3. 잔액 조회 (파트너 DB에서)
$balance = getUserBalance($userId); // 파트너 구현

if ($balance === null) {
    exit(json_encode(['success' => false, 'error' => 'User not found']));
}

// 4. 응답
echo json_encode([
    'success' => true,
    'balance' => $balance
]);

function getUserBalance($userId) {
    // 파트너 DB에서 잔액 조회
    global $pdo;
    $stmt = $pdo->prepare("SELECT balance FROM users WHERE upbit_user_id = ?");
    $stmt->execute([$userId]);
    $user = $stmt->fetch();
    return $user ? floatval($user['balance']) : null;
}
?>
```

### 4.5 Debit API (배팅 차감)

#### **Request**
```json
{
  "action": "debit",
  "user_id": "player001",
  "game_id": "202603150001",
  "game_type": "30s",
  "amount": 10000,
  "tx_id": "tx_abc123",
  "timestamp": 1770380000
}
```

| 파라미터 | 설명 |
|---------|------|
| game_id | 라운드 ID (YYYYMMDDNNNN) |
| **game_type** | **게임 타입 ("30s" 또는 "60s")** |
| amount | 차감 금액 |
| tx_id | 트랜잭션 ID (중복 방지용) |

#### **Response (성공)**
```json
{
  "success": true,
  "balance": 990000,
  "tx_id": "tx_abc123"
}
```

#### **Response (실패 - 잔액 부족)**
```json
{
  "success": false,
  "error": "Insufficient balance",
  "error_code": "INSUFFICIENT_FUNDS"
}
```

#### **구현 예제 (PHP)**

```php
<?php
// 파트너 서버: api/upbit/debit.php

header('Content-Type: application/json');

// 1. 서명 검증
$secretKey = 'your_callback_secret_key_from_upbit';
$requestBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

if (!hash_equals(hash_hmac('sha256', $requestBody, $secretKey), $signature)) {
    http_response_code(401);
    exit(json_encode(['success' => false, 'error' => 'Invalid signature']));
}

// 2. 요청 파싱
$data = json_decode($requestBody, true);
$userId = $data['user_id'];
$gameId = $data['game_id'];
$amount = floatval($data['amount']);
$txId = $data['tx_id'];

// 3. 중복 확인
if (isDuplicateTransaction($txId)) {
    exit(json_encode(['success' => false, 'error' => 'Duplicate transaction']));
}

// 4. 잔액 확인
$balance = getUserBalance($userId);
if ($balance < $amount) {
    exit(json_encode([
        'success' => false,
        'error' => 'Insufficient balance',
        'error_code' => 'INSUFFICIENT_FUNDS'
    ]));
}

// 5. 트랜잭션 처리
try {
    $pdo->beginTransaction();

    // 잔액 차감
    $stmt = $pdo->prepare("UPDATE users SET balance = balance - ? WHERE upbit_user_id = ?");
    $stmt->execute([$amount, $userId]);

    // 트랜잭션 기록
    $stmt = $pdo->prepare("
        INSERT INTO upbit_transactions (tx_id, user_id, game_id, type, amount, created_at)
        VALUES (?, ?, ?, 'debit', ?, NOW())
    ");
    $stmt->execute([$txId, $userId, $gameId, $amount]);

    $pdo->commit();

    $newBalance = $balance - $amount;

    echo json_encode([
        'success' => true,
        'balance' => $newBalance,
        'tx_id' => $txId
    ]);

} catch (Exception $e) {
    $pdo->rollBack();
    error_log("Debit error: " . $e->getMessage());
    exit(json_encode(['success' => false, 'error' => 'Database error']));
}
?>
```

### 4.6 Credit API (당첨금 지급)

#### **Request**
```json
{
  "action": "credit",
  "user_id": "player001",
  "game_id": "202603150001",
  "game_type": "30s",
  "win_amount": 19000,
  "tx_id": "tx_abc123",
  "start_price": 66500.00,
  "end_price": 66550.00,
  "result": "UP",
  "timestamp": 1770380030
}
```

| 파라미터 | 설명 |
|---------|------|
| game_id | 라운드 ID (YYYYMMDDNNNN) |
| **game_type** | **게임 타입 ("30s" 또는 "60s")** |
| win_amount | 당첨금 (배팅액 × 배당률) |
| tx_id | 트랜잭션 ID (debit과 동일) |
| start_price | 라운드 시작 BTC 가격 |
| end_price | 라운드 종료 BTC 가격 |
| result | 라운드 결과 ("UP", "DOWN", "DRAW") |

#### **Response**
```json
{
  "success": true,
  "balance": 1009000,
  "tx_id": "tx_abc123"
}
```

#### **구현 예제 (PHP)**

```php
<?php
// 파트너 서버: api/upbit/credit.php

header('Content-Type: application/json');

// 서명 검증
$secretKey = 'your_callback_secret_key_from_upbit';
$requestBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

if (!hash_equals(hash_hmac('sha256', $requestBody, $secretKey), $signature)) {
    http_response_code(401);
    exit(json_encode(['success' => false, 'error' => 'Invalid signature']));
}

// 요청 파싱
$data = json_decode($requestBody, true);
$userId = $data['user_id'];
$gameId = $data['game_id'];
$winAmount = floatval($data['win_amount']);
$txId = $data['tx_id'];

// 중복 확인
if (isDuplicateTransaction($txId)) {
    exit(json_encode(['success' => false, 'error' => 'Duplicate transaction']));
}

// 잔액 증가
try {
    $pdo->beginTransaction();

    $stmt = $pdo->prepare("UPDATE users SET balance = balance + ? WHERE upbit_user_id = ?");
    $stmt->execute([$winAmount, $userId]);

    $stmt = $pdo->prepare("
        INSERT INTO upbit_transactions (tx_id, user_id, game_id, type, amount, created_at)
        VALUES (?, ?, ?, 'credit', ?, NOW())
    ");
    $stmt->execute([$txId, $userId, $gameId, $winAmount]);

    $pdo->commit();

    $balance = getUserBalance($userId);

    echo json_encode([
        'success' => true,
        'balance' => $balance,
        'tx_id' => $txId
    ]);

} catch (Exception $e) {
    $pdo->rollBack();
    error_log("Credit error: " . $e->getMessage());
    exit(json_encode(['success' => false, 'error' => 'Database error']));
}
?>
```

### 4.7 Cancel API (배팅 취소)

#### **Request**
```json
{
  "action": "cancel",
  "user_id": "player001",
  "game_id": "202603150001",
  "amount": 10000,
  "tx_id": "tx_abc123",
  "timestamp": 1770380015
}
```

#### **Response**
```json
{
  "success": true,
  "balance": 1000000,
  "tx_id": "tx_abc123"
}
```

---

## 5. 콜백 보안

### 5.1 HMAC-SHA256 서명

모든 콜백 요청은 **HMAC-SHA256**으로 서명됩니다.

**서명 생성 (UPBIT 서버):**
```php
$requestBody = json_encode($data);
$signature = hash_hmac('sha256', $requestBody, $secretKey);
// X-Signature 헤더로 전송
```

**서명 검증 (파트너 서버):**
```php
$requestBody = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_SIGNATURE'];
$secretKey = 'your_callback_secret_key';

$expectedSignature = hash_hmac('sha256', $requestBody, $secretKey);

if (!hash_equals($expectedSignature, $receivedSignature)) {
    // 서명 불일치 - 거부
    http_response_code(401);
    exit(json_encode(['success' => false, 'error' => 'Invalid signature']));
}
```

**⚠️ 주의:**
- `hash_equals()` 사용 (타이밍 공격 방지)
- 원본 요청 body를 그대로 사용
- JSON 파싱 전에 검증

### 5.2 중복 방지

**tx_id로 중복 요청 방지:**

```php
function isDuplicateTransaction($txId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM upbit_transactions WHERE tx_id = ?");
    $stmt->execute([$txId]);
    return (bool)$stmt->fetch();
}
```

### 5.3 IP 화이트리스트 (선택)

```php
$allowedIPs = ['123.456.789.0', '98.76.54.32'];
$requestIP = $_SERVER['REMOTE_ADDR'];

if (!in_array($requestIP, $allowedIPs)) {
    http_response_code(403);
    exit(json_encode(['success' => false, 'error' => 'IP not allowed']));
}
```

---

## 6. 테스트

### 6.1 테스트 환경

**테스트 URL:**
```
https://bitupgo.com/partner_test_login.html
```

**테스트 Secret Key:**
```
test_secret_key_12345
```

### 6.2 테스트 절차

#### **Step 1: 콜백 서버 구현**

4개 API 구현:
- `/api/upbit/balance`
- `/api/upbit/debit`
- `/api/upbit/credit`
- `/api/upbit/cancel`

#### **Step 2: 파트너 페이지에서 URL 설정**

https://pt.bitupgo.com → API 콜백 설정

#### **Step 3: 테스트 버튼으로 연결 확인**

각 API별로 "테스트" 버튼 클릭 → 200 응답 확인

#### **Step 4: 게임 론칭 테스트**

```bash
curl -X POST https://bitupgo.com/api/partner.php?action=login \
  -H "Content-Type: application/json" \
  -H "X-Partner-Key: your_secret_key" \
  -d '{
    "user_id": "testuser",
    "user_pw": "password123",
    "nickname": "테스터"
  }'
```

#### **Step 5: 실제 게임 플레이**

1. 토큰 URL로 접속
2. 배팅 (10,000원 UP)
3. 파트너 서버 로그 확인:
   - balance 콜백 1회
   - debit 콜백 1회 (T+25초)
   - credit 콜백 1회 (WIN 시, T+30초)

### 6.3 콜백 로그 확인

**파트너 페이지:**
https://pt.bitupgo.com → 콜백 로그

**확인 사항:**
- ✅ HTTP 200 응답
- ✅ 요청/응답 데이터 정상
- ✅ 에러 없음

---

## 7. 에러 코드

### 7.1 게임 론칭 API 에러

| HTTP | error | 설명 | 조치 |
|------|-------|------|------|
| 401 | Invalid partner key | Secret Key 불일치 | Secret Key 확인 |
| 400 | Missing user_id or user_pw | 필수 파라미터 누락 | user_id, user_pw 전송 |
| 400 | Invalid password | 비밀번호 불일치 | 동일한 비밀번호 사용 확인 |
| 500 | User registration failed | 회원가입 실패 | 로그 확인 |

### 7.2 콜백 API 에러 (파트너 응답)

| error_code | 설명 | 조치 |
|-----------|------|------|
| INSUFFICIENT_FUNDS | 잔액 부족 | 정상 동작 |
| USER_NOT_FOUND | 사용자 없음 | user_id 확인 |
| DUPLICATE_TRANSACTION | 중복 요청 | 정상 동작 (무시) |
| DATABASE_ERROR | DB 오류 | 파트너 서버 로그 확인 |

### 7.3 UPBIT 내부 에러

콜백 실패 시 UPBIT의 동작:

| 상황 | 처리 |
|------|------|
| balance 실패 | 배팅 거부 |
| debit 실패 | 배팅 취소 (cancelled) |
| credit 실패 | 로컬 잔액으로 처리 (로그 기록) |

---

## 8. FAQ

### Q1. 사용자 ID 중복을 어떻게 방지하나요?

**A:** Prefix를 사용하세요.

```javascript
const userId = `partner_${partnerId}_${localUserId}`;
// 예: "casino_user_12345"
```

### Q2. 비밀번호를 잊어버렸는데 어떻게 하나요?

**A:** 일관된 해시를 사용하면 문제없습니다.

```php
// 항상 동일한 비밀번호 생성
$password = hash('sha256', $userId . 'fixed_salt');
```

### Q3. 콜백 URL을 변경하려면?

**A:** 파트너 페이지에서 변경 후 저장하세요.

https://pt.bitupgo.com → API 콜백 설정

### Q4. Secret Key가 노출됐어요!

**A:** 즉시 담당자에게 연락하여 재발급 받으세요.

### Q5. 게임이 iframe에서 안 열려요.

**A:** X-Frame-Options 확인하세요.

```
UPBIT는 모든 도메인에서 iframe 허용됨
```

### Q6. 콜백이 너무 많이 와요.

**A:** balance 콜백은 5초 주기로 호출됩니다.

- 게임 중: 5초마다
- 대기 중: 35초마다

### Q7. 잔액이 안 맞아요.

**A:** 콜백 로그를 확인하세요.

파트너 페이지 → 콜백 로그 → 요청/응답 확인

### Q8. 테스트 환경이 있나요?

**A:** 동일 서버를 사용하되, 소액으로 테스트하세요.

```
초기 잔액: 100,000원
최소 배팅: 10,000원
```

### Q9. 여러 게임을 동시에 제공할 수 있나요?

**A:** 네, 동일한 Secret Key로 여러 사이트에서 사용 가능합니다.

### Q10. HTTPS가 필수인가요?

**A:** 네, 콜백 URL은 반드시 HTTPS여야 합니다.

---

### 리소스

- **파트너 페이지:** https://pt.bitupgo.com
- **테스트 페이지:** https://bitupgo.com/partner_test_login.html
- **API 문서:** UPBIT_API_Documentation_v2.0_2026-03-15.md

---

## 📝 체크리스트

연동 전 확인 사항:

- [ ] 파트너 계정 발급 완료
- [ ] Secret Key 안전하게 보관
- [ ] 콜백 서버 4개 API 구현 완료
- [ ] HTTPS 환경 구축
- [ ] 파트너 페이지에서 콜백 URL 설정
- [ ] 서명 검증 구현 완료
- [ ] 중복 방지 로직 구현
- [ ] 테스트 완료 (balance, debit, credit, cancel)
- [ ] 게임 론칭 테스트 완료
- [ ] 실제 배팅 테스트 완료
- [ ] 로그 모니터링 체계 구축
- [ ] 에러 처리 구현 완료
- [ ] 프로덕션 배포 완료

---

---

## 📝 변경 이력

| 버전 | 날짜 | 변경 내용 |
|------|------|----------|
| 1.0 | 2026-03-15 | 초기 문서 작성 |
| **1.1** | **2026-04-03** | **60초 게임 추가, game_type 파라미터 추가 (debit/credit)** |

---

**© 2026 UPBIT. All rights reserved.**

**버전:** 1.1
**최종 수정:** 2026-04-03
