Game8 Store – Developer Center
首页 › 技术规格与 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
}