# 그쪽 클로드에게 던질 프롬프트 (복붙용)

> 이 파일 전체를 그쪽 사이트의 Claude Code/AI에 보내면 됨.
> 첨부: `seamless_api_concept_2026-06-03.md` (같은 폴더에 둠).

---

## ⬇️ 여기부터 복사 시작 ⬇️

안녕. 너는 지금 우리 사이트(1222 코드베이스, UPBIT 기반 심리스 월렛 사이트)에서 작업하게 된다. 다른 사이트(SPC, BetMGM 스타일 스포츠북)에서 잘 작동중인 **"알(egg) + 심리스 월렛"** 모델을 우리 사이트에 도입하려 한다.

**첨부된 명세 문서**: `seamless_api_concept_2026-06-03.md` — 12장 + 부록 A/B/C로 구성된 풀 스펙. **반드시 먼저 끝까지 읽고 시작해라.**

### 우리 사이트(1222) 현황

- PHP 8.x + MySQL
- 기존 구조: `site_user/frontend/includes/callback.php`의 `class PartnerCallback`이 이미 UPBIT식 4-URL 콜백(balance/debit/credit/cancel)을 구현. `site_partner/pages/callback.php`에 콜백 설정 UI 있음.
- DB: `users` 단일 테이블 + `user_partner_settings` 별도, 계층은 `user_hierarchy` (closure table, depth 컬럼)
- HMAC-SHA256 서명 (`callback_secret_key`), `X-Signature`/`X-Partner-Id` 헤더 — 이미 동일

### 도입할 것 — 한 줄 요약

UPBIT 콜백은 **회원 외부지갑만** 다룸. SPC는 거기에 **"알(파트너 잔액 한도 + GGR 정산 원장)"이라는 두 번째 트랙**을 추가. 두 트랙은 독립적으로 동시 갱신된다.

- 알 = `user_partner_settings.balance` (신규 컬럼)
- 알 원장 = `egg_transfers` (신규 테이블, `UNIQUE(reference_type, reference_id)`로 멱등)
- 베팅 시: 콜백으로 회원 외부지갑 −stake + 로컬 알도 −stake (한도 차감)
- 정산 시: 콜백으로 회원 외부지갑 ±payout + 로컬 알도 +payout(won)/+stake(void)/0(lost)
- 부수: 베팅 무효 사유(`void_reason`) 4채널 노출 (회원/관리자/파트너 페이지 + 콜백 payload)

### 제약 (절대 어기지 마라)

1. **운영 데이터 보호**: 기존 회원/베팅/파트너 row를 **삭제하지 마라**. 작업 중 ALTER만 사용, 멱등(`IF NOT EXISTS`) 필수.
2. **트랜잭션 + FOR UPDATE 락**: 베팅 접수 / 정산은 반드시 DB 트랜잭션 안에서. 동시성 보호 위해 users·user_partner_settings 행 `SELECT ... FOR UPDATE`. 락 없이 잔액 산수 금지.
3. **멱등성**: `egg_transfers.UNIQUE(reference_type, reference_id)` 필수. 같은 bet_id로 settle 두 번 호출돼도 두 번째는 silently skip 되어야 한다 (잔액 변동 X).
4. **콜백 멱등 가드**: outbound 콜백(`debit/credit/cancel`)은 같은 `game_id+action+success` 캐시 조회 후 캐시 hit이면 재호출하지 말고 캐시 반환. `callback_logs` 테이블 활용.
5. **클라이언트 배당 불신**: 베팅 접수 시 클라이언트가 보낸 odds 무시. DB `odds` 테이블 원본에서 다시 조회 → `(1 - odds_margin/100)` 적용 → 서버 재계산 → potential_win 계산.
6. **콜백 실패 ≠ 정산 실패**: credit/cancel 콜백이 실패해도 베팅 status는 won/void로 확정 (트랜잭션 분리). 콜백은 멱등 가드로 다음 사이클에 자동 재시도.
7. **음수 알 차단**: 베팅 접수 전 `user_partner_settings.balance >= stake` 확인. 부족하면 422 거부. 모니터링 SQL `WHERE balance < 0`은 0건 유지가 운영 기준.
8. **크리덴셜 절대 commit 금지**: `callback_secret_key`, DB 비번 등 `.gitignore` 처리.

