연동 체크리스트
운영 전환 전에 오퍼레이터 쪽에서 확인할 항목입니다. 검수 때 이 목록을 기준으로 함께 점검합니다.
서명 · 보안
- [ ] 테스트 벡터 3개(POST · 인코딩된 쿼리 GET · 지갑 debit)가 모두 기대 서명과 같다.
- [ ] 서명 비밀키를 hex 디코딩하지 않고 64자리 문자열 그대로 키로 쓴다.
- [ ] 서명한 본문 바이트와 실제로 보내는 본문 바이트가 같다(두 번 직렬화하지 않는다).
- [ ] 쿼리 값을 퍼센트 인코딩하고(
+→%2B), 그 문자열 그대로 서명한다. - [ ] 모든 Operator API 요청에
X-Api-Key를 싣는다. - [ ] 재시도할 때 타임스탬프 · nonce · 서명을 새로 만든다.
- [ ] 오퍼레이터 서버 시계가 NTP 로 맞춰져 있다(허용 오차 ±5분).
- [ ] API 키 · 서명 비밀키가 서버 설정에만 있고, 브라우저 코드 · 앱 · 저장소 · 로그에 남지 않는다.
- [ ] Operator API 를 부르는 모든 서버의 공인 IP 가 IP 허용 목록에 등록돼 있다.
- [ ] 지갑 URL 이 HTTPS 이고, 방화벽이 플랫폼의 지갑 콜백 발신 IP 를 허용한다.
- [ ] 운영 환경 자격증명 · 베이스 URL · 게임 화면 오리진으로 바꿨다(테스트 값이 남아 있지 않다).
지갑 콜백 — 공통
- [ ] 콜백 서명을 원문 본문 바이트로 검증하고,
X-Operator· 타임스탬프(±5분) · 상수 시간 비교를 모두 한다. - [ ] 콜백 nonce 를 5분 이상, 여러 서버가 공유하는 저장소에 보관해 재사용을 거절한다.
- [ ] 리버스 프록시가 경로를 바꾸더라도 등록된 지갑 URL 의 원래 경로로 서명을 검증한다.
- [ ] 업무 결과는 거절을 포함해 모두 HTTP 200 + 본문
status로 돌려준다. - [ ] 응답
balance는 처리 후 잔액이고, 통화 최소 단위 정수다. - [ ]
tx_id멱등: 이미 처리한tx_id가 다시 오면 잔액을 다시 바꾸지 않고 처음과 같은 응답(거절이었으면 같은 거절)을 돌려준다. - [ ] 같은 플레이어의 거래를 동시에 처리해도 잔액이 꼬이지 않는다(DB 트랜잭션 · 행 잠금).
- [ ] 응답 시간이 충분히 짧다 — 플랫폼은
balance·debit을 3초,credit·rollback을 5초 기다린다. - [ ] 지갑 원장에
tx_id·round_id·reason·amount· 처리 결과를 남겨 대사할 수 있다.
balance
- [ ] 없는 플레이어는
PLAYER_NOT_FOUND, 통화가 다르면CURRENCY_MISMATCH로 응답한다.
debit
- [ ] 잔액이 모자라면 차감하지 않고
INSUFFICIENT_FUNDS를 돌려준다. - [ ] 차단 · 자기 배제 중인 플레이어는
PLAYER_BLOCKED로 거절한다. - [ ] 이미 rollback 이 먼저 도착한
tx_id의 debit 이 늦게 오면 차감하지 않고TX_ALREADY_ROLLED_BACK을 돌려준다. - [ ] debit 이 타임아웃 · HTTP 오류 ·
UNKNOWN_ERROR로 끝나면 플랫폼이 debit 을 재시도하지 않고 곧바로 rollback 을 보낸다는 것을 전제로 구현했다. - [ ]
reason(bet·ante·double·split·buy_in·top_up)을 원장에 남긴다.
credit
- [ ] 금액
0인 credit(패배한 라운드 정산)을 정상 처리하고OK를 돌려준다. - [ ] 차단된 플레이어에게도 credit 을 반영한다(진행 중이던 판의 당첨 · 캐시아웃).
- [ ] credit 은
OK가 아니면 상태와 관계없이 계속 재시도된다는 것을 알고, 정상 거래에OK외의 상태를 돌려주지 않는다. - [ ] 재시도 간격(1초 → 2초 → 5초 → 15초 → 1분 → 5분, 이후 5분마다)으로 같은
tx_id가 반복돼도 한 번만 반영된다.
rollback
- [ ]
ref_tx_id로 원 debit 을 찾는다. - [ ] 원 debit 이 성공했으면 그 금액을 되돌리고
OK. - [ ] 원 debit 기록이 없으면(debit 이 닿지 않음) 아무것도 되돌리지 않고
OK를 돌려주며, 그ref_tx_id를 기록해 늦게 오는 debit 을 거절한다. - [ ] 원 debit 이 이미 되돌려졌거나 거절됐으면 다시 되돌리지 않고
OK. - [ ] rollback 도
OK가 아니면 계속 재시도된다는 것을 전제로 구현했다. - [ ]
reason(cancel_bet·round_void·timeout·system)을 원장에 남긴다.
장애 시나리오
검수 때 테스트 지갑에 일부러 장애를 넣어 확인하면 좋습니다.
- [ ] debit 응답을 3초 넘게 늦추면 → 베팅 · 착석이 실패하고, rollback(
timeout)이 와서 잔액이 원래대로 돌아온다. - [ ] debit 에 HTTP 500 을 돌려주면 → 위와 같다.
- [ ] credit 에 몇 번 HTTP 500 을 돌려준 뒤 정상 응답하면 → 재시도로 결국 한 번만 반영된다.
- [ ] 같은
tx_id를 두 번 보내도 잔액이 한 번만 바뀐다. - [ ] 지갑 서버를 잠시 내렸다 올려도, 다시 올라온 뒤 밀린 credit · rollback 이 모두 반영된다.
게임 실행
- [ ]
POST /launch를 오퍼레이터 서버에서만 부른다. - [ ] 플레이어가 누를 때마다
launch_url을 새로 받고(60초 · 1회용), 받은 그대로 연다. - [ ] 새 창은 클릭 처리기 안에서 바로 열고,
noopener를 쓰지 않는다. - [ ] iframe 으로 넣는다면 임베드 도메인을 등록했고,
game.closed를 받으면 iframe 을 닫는다. - [ ]
return_url을 보내거나 기본 복귀 URL 이 등록돼 있다(없으면 postMessage 가 오지 않는다). - [ ] postMessage 를 받을 때
event.origin과data.source === "games-platform"을 확인한다. - [ ]
external_id는 플레이어마다 바뀌지 않는 값이고, 64바이트 이하다. - [ ]
nickname을 2~16자로 맞춰 보낸다. - [ ] 플레이어 통화가 고정된다는 것(
CURRENCY_MISMATCH)을 반영했다. - [ ]
operator_session_id에 로그인 토큰 원문 대신 불투명한 ID 를 쓴다. - [ ]
PLAYER_BLOCKED·GAME_MAINTENANCE·TABLE_NOT_FOUND같은 실행 오류를 플레이어에게 알맞게 안내한다.
플레이어 관리
- [ ] 사이트 로그아웃 · 자기 배제 · 계정 정지 때
POST /players/{external_id}/logout를 부른다(정지는block: true). - [ ] 정지를 풀 때
block: false로 다시 부른다.
대사 · 운영
- [ ]
GET /transactions로 기간별 지갑 거래를 받아 자기 지갑 원장과tx_id단위로 맞춰 본다(정기 작업). - [ ] 대사에서
pending·uncertain상태 거래를 따로 살핀다. - [ ] 베팅 내역 화면이 필요하면
GET /rounds·GET /rounds/{round_id}를 쓴다. - [ ]
prepaid운영이면GET /credit로 알 잔액을 감시하고, 경고 기준(warn_below) 아래로 내려가기 전에 충전을 요청한다. - [ ] Operator API 의
429·5xx에 대기 후 재시도한다(쓰기 요청은 결과를 먼저 조회). - [ ] 장애 · 대사 불일치 때 연락할 담당자와 경로를 정했다.