阅读主题
支付开关与模拟支付联调
开发者控制台 → 游戏接入 → 支付与资产 → 游戏支付模式。每款游戏选择一种模式,保存后立即生效,不需要发布接入草稿:
| 模式 | 游戏内统一充值入口的行为 |
|---|---|
关闭 disabled | 不接受新下单或待支付模拟单的付款;保留历史订单查询和必要清理 |
正常支付 real | 使用真实通道;商户与适配器尚未配置时返回不可用,不会自动降级成模拟付款 |
模拟支付 sandbox | 仅当前游戏有效测试账号可以创建与支付模拟订单,不收真实资金 |
游戏始终调用同一套 POST /api/orders → POST /api/orders/{id}/pay,下单不指定支付方式,服务端根据当前配置选择。不要再增加游戏自己的正常/模拟支付开关。后续后台切换模式不要求重新接入或更新游戏。
真实支付开关控制接入意愿;真实商户、支付适配器与回调未验收时,即使开启也不能收款。模拟支付默认关闭,只对当前游戏的有效联调账号开放;普通玩家、其他游戏账号及失效测试账号不能模拟付款。已有生产数据库无需切换到演示环境。
1. 准备测试账号与角色
- 在环境管理开启当前游戏联调、登记精确来源并创建测试账号。
- 选择模拟支付模式,使用该账号通过 SDK 登录。可信服务端交换返回
user.testAccount: true,游戏不得相信浏览器自报的测试身份。 - 游戏服务端登记测试区服和角色,再调用
/api/server/roles/bind绑定该角色。商品必须是该游戏已启用的game_item,履约模式为external。
模拟订单不会给正式平台币钱包充值,不发放平台月卡,不使用正式优惠券,也不计入会员成长、真实支付金额或代理佣金。游戏应将模拟资产放在专属测试角色、测试服或独立模拟余额中,不能给正式角色增加真实资产。
2. 读取实际能力
已登录时,能力查询需要附带该应用的平台玩家会话:
ts
const response = await fetch(`${apiBase}/api/capabilities?appId=${encodeURIComponent(appId)}`, {
headers: { Authorization: `Bearer ${platformSession.token}` }
})
const capabilities = await response.json()
const checkout = capabilities.checkout
const canPay = checkout?.available === true
// checkout.mode: disabled / real / sandbox
// 不可用时展示 checkout.reason,不要仅依据 mode 开启按钮。checkout.available 受支付模式、联调设置、有效会话、账号归属、外部履约和维护状态共同约束。未登录查询始终不能获得模拟付款资格,即使来源是已登记的本地地址。切换账号后重新读取,不能跨账号缓存可用状态;服务端仍会在下单和付款时重新校验。
3. 统一创建订单与付款
以下 HTTP 示例适用于现有游戏及旧 SDK;省略 paymentMethod 与传入 auto 等效,无需重新接入登录或角色体系:
ts
async function post(path, body) {
const response = await fetch(`${apiBase}${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${platformSession.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
const result = await response.json()
if (!response.ok) throw new Error(result.error?.message || '支付请求失败')
return result
}
// 每次用户确认购买创建一组编号;网络重试复用它们。
const createRequestId = crypto.randomUUID()
const payRequestId = crypto.randomUUID()
const order = await post('/api/orders', {
productId: 'your-game-product-id',
roleId: trustedRole.bindingId,
requestId: createRequestId
})
const paid = await post(`/api/orders/${order.id}/pay`, {
requestId: payRequestId
})
// 用 order.sandbox / paid.sandbox 标识模拟订单。
// paid.status === 'paid':已付款,等待游戏服务端确认发货。
// 不能根据点击成功直接在客户端增加道具。每个独立操作生成请求 ID,网络重试复用原 ID。确认付款本身不代表发货成功,游戏回执完成后订单才变为 fulfilled。
模式在创建订单时冻结。 后台切换模式后,旧模拟订单不会变成真实订单;使用相同创建请求 ID 重试仍返回原订单。切出模拟模式后,未支付模拟订单不能继续付款;已成功付款的重试保留成功结果。真实供应商未就绪时返回 PAYMENT_NOT_CONFIGURED,关闭模式返回 CHECKOUT_DISABLED。
旧版显式 demo / wallet 请求保留原兼容行为,但不属于新的后台自动选择路径。现有游戏若写死支付方式,应做一次调整,省略该字段。历史 v0.6.0 下载包不覆盖;其 TypeScript 声明仍要求旧字段,可先使用上述 HTTP 调用,不要通过类型强转伪造支持。
升级时如果旧版已创建订单但响应丢失,新版使用相同玩家、游戏、商品、角色、优惠券和请求 ID,省略方式或传 auto,可认回旧单;原显式方式重试也仍然有效。更改商品或角色等内容仍返回幂等冲突。不要为未知结果重建请求 ID,否则会被视为新购买。
要验证付款失败,调用:
http
POST /api/orders/{orderId}/simulate
Authorization: Bearer <平台玩家会话>
Content-Type: application/json
{"outcome":"failure","requestId":"本次模拟操作的唯一ID"}响应为 {order, outcome: "failure"},订单保持待支付,不扣款、不生成发货事件。outcome: "success" 模拟付款成功;改变结果必须使用新请求 ID。同一个 ID 改为另一结果会返回幂等冲突,旧失败请求重试仍返回原失败快照;最新订单状态使用 GET /api/orders/{orderId} 查询。
取消使用 POST /api/orders/{orderId}/close;未付款订单按原有 30 分钟有效期过期。已支付不能直接关闭。
4. 游戏服务端消费测试发货
测试消费者必须明确传入 sandbox: true:
http
POST /api/server/deliveries/claim
X-API-Key: <仅服务端保存的应用密钥>
Content-Type: application/json
{"appId":"当前应用AppID","limit":10,"sandbox":true}该请求只领取 sandbox: true 的模拟事件。未传 sandbox 或传 false 的旧消费者只领取非模拟事件,避免历史代码把测试单当作真实付款。
隔离联调副本应停用自动轮询,并在领取时增加 orderId: "本次测试订单ID",只消费自己这次创建的订单。orderId 不放宽应用密钥、游戏归属、模拟队列及租约校验;找不到符合条件的事件返回空列表,不会领取别的订单。正式服务统一轮询时可省略该字段。
游戏服务端须核对事件的 sandbox、目标区服、角色、商品以及订单,先按事件 ID 幂等发放至测试资产,再携带租约调用 /api/server/deliveries/{eventId}/ack。失败使用 /fail,重试沿用原事件 ID,不能重复到账。测试标记须写入游戏订单、回执和资产记录,去重内容也必须包含该标记。
5. 模拟退款及关闭
已履约模拟订单可用 sdk.refundOrder(order.id, requestId) 发起测试回收。订单进入 refund_pending,生成 sandbox: true, action: "revoke" 事件;游戏回收测试资产并确认后变为 refunded。这不是向微信或支付宝退款。
关闭模拟支付后,停止新建和未付款订单的确认。已成功操作的幂等查询仍可使用,已付款事件继续允许领取和确认,已有模拟订单仍可按权限退款清理。到期测试账号不能继续使用会话,管理员可处理遗留模拟订单。
配置接口
GET /api/developer/applications/{gameId}/payment-settings 查询配置;PUT 保存 {mode, version, requestId},mode 为 disabled、real 或 sandbox。需接入配置权限,切入或切出模拟模式还需本地联调权限。管理员对应路径为 /api/admin/applications/{gameId}/payment-settings。
配置按游戏隔离、记录审计并校验版本。响应返回 mode、兼容布尔字段及实际 real.available、sandbox.available。旧双开关请求继续接受;历史两项同时开启时以模拟模式为准。新控制台仅保存一种模式。游戏使用带玩家会话的 capabilities.checkout 判断当前玩家的实际能力。