### 작업 순서 (단계별, 각 단계 검증 후 다음으로)

#### Step 1. DB 스키마 (롤백 가능한 ALTER만)
첨부 §C-4에 SQL 다 있음. 그대로 실행:
- `user_partner_settings`에 `balance` / `odds_margin` 컬럼 추가
- `egg_transfers` 테이블 신규 (UNIQUE 키 필수)
- `bets.void_reason`, `bet_selections.void_reason`, `bets.settled_at` 컬럼 추가

검증:
```sql
DESC user_partner_settings; -- balance / odds_margin 보여야
DESC egg_transfers;          -- UNIQUE 키 확인
DESC bets; DESC bet_selections; -- void_reason 보여야
```

#### Step 2. `EggLedger` 클래스 추가
첨부 §부록 B의 SPC `app/Services/EggLedger.php` 참고. 1222의 PHP 컨벤션(global `getDB()` 등)에 맞춰 포팅. 4 메서드:
- `debit($settingsId, $userId, $betId, $stake)` — 베팅 시 −stake
- `settle($settingsId, $userId, $betId, $stake, $payout, $status)` — 정산: won→+payout, void→+stake, lost→0
- `reverse($settingsId, $userId, $betId, $stake)` — 베팅 취소(정산 전) +stake 되돌림
- `post()` private — 공통 멱등 INSERT + balance UPDATE

검증: 단위 함수 호출해서 `egg_transfers` row 정확히 쌓이는지 + 같은 bet_id 두 번 호출시 두 번째는 false 리턴되는지.

#### Step 3. 베팅 접수 라우트에 알 한도 + EggLedger.debit 추가
첨부 §4 11단계 흐름 그대로. 1222의 베팅 접수 코드 어디인지 먼저 찾고:
- Step 5b: `user_partner_settings.balance >= stake` 가드 추가
- Step 10c: `EggLedger::debit()` 호출 (트랜잭션 안)
- Step 8: 배당 재계산 (server-side, odds_margin 적용)

기존 PartnerCallback 동작은 그대로 유지. 알 트랙만 추가.

#### Step 4. 정산 로직에 EggLedger.settle 추가
첨부 §5. 1222의 정산 코드(settle daemon 또는 cron) 위치 찾고:
- 트랜잭션 안에서 `EggLedger::settle()` 호출
- void인 경우 `bets.void_reason`, `bet_selections.void_reason` 채우기
- 콜백 분기: won→credit / void→cancel / lost→result (잔액 변동 없음 통지)
- cancel 콜백 payload에 `void_reason` 필드 + selections meta 포함

#### Step 5. void_reason 4채널 노출
- 회원 페이지 (베팅 내역) 노란박스
- 관리자 페이지 (베팅 모달)
- 파트너 페이지 (베팅 모달)
- 콜백 payload (cancel/result의 `void_reason` + `selections[].void_reason`)

UI는 단순. 첨부 §9 표 참고.

#### Step 6. UI 추가 (선택 — 백엔드 검증 후)
첨부 §12.5 사이트별 체크리스트 참고:
- 관리자: 파트너 관리에 "알 충전" 액션 추가 (egg_transfers INSERT + balance UPDATE 트랜잭션)
- 파트너: 콜백 설정 페이지(기존)에 "테스트 전송" 버튼 추가, 알 잔액 표시
- 베팅 리스트: 기간 검색(date_from/date_to 기본 24h) + 페이지네이션 100건/page

### 검증 (Step 4 완료 후 필수)

