본문으로 건너뛰기
한국어 번역 진행 중한국어 문서를 완성하는 동안 일부 가이드와 API 설명은 아직 영어로 표시될 수 있습니다.

콜백 테스트

이 가이드는 본 서비스 시작 전에 Seamless Wallet 콜백 구현을 검증하는 데 도움을 드립니다. 아래의 체크리스트와 테스트 흐름을 따라 엔드포인트가 올바르게 동작하는지 확인하십시오.


출시 전 체크리스트

엔드포인트 가용성

  • 4개의 엔드포인트가 모두 구현됨: /balance, /debit, /credit, /rollback
  • Ruby의 테스트 환경에서 HTTPS를 통해 엔드포인트에 접근 가능
  • callback_url이 Ruby 브랜드 설정에 등록됨 (후행 슬래시 없음)
  • 엔드포인트가 application/json 응답을 반환

응답 정확성

  • /balance{ "balance": "<decimal string>" }을 반환
  • /debit{ "balance": "<decimal string>", "balance_before": "<decimal string>" }을 반환
  • /credit{ "balance": "<decimal string>", "balance_before": "<decimal string>" }을 반환
  • /rollback{ "balance": "<decimal string>" }을 반환 (balance_before 없음)
  • 모든 balance 값이 숫자가 아닌 소수점 문자열로 반환됨 (예: "1250.00")

인증

  • 4개의 모든 엔드포인트에 서명 검증이 구현됨
  • JSON 파싱 전에 원시 본문 바이트가 캡처됨
  • X-Aggregator-Key가 설정된 api_key에 대해 검증됨
  • X-Aggregator-Timestamp가 300초 범위 내에서 확인됨
  • 서명 비교에 상수 시간 함수(Constant-time Function)를 사용 (== 아님)
  • 유효하지 않은 서명이 있는 요청이 401을 반환
  • 만료된 타임스탬프가 있는 요청이 401을 반환

멱등성(Idempotency)

  • 동일한 transaction_id/debit에 두 번 전송 시 재처리 없이 동일한 응답을 반환
  • 동일한 transaction_id/credit에 두 번 전송 시 재입금 없이 동일한 응답을 반환
  • 동일한 transaction_id/rollback에 두 번 전송 시 재입금 없이 동일한 응답을 반환
  • /balance가 매번 호출 시 현재 실시간 잔액을 반환 (캐싱 없음)

성능

  • 예상 부하에서 모든 엔드포인트가 3초 이내에 응답
  • 최대 부하에서도 5초 이내에 응답 (하드 타임아웃)
  • 동시 요청으로 부하 테스트를 수행하여 차감 로직의 경쟁 상태(Race Condition)가 없음을 검증

오류 처리

  • /debit에서 잔액 부족 시 비-2xx 상태 코드를 반환
  • 알 수 없는 player_id에 대해 비-2xx 상태 코드를 반환
  • 내부 오류 시 5xx를 반환 (오류 본문이 포함된 200이 아님)

권장 테스트 흐름

1단계 -- 단위 테스트(Unit Testing)

Ruby의 테스트 환경에 연결하기 전에, 로컬에서 직접 작성한 요청으로 구현을 검증하십시오.

1. 서명 검증 테스트

수동으로 요청을 작성하여 유효한 서명이 수락되고 유효하지 않은 서명이 거부되는지 확인하십시오:

import hashlib
import hmac
import json
import time

api_secret = "your_test_secret"
body = json.dumps({"player_id": 1, "username": "test"}).encode()
timestamp = int(time.time())
timestamp_str = str(timestamp)

sig = hmac.new(
api_secret.encode(),
body + timestamp_str.encode(),
hashlib.sha256,
).hexdigest()

print(f"X-Aggregator-Key: your_test_key")
print(f"X-Aggregator-Timestamp: {timestamp_str}")
print(f"X-Aggregator-Signature: {sig}")
print(f"Body: {body.decode()}")

이 요청을 로컬 서버로 전송하여 200이 반환되는지 확인하십시오. 그런 다음 본문 또는 서명을 수정하여 401이 반환되는지 확인하십시오.

2. 멱등성 테스트

