ホーム › 技術仕様 & API
テスト実装ガイド
Game8 Storeと連携し、GET /check(購入可否チェック)および 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
}