홈 › 기술 사양 & API
테스트 구현 가이드
📝 본 페이지는 참고용 번역(한국어)입니다. 일본어 원본과 차이가 있는 경우 일본어판을 우선합니다.
Game8 Store와 연동하여,API 사양서를 확인하여 필수 API(구매 가능 여부 체크(구매 가능 여부 체크) 및 POST /register(구매 등록)을 간단한 테스트 구현으로 시험해 보는 방법을 소개합니다.
예상 소요 시간:15분
AI 코딩 도구를 사용하시는 경우 AI 에이전트 연동(llms.txt / MCP) 페이지의 순서대로 본 문서를 읽어 들이면 사양을 참조하면서 구현할 수 있습니다.
엔드포인트 구현(예상 소요 시간: 10분)
필요한 환경
- Node.js(Express) 또는 Python(FastAPI) 사용
- 서버를 로컬 또는 클라우드(AWS, GCP, Vercel, Render 등)에 배포할 수 있는 상태
Node.js(Express)로 구현
const express = require("express");
const cors = require("cors");
const crypto = require("crypto");
const app = express();
// 서명 검증을 위해 파싱 전의 요청 본문을 보관
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString("utf8"); } }));
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 검증 미들웨어(GET은 쿼리 문자열, POST은 요청 본문을 그대로 사용)
const verifySignature = (req, res, next) => {
const signature = req.headers["x-signature"];
if (!signature) {
return res.status(400).json({ result_code: "PUB1004", message: "서명이 없습니다" });
}
const payload = req.method === "GET"
? (req.originalUrl.split("?")[1] || "")
: (req.rawBody || "");
const computedSignature = crypto.createHmac("sha256", SECRET_KEY).update(payload).digest("base64");
if (signature !== computedSignature) {
return res.status(400).json({ result_code: "PUB1004", message: "서명이 유효하지 않습니다" });
}
next();
};
const isInt = (v) => v !== undefined && v !== null && Number.isInteger(Number(v));
// 구매 가능 여부 체크(아이템 확보 대응)
app.get("/check", verifySignature, (req, res) => {
const { game, user, transaction_id, item, item_category, price, selling_price, billing_amount } = req.query;
if (!game || !user || !transaction_id || !item || !["paid", "free"].includes(item_category)
|| !isInt(price) || !isInt(selling_price) || !isInt(billing_amount)) {
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, reset_at: null },
stock: null,
requested_price: Number(price)
});
});
// 구매 등록
app.post("/register", verifySignature, (req, res) => {
try {
const { game, user, item, transaction_id, item_category, item_name,
price, selling_price, billing_amount, purchased_at, contents } = req.body;
// ref・payment_method 은 null을 허용하는 필수 항목이므로 키의 유무만 확인
const hasNullable = "ref" in req.body && "payment_method" in req.body;
if (!game || !user || !item || !transaction_id || !["paid", "free"].includes(item_category) || !item_name
|| !isInt(price) || !isInt(selling_price) || !isInt(billing_amount) || !purchased_at || !hasNullable
|| !Array.isArray(contents) || contents.length === 0) {
return res.status(400).json({ result_code: "PUB2001", message: "필수 파라미터 부족 또는 유효하지 않은 값" });
}
// 여기서 contents(content_id / content_name / quantity)를 바탕으로 아이템 지급
res.json({
request_id: "def456",
timestamp: new Date().toISOString(),
result_code: "PUB0000",
message: "구매 실적을 등록했습니다.",
item_granted: true
});
} catch (error) {
res.status(500).json({ result_code: "PUB5000", message: "내부 서버 오류" });
}
});
app.listen(3000, () => console.log("Server running on port 3000"));Python(FastAPI)으로 구현
from fastapi import FastAPI, Request, HTTPException, Header, Depends
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Literal, Optional
from datetime import datetime, timezone
import hmac
import hashlib
import base64
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"
# 오류는 공통 응답 본문(result_code・message) 형식으로 반환
@app.exception_handler(HTTPException)
async def http_error_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=exc.status_code, content=exc.detail)
# 필수 파라미터 누락・형식 오류는 PUB2001로 반환
@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=400,
content={"result_code": "PUB2001", "message": "필수 파라미터 부족 또는 유효하지 않은 값"}
)
# 요청 모델
class Content(BaseModel):
content_id: str
content_name: str
quantity: int
class RegisterRequest(BaseModel):
game: str
user: str
item: str
transaction_id: str
item_category: Literal["paid", "free"]
item_name: str
price: int
selling_price: int
billing_amount: int
purchased_at: str
ref: Optional[str] # null 허용・키는 필수
payment_method: Optional[str] # null 허용・키는 필수
contents: list[Content]
# 인증 의존성 함수
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
def _check_signature(payload: bytes, x_signature: Optional[str]):
if not x_signature:
raise HTTPException(
status_code=400,
detail={"result_code": "PUB1004", "message": "서명이 없습니다"}
)
computed_signature = base64.b64encode(
hmac.new(SECRET_KEY.encode(), payload, hashlib.sha256).digest()
).decode()
if not hmac.compare_digest(x_signature, computed_signature):
raise HTTPException(
status_code=400,
detail={"result_code": "PUB1004", "message": "서명이 유효하지 않습니다"}
)
return True
# 서명 검증(GET): 받은 쿼리 문자열을 그대로 사용
async def verify_signature_get(request: Request, x_signature: Optional[str] = Header(None)):
return _check_signature(request.url.query.encode(), x_signature)
# 서명 검증(POST): 받은 요청 본문을 그대로 사용
async def verify_signature_post(request: Request, x_signature: Optional[str] = Header(None)):
return _check_signature(await request.body(), x_signature)
def now_iso():
return datetime.now(timezone.utc).isoformat()
@app.get("/check")
async def check(
game: str,
user: str,
transaction_id: str,
item: str,
item_category: Literal["paid", "free"],
price: int,
selling_price: int,
billing_amount: int,
_auth: bool = Depends(verify_auth),
_sig: bool = Depends(verify_signature_get)
):
return {
"request_id": "abc123",
"timestamp": now_iso(),
"result_code": "PUB0000",
"message": "구매 가능",
"purchasable": "available",
"age_category": "under_18",
"account_limit": {"total": 10000, "remaining": 5000, "reset_at": None},
"stock": None,
"requested_price": price
}
@app.post("/register")
async def register(
request: RegisterRequest,
_auth: bool = Depends(verify_auth),
_sig: bool = Depends(verify_signature_post)
):
try:
# 여기서 request.contents(content_id / content_name / quantity)를 바탕으로 아이템 지급
return {
"request_id": "def456",
"timestamp": now_iso(),
"result_code": "PUB0000",
"message": "구매 실적을 등록했습니다.",
"item_granted": True
}
except Exception:
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)Postman을 사용한 테스트(예상 소요 시간: 5분)
Authorization 헤더에 Bearer your_access_token , X-Signature에 산출한 서명을 설정한 후 아래 요청을 시도
※본 페이지의 샘플은 연결 확인용 최소 구성입니다. 실제 요청·응답의 전체 항목은API 사양서를 참조해 주세요.
GET /check 의 요청 예시
GET http://localhost:3000/check?game=game123&user=user456&transaction_id=txn_123456&item=item789&item_category=paid&price=500&selling_price=500&billing_amount=500
Authorization: Bearer your_access_token
X-Signature: <SIGNED_DATA>응답 예시:
{
"request_id": "abc123",
"timestamp": "2025-02-27T12:00:00Z",
"result_code": "PUB0000",
"message": "구매 가능",
"purchasable": "available",
"age_category": "under_18",
"account_limit": {
"total": 10000,
"remaining": 5000,
"reset_at": null
},
"stock": null,
"requested_price": 500
}POST /register 의 요청 예시
POST http://localhost:3000/register
Authorization: Bearer your_access_token
Content-Type: application/json
X-Signature: <SIGNED_DATA>
{"game":"game123","user":"user456","item":"item789","transaction_id":"txn_123456","item_category":"paid","item_name":"100젬","price":500,"selling_price":500,"billing_amount":500,"purchased_at":"2025-02-27T12:00:00Z","ref":"game8.jp","payment_method":"Card","contents":[{"content_id":"item789-1","content_name":"젬","quantity":100}]}응답 예시:
{
"request_id": "def456",
"timestamp": "2025-02-27T12:00:00Z",
"result_code": "PUB0000",
"message": "구매 실적을 등록했습니다.",
"item_granted": true
}