동일한 transaction_id 값으로 동일한 /debit 요청을 두 번 전송하십시오. 다음을 검증합니다:

  • 잔액이 한 번만 차감됨
  • 두 호출 모두 동일한 balancebalance_before 값을 반환

3. 엣지 케이스 테스트

  • 빈 선택적 필드 (필수 필드만 전송)
  • amount"0.01" (최소값)
  • 잔액 한도에 근접한 큰 금액
  • 동시 동일 요청 (경쟁 상태 테스트)

2단계 -- 통합 테스트(Integration Testing)

Ruby 담당 매니저가 제공한 테스트 자격 증명을 사용하여 Ruby의 샌드박스/테스트 환경에 연결하십시오.

일반적인 테스트 순서:

  1. /balance 호출 -- 올바른 테스트 플레이어 잔액이 반환되는지 확인
  2. 알려진 금액으로 /debit 호출 -- balance_before/balance 결과와 일치하는지 확인
  3. 더 작은 금액으로 /credit 호출 -- 잔액이 올바르게 증가하는지 확인
  4. /rollback 호출 -- 잔액이 복원되는지 확인
  5. 동일한 transaction_id/debit 반복 -- 멱등 응답이 반환되고 이중 차감이 발생하지 않는지 확인
  6. /balance 다시 호출 -- 최종 잔액이 일관되는지 확인

3단계 -- 부하 및 복원력 테스트(Load and Resilience Testing)

본 서비스 시작 전에 실제 트래픽 환경에서 구현을 검증하십시오:

  • 동일 플레이어에 대한 동시 debit+credit 요청을 실행하여 경쟁 상태가 없는지 검증
  • 느린 데이터베이스 응답을 시뮬레이션하여 서버가 5초 타임아웃 이내에 유지되는지 확인
  • 데이터베이스가 일시적으로 사용 불가능할 때 서버가 5xx를 반환하는지 (멈추지 않는지) 검증

흔한 오류

서명 불일치 (401)

증상: Ruby 로그에 상태 401로 콜백 실패가 표시됨; 4개의 모든 엔드포인트가 요청을 거부합니다.

체크리스트:

  • JSON 파싱 전에 원시 본문 바이트를 읽고 있습니까? 동일한 내용이라도 JSON 본문을 재직렬화하면 필드 순서나 공백이 변경될 수 있습니다.
  • 올바른 시크릿을 사용하고 있습니까? 콜백은 team_api_secret이 아닌 brand.api_secret을 사용합니다.
  • 연결 순서가 올바릅니까? body_bytes + timestamp_bytes여야 합니다. 타임스탬프는 끝에 위치합니다. Team API 서명에서는 처음에 위치하는 것과 다릅니다.
  • 16진수 인코딩이 소문자입니까? X-Aggregator-Signature 헤더 값과 직접 비교하십시오.

디버깅 팁: HMAC 계산에 입력하는 정확한 바이트를 로깅하고 참조 구현과 비교하십시오. 콜백 인증의 Python 예제가 신뢰할 수 있는 참조입니다.

타임아웃 실패

증상: Ruby가 콜백 타임아웃 오류를 보고함; 플레이어의 게임 트랜잭션이 실패합니다.

체크리스트:

  • 잔액/트랜잭션 조회를 위한 데이터베이스 쿼리에 적절한 인덱스가 있습니까?
  • 응답 스레드를 차단할 수 있는 동기 I/O를 수행하고 있습니까?
  • 콜백 서버가 Ruby 서버와 낮은 지연 시간을 가진 리전에 배포되어 있습니까? 예상 소스 IP 범위에 대해서는 담당 매니저에게 문의하십시오.
  • 타임아웃 발생 시 서버가 메모리 또는 CPU 압력을 받고 있습니까?

디버깅 팁: 콜백 엔드포인트에 요청별 타이밍 로그를 추가하십시오. 데이터베이스 쿼리에 소요된 시간을 네트워크 시간과 별도로 로깅하십시오. 데이터베이스 쿼리는 빠르지만 전체 응답이 느린 경우, 커넥션 풀(Connection Pool) 고갈 여부를 확인하십시오.

비멱등 응답

증상: 플레이어가 게임 라운드 후 중복 청구를 신고함; 정산 보고서에서 잔액 불일치가 발생합니다.

