Game8 Store – Developer Center
ホーム › 技術仕様 & API

テスト実装ガイド

Game8 Storeと連携し、GET /check(購入可否チェック)および 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"
}