Game8 Store – Developer Center
› 기술 사양 & API

테스트 구현 가이드

📝 본 페이지는 참고용 번역(한국어)입니다. 일본어 원본과 차이가 있는 경우 일본어판을 우선합니다.

Game8 Store와 연동하여,API 사양서를 확인하여 필수 API(구매 가능 여부 체크(구매 가능 여부 체크) 및 POST /register(구매 등록)을 간단한 테스트 구현으로 시험해 보는 방법을 소개합니다.

예상 소요 시간:15분

1. 엔드포인트 구현(예상 소요 시간: 10분)

1.1 필요한 환경

  • Node.js(Express) 또는 Python(FastAPI) 사용
  • 서버를 로컬 또는 클라우드(AWS, GCP, Vercel, Render 등)에 배포할 수 있는 상태

1.2 Node.js(Express)로 구현

const express = require("express");
const cors = require("cors");
const crypto = require("crypto");

const app = express();
app.use(express.json());
app.use(cors());

const VALID_ACCESS_TOKEN = "your_access_token";
const SECRET_KEY = "your_secret_key";

// 인증 미들웨어
app.use((req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ result_code: "PUB1001", message: "인증 정보가 부족합니다" });
  }
  const [scheme, token] = authHeader.split(" ");
  if (scheme !== "Bearer" || token !== VALID_ACCESS_TOKEN) {
    return res.status(401).json({ result_code: "PUB1002", message: "유효하지 않은 액세스 토큰입니다" });
  }
  next();
});

// X-Signature 검증 미들웨어
const verifySignature = (req, res, next) => {
  const signature = req.headers["x-signature"];
  if (!signature) {
    return res.status(400).json({ result_code: "PUB1004", message: "서명이 없습니다" });
  }
  const requestData = req.method === "GET" ? req.query : req.body
  const payload = JSON.stringify(requestData);
  const computedSignature = crypto.createHmac("sha256", SECRET_KEY).update(payload).digest("base64");
  if (signature !== computedSignature) {
    return res.status(400).json({ result_code: "PUB1004", message: "서명이 유효하지 않습니다" });
  }
  next();
};

// 구매 가능 여부 체크(아이템 확보 대응)
app.get("/check", verifySignature, (req, res) => {
    const { game, user, item, price, transaction_id } = req.query;
    if (!game || !user || !item || !price || !transaction_id || isNaN(price)) {
        return res.status(400).json({ result_code: "PUB2001", message: "필수 파라미터 부족 또는 유효하지 않은 값" });
    }
    res.json({
        request_id: "abc123",
        timestamp: new Date().toISOString(),
        result_code: "PUB0000",
        message: "구매 가능",
        purchasable: "available",
        age_category: "under_18",
        account_limit: { total: 10000, remaining: 5000 },
        requested_price: Number(price),
        transaction_id: transaction_id
    });
});

// 구매 등록
app.post("/register", verifySignature, (req, res) => {
    try {
        const { game, user, item, transaction_id, item_name, price, quantity } = req.body;
        if (!game || !user || !item || !transaction_id || !item_name || !price || !quantity || isNaN(price) || isNaN(quantity)) {
            return res.status(400).json({ result_code: "PUB2001", message: "필수 파라미터 부족 또는 유효하지 않은 값" });
        }
        res.json({
            request_id: "def456",
            timestamp: new Date().toISOString(),
            result_code: "PUB0000",
            message: "구매 실적을 등록했습니다.",
            item_granted: true,
            transaction_id: transaction_id
        });
    } catch (error) {
        res.status(500).json({ result_code: "PUB5000", message: "내부 서버 오류" });
    }
});

app.listen(3000, () => console.log("Server running on port 3000"));

1.3 Python(FastAPI)으로 구현

from fastapi import FastAPI, Request, HTTPException, Header, Depends
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import hmac
import hashlib
import base64
import json
from datetime import datetime
from typing import Optional
from urllib.parse import urlencode

app = FastAPI()

# CORS미들웨어 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

SECRET_KEY = "your_secret_key"
VALID_ACCESS_TOKEN = "your_access_token"

# 요청 모델
class RegisterRequest(BaseModel):
    game: str
    user: str
    item: str
    transaction_id: str
    item_name: str
    price: int
    quantity: int

# 인증 의존성 함수
async def verify_auth(authorization: Optional[str] = Header(None)):
    if not authorization:
        raise HTTPException(
            status_code=401, 
            detail={"result_code": "PUB1001", "message": "인증 정보가 부족합니다"}
        )
    
    if authorization != f"Bearer {VALID_ACCESS_TOKEN}":
        raise HTTPException(
            status_code=401, 
            detail={"result_code": "PUB1002", "message": "유효하지 않은 액세스 토큰입니다"}
        )
    return True