근본 원인: 귀사의 서버가 동일한 transaction_id를 두 번 처리하여 debit/credit/rollback을 두 번째로 적용했습니다.

체크리스트:

  • transaction_id가 유니크 제약 조건이 있는 테이블에 저장되어 있습니까?
  • 트랜잭션을 적용하기 전에 기존 transaction_id를 확인합니까?
  • 동시 부하에서 동일한 transaction_id를 가진 두 요청이 중복 검사를 동시에 통과할 수 있습니까? 이를 방지하려면 애플리케이션 수준 검사만이 아닌 데이터베이스 수준의 유니크 제약 조건을 사용하십시오.

수정 패턴:

-- Example unique constraint
ALTER TABLE wallet_transactions
ADD CONSTRAINT uq_transaction_id UNIQUE (transaction_id, action);

그런 다음 핸들러에서:

  • 트랜잭션 레코드 삽입을 시도합니다
  • 유니크 제약 조건 위반이 발생하면 기존 레코드를 조회하여 저장된 응답을 반환합니다
  • 중복 시 지갑 작업을 절대 다시 적용하지 마십시오

잘못된 응답 형태

증상: Ruby 로그에 성공적인 HTTP 응답이 표시되지만 게임 라운드가 올바르지 않게 동작합니다 (잘못된 잔액 표시, 라운드 미완료).

체크리스트:

  • balance 값이 JSON 숫자(1250.00)가 아닌 문자열(예: "1250.00")로 반환됩니까? Ruby는 Decimal(str(...))을 통해 파싱하므로 JSON 숫자도 작동하지만, 문자열이 기대 형식입니다.
  • /rollback{ "balance": "..." }만 반환합니까 -- balance_before 없이? 추가 필드는 무시되지만 구현이 명세와 일치하는지 확인하십시오.
  • /debit/creditbalancebalance_before 둘 다 반환합니까? balance_before가 누락되면 "0"으로 처리됩니다.

시계 오차 (타임스탬프 검증 실패)

증상: 배포 직후 모든 콜백이 401로 실패; 정상 운영 중 간헐적 401 발생.

원인: 귀사의 서버 시계가 Ruby 서버와 300초 이상 동기화되지 않았습니다.

수정: NTP가 실행 중이고 서버 시간이 동기화되어 있는지 확인하십시오. 300초 범위는 올바르게 설정된 서버라면 충분히 넉넉합니다.


디버깅 팁

원시 헤더와 본문 로깅

개발 중에는 모든 수신 콜백 요청에 대해 3개의 X-Aggregator-* 헤더와 원시 본문 바이트를 모두 로깅하십시오. 이렇게 하면 로컬에서 문제를 재현하기 쉬워집니다.

실패한 요청을 로컬에서 재생

서명 검증에 실패하는 콜백을 수신한 경우, 정확한 헤더와 원시 본문을 로깅하십시오. 그런 다음 해당 요청을 로컬에서 검증 코드에 대해 재생하여 불일치를 디버깅할 수 있습니다.

참조 구현을 사용하여 테스트 요청 생성

위 1단계의 Python 코드 스니펫은 올바르게 서명된 테스트 요청을 생성합니다. 이를 사용하여 로컬 서버에 직접 요청을 전송하면 서명 검증 문제인지 비즈니스 로직 문제인지 격리할 수 있습니다.

본문을 수정하는 미들웨어 확인

일부 웹 프레임워크에는 핸들러가 보기 전에 JSON을 디코딩하고 재인코딩하는 본문 파싱 미들웨어가 있습니다. 미들웨어가 본문을 변환하기 전에 가장 바깥 계층에서 원시 바이트를 캡처하는지 확인하십시오. 프레임워크별 참고사항은 콜백 인증을 참조하십시오.

소수점 처리 검증

amount를 파싱하고 balance 값을 반환할 때 부동소수점이 아닌 소수점(Decimal) 라이브러리를 사용하십시오. 예를 들어, "100.10"을 부동소수점으로 표현하면 내부적으로 100.09999999999999가 될 수 있습니다. Python의 Decimal, Java의 BigDecimal, 또는 Node.js의 decimal.js/big.js를 사용하십시오.