24시간 모니터링 — 무관용 0 유지:
- `SELECT * FROM user_partner_settings WHERE balance < 0` → **0건**
- `SELECT * FROM bets WHERE status='pending' AND created_at < NOW() - INTERVAL 1 HOUR` 진짜 finished 경기인데 미정산이면 **0건** (안전망이 환불 처리해야)
- `SELECT * FROM callback_logs WHERE http_code != 200 AND created_at > NOW() - INTERVAL 1 HOUR` 콜백 실패는 모니터링하되 멱등 가드로 다음 사이클에 자동 복구되는지 확인
- 같은 bet_id로 settle 2번 강제 호출 → `egg_transfers` 2번 들어가지 않고 1번만 (멱등성 검증)
- 베팅 100건 정도 자동 베팅 스크립트 돌려보고 `won + lost*0 + void*stake + payout` 합산이 `user_partner_settings.balance` 변동분과 일치하는지

### 절대 하지 말 것

- 기존 `PartnerCallback` 클래스 시그니처 변경 (호환 깨짐). 추가 기능은 새 메서드로.
- 기존 회원/베팅 데이터 마이그레이션 (테스트 데이터로만 검증)
- `callback_secret_key`를 git에 commit하거나 로그에 노출
- `EggLedger` 멱등 가드 없이 직접 `UPDATE balance` (중복 정산 위험)
- 베팅 접수에서 클라이언트 odds 그대로 사용 (위변조 가능)
- 트랜잭션 밖에서 `partners.balance` 읽고 산수해서 UPDATE (race condition)

### 단계별 보고

각 Step 끝나면 한 번씩 결과 보고:
- Step 1: 스키마 ALTER 결과 + DESC 출력
- Step 2: EggLedger 4 메서드 + 단위 검증 결과
- Step 3: 베팅 접수 수정 diff + 테스트 베팅 1건 결과(egg_transfers 행 확인)
- Step 4: 정산 수정 diff + 테스트 베팅 정산 결과 + 콜백 로그
- Step 5: 4채널 노출 스크린샷 또는 코드 위치
- Step 6: UI 추가 결과

### 의문이 있으면

코드 진행 전에 명확히 묻고 시작해라:
- 우리 사이트의 베팅 접수 라우트 정확한 파일 위치
- 정산 코드가 daemon인지 cron인지 manual인지
- `user_partner_settings`에 외부 시스템 키(우리 사이트 입장에선 partner 외부 시스템)를 어떻게 인증하는지
- 운영 트래픽 현재 어느 정도인지 (운영중이면 점진적 도입 / 신규면 한번에)

→ 위 4개 답 확보 전에 코드 수정 시작하지 마라.

### 참고 자료 (첨부 외)

- SPC 사이트는 매번 12h 모니터링(454 베팅 / 16 종목)으로 무관용 0 검증 중. 검증 패턴은 첨부 §부록 A.
- 우리 SPC 측 코드 레퍼런스는 첨부 §부록 B 표.

질문 있으면 답하고 시작해라. 시작하면 Step 1부터 순서대로.

## ⬆️ 여기까지 복사 ⬆️

---

## 사용법

1. **이 파일** + `seamless_api_concept_2026-06-03.md` 두 파일을 그쪽 클로드에 전달
2. `seamless_api_concept_2026-06-03.md`를 첨부(또는 그쪽 작업폴더에 복사) → 위 프롬프트 본문(⬇️부터 ⬆️까지) 그대로 복붙해서 발사
3. Step 1 진행 결과 받으면 검증 후 Step 2 진행하라고 답장 — 한 단계씩 진행하면 사고 0

## 조정이 필요할 때

- 그쪽이 운영중이면 프롬프트의 "운영 데이터 보호" 부분 강조
- 그쪽이 신규 구축이면 Step 6 UI 부분도 한번에 진행 OK
- 그쪽이 PHP 다른 프레임워크(Laravel 등)면 §C-3 메서드 시그니처 부분 그쪽 컨벤션에 맞춰 변환하라고 추가 명시