# 서명 검증 의존성 함수(GET 요청용)
async def verify_signature_get(request: Request, x_signature: Optional[str] = Header(None)):
    if not x_signature:
        raise HTTPException(
            status_code=400, 
            detail={"result_code": "PUB1004", "message": "서명이 없습니다"}
        )
    
    # 쿼리 파라미터 가져오기
    query_params = dict(request.query_params)
    payload = urlencode(query_params)
    
    # 서명 계산
    computed_signature = base64.b64encode(
        hmac.new(SECRET_KEY.encode(), payload.encode(), hashlib.sha256).digest()
    ).decode()
    
    if x_signature != computed_signature:
        raise HTTPException(
            status_code=400, 
            detail={"result_code": "PUB1004", "message": "서명이 유효하지 않습니다"}
        )
    return True

# 서명 검증 의존성 함수(POST 요청용)
async def verify_signature_post(request: Request, x_signature: Optional[str] = Header(None)):
    if not x_signature:
        raise HTTPException(
            status_code=400, 
            detail={"result_code": "PUB1004", "message": "서명이 없습니다"}
        )
    
    # 요청 본문 가져오기
    body_bytes = await request.body()
    if not body_bytes:
        raise HTTPException(
            status_code=400, 
            detail={"result_code": "PUB2001", "message": "필수 파라미터 부족 또는 유효하지 않은 값"}
        )
    
    # JSON으로 파싱
    try:
        body_json = json.loads(body_bytes)
        payload = json.dumps(body_json)
    except json.JSONDecodeError:
        raise HTTPException(
            status_code=400, 
            detail={"result_code": "PUB2001", "message": "유효하지 않은 JSON 포맷"}
        )
    
    # 서명 계산
    computed_signature = base64.b64encode(
        hmac.new(SECRET_KEY.encode(), payload.encode(), hashlib.sha256).digest()
    ).decode()
    
    if x_signature != computed_signature:
        raise HTTPException(
            status_code=400, 
            detail={"result_code": "PUB1004", "message": "서명이 유효하지 않습니다"}
        )
    return True

@app.get("/check")
async def check(
    game: str, 
    user: str, 
    item: str, 
    price: int, 
    transaction_id: str,
    _auth: bool = Depends(verify_auth),
    _sig: bool = Depends(verify_signature_get)
):
    return {
        "request_id": "abc123",
        "timestamp": datetime.utcnow().isoformat(),
        "result_code": "PUB0000",
        "message": "구매 가능",
        "purchasable": "available",
        "age_category": "under_18",
        "account_limit": {"total": 10000, "remaining": 5000},
        "requested_price": price,
        "transaction_id": transaction_id
    }

@app.post("/register")
async def register(
    request: RegisterRequest,
    _auth: bool = Depends(verify_auth),
    _sig: bool = Depends(verify_signature_post)
):
    try:
        return {
            "request_id": "def456",
            "timestamp": datetime.utcnow().isoformat(),
            "result_code": "PUB0000",
            "message": "구매 실적을 등록했습니다.",
            "item_granted": True,
            "transaction_id": request.transaction_id
        }
    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail={"result_code": "PUB5000", "message": "내부 서버 오류"}
        )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=3000)

2. Postman을 사용한 테스트(예상 소요 시간: 5분)

Authorization 헤더에 Bearer your_access_token , X-Signature에 산출한 서명을 설정한 후 아래 요청을 시도

※본 페이지의 샘플은 연결 확인용 최소 구성입니다. 실제 요청·응답의 전체 항목은API 사양서를 참조해 주세요.

2.1 GET /check 의 요청 예시

GET http://localhost:3000/check
X-Signature: <SIGNED_DATA>

{
  "game": "game123",
  "user": "user456",
  "item": "item789",
  "transaction_id": "txn_123456",
  "price": 500
}

응답 예시:

{
  "request_id": "abc123",
  "timestamp": "2025-02-27T12:00:00Z",
  "result_code": "PUB0000",
  "message": "구매 가능",
  "purchasable": true,
  "transaction_id": "txn_123456"
}

2.2 POST /register 의 요청 예시

POST http://localhost:3000/register
Content-Type: application/json
X-Signature: <SIGNED_DATA>

{
  "game": "game123",
  "user": "user456",
  "item": "item789",
  "transaction_id": "txn_123456",
  "item_name": "100젬",
  "price": 500,
  "quantity": 1
}

응답 예시:

{
  "request_id": "def456",
  "timestamp": "2025-02-27T12:00:00Z",
  "result_code": "PUB0000",
  "message": "구매 실적을 등록했습니다.",
  "item_granted": true,
  "transaction_id": "txn_123456"
}