- 响应示例从 4 个字段补全为完整订单对象(product/金额/手续费/时间等 21 字段) - 备注补充双单号说明:下单后保存 order_no 用于后续链路,client_order_no 仅用于幂等 - 说明 data 透传字段行为
407 lines
17 KiB
TypeScript
407 lines
17 KiB
TypeScript
import type { EndpointSpec, ParamSpec } from './types'
|
||
|
||
// 通用响应体字段说明,所有接口共用。
|
||
export const commonResponseFields: ParamSpec[] = [
|
||
{ name: 'code', type: 'int', required: true, desc: '0 表示成功,非 0 表示失败' },
|
||
{ name: 'message', type: 'string', required: true, desc: '成功为 "ok",失败为错误描述' },
|
||
{ name: 'data', type: 'object', desc: '业务数据,失败时省略' },
|
||
]
|
||
|
||
// 通用错误码。
|
||
export const errorCodes: ParamSpec[] = [
|
||
{ name: '400', type: 'int', desc: '参数错误 / 业务校验失败(余额不足、库存不足、状态非法等)' },
|
||
{ name: '401', type: 'int', desc: '鉴权失败(Key 缺失、签名错误、时间戳过期、Nonce 重复)' },
|
||
{ name: '403', type: 'int', desc: '权限不足(缺少所需 scope 或商户功能未开通)' },
|
||
{ name: '404', type: 'int', desc: '资源不存在(订单号不存在等)' },
|
||
{ name: '500', type: 'int', desc: '服务端内部错误' },
|
||
]
|
||
|
||
// 订单状态与可发货说明。
|
||
export const orderStatusTable: ParamSpec[] = [
|
||
{ name: 'paid', type: 'order_status', desc: '已支付/已扣款,可发货', example: 'can_ship=true' },
|
||
{ name: 'delivering', type: 'order_status', desc: '发货中', example: 'can_ship=false' },
|
||
{ name: 'delivered', type: 'order_status', desc: '已交付', example: 'can_ship=false' },
|
||
{ name: 'ship_failed', type: 'order_status', desc: '发货失败,可重试', example: 'can_ship=true' },
|
||
{ name: 'cancelled', type: 'order_status', desc: '已取消', example: 'can_ship=false' },
|
||
]
|
||
|
||
// 订单对象公共字段说明,下单/查询/取消响应共用。
|
||
const orderFields: ParamSpec[] = [
|
||
{ name: 'order_no', type: 'string', required: true, desc: '平台订单号' },
|
||
{ name: 'client_order_no', type: 'string', required: true, desc: '调用方传入的幂等单号' },
|
||
{ name: 'order_status', type: 'enum', desc: '订单状态,见「状态说明」' },
|
||
{ name: 'can_ship', type: 'bool', desc: '是否允许发货' },
|
||
{ name: 'cannot_ship_reason', type: 'string', desc: 'can_ship=false 时的原因' },
|
||
{ name: 'product.sku', type: 'string', desc: '商品标识,发货以此为准' },
|
||
{ name: 'product.name', type: 'string', desc: '商品展示名' },
|
||
{ name: 'quantity', type: 'int', desc: '数量' },
|
||
{ name: 'base_amount', type: 'int', desc: '商品基础金额(最小货币单位)' },
|
||
{ name: 'fee_type', type: 'enum(rate|fixed)', desc: '手续费类型' },
|
||
{ name: 'service_fee_amount', type: 'int', desc: '手续费金额' },
|
||
{ name: 'amount', type: 'int', desc: '实付总额 = base_amount + service_fee_amount' },
|
||
{ name: 'currency', type: 'string', desc: '货币,默认 POINT' },
|
||
{ name: 'buyer_reference', type: 'string', desc: '买家标识/备注' },
|
||
{ name: 'data', type: 'object', desc: '下单时透传的请求数据(原样回显)' },
|
||
{ name: 'result', type: 'object', desc: '发货平台回调的结果数据(原样回显)' },
|
||
{ name: 'provider_order_no', type: 'string', desc: '发货平台侧单号' },
|
||
{ name: 'failure_reason', type: 'string', desc: '最近一次失败原因' },
|
||
{ name: 'created_at', type: 'datetime', desc: '创建时间' },
|
||
{ name: 'delivered_at', type: 'datetime', desc: '交付时间' },
|
||
{ name: 'cancelled_at', type: 'datetime', desc: '取消时间' },
|
||
]
|
||
|
||
const orderResponseExample = `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"order_no": "FO20260730000123",
|
||
"client_order_no": "shop-10001",
|
||
"order_status": "paid",
|
||
"can_ship": true,
|
||
"cannot_ship_reason": "",
|
||
"product": { "sku": "suit_pink_sheep", "name": "套装-糯粉咩咩" },
|
||
"quantity": 1,
|
||
"base_amount": 100,
|
||
"fee_type": "rate",
|
||
"service_fee_amount": 5,
|
||
"amount": 105,
|
||
"currency": "POINT",
|
||
"buyer_reference": "buyer-001",
|
||
"data": { "server": "ios-wechat", "uid": "player-id" },
|
||
"provider_order_no": "",
|
||
"failure_reason": "",
|
||
"created_at": "2026-07-30T10:00:00+08:00",
|
||
"delivered_at": null,
|
||
"cancelled_at": null
|
||
}
|
||
}`
|
||
|
||
const deliveryOrderResponseExample = `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"order_no": "FO20260730000123",
|
||
"status": "paid",
|
||
"can_ship": true,
|
||
"cannot_ship_reason": "",
|
||
"product": { "name": "套装-糯粉咩咩", "sku": "suit_pink_sheep", "game": "和平精英" },
|
||
"buyer_name": "buyer-001",
|
||
"amount": 105,
|
||
"created_at": "2026-07-30T10:00:00+08:00",
|
||
"shipped_at": null,
|
||
"ship_fail_reason": "",
|
||
"game_channel": "安卓-QQ",
|
||
"game_uid": "4808146277",
|
||
"role_name": "巫师哈丁12",
|
||
"pay_score": 360,
|
||
"good": { "title": "套装-糯粉咩咩", "image": "https://..." }
|
||
}
|
||
}`
|
||
|
||
const deliveryLinkResponseExample = `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"order_no": "FO20260730000123",
|
||
"delivery_url": "https://shop.example/delivery/dlc/FO20260730000123?exp=1753934400&sign=...",
|
||
"expires_at": "2026-07-31T12:00:00+08:00",
|
||
"exp": 1753934400,
|
||
"sign": "..."
|
||
}
|
||
}`
|
||
|
||
const deliveryBindResponseExample = `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"bind_uuid": "bind-123",
|
||
"bind_url": "https://bind.example/qrcode",
|
||
"qr_url": "https://api.qrserver.com/v1/create-qr-code/?size=240x240&data=https%3A%2F%2Fbind.example%2Fqrcode"
|
||
}
|
||
}`
|
||
|
||
const deliverySubmitResponseExample = `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"order_no": "FO20260730000123",
|
||
"status": "delivering",
|
||
"message": "已提交上游发货,等待发货结果回传",
|
||
"provider_order_no": "provider-1",
|
||
"game_account": {
|
||
"game_account": "4808146277",
|
||
"game_account_role_name": "巫师哈丁12"
|
||
},
|
||
"upstream_order": {
|
||
"order_no": "provider-1"
|
||
}
|
||
}
|
||
}`
|
||
|
||
export const endpoints: EndpointSpec[] = [
|
||
{
|
||
key: 'list-products',
|
||
method: 'GET',
|
||
path: '/api/client/v1/products',
|
||
title: '商品列表',
|
||
summary: '查询商户已授权且上架的商品列表',
|
||
scope: 'products:read',
|
||
queryParams: [
|
||
{ name: 'page', type: 'int', desc: '页码,默认 1', example: '1' },
|
||
{ name: 'size', type: 'int', desc: '每页条数,默认 20', example: '20' },
|
||
],
|
||
responseExample: `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"list": [
|
||
{
|
||
"id": 12,
|
||
"sku": "suit_pink_sheep",
|
||
"display_name": "套装-糯粉咩咩",
|
||
"price_amount": 100,
|
||
"cost_amount": 80,
|
||
"currency": "POINT",
|
||
"stock": -1,
|
||
"status": "active",
|
||
"product": { "code": "suit_pink_sheep", "name": "套装-糯粉咩咩", "category": "和平精英" }
|
||
}
|
||
],
|
||
"total": 1,
|
||
"page": 1,
|
||
"size": 20
|
||
}
|
||
}`,
|
||
responseFields: [
|
||
{ name: 'list[].sku', type: 'string', desc: '商品标识,下单时作为 sku 传入' },
|
||
{ name: 'list[].display_name', type: 'string', desc: '商户自定义展示名' },
|
||
{ name: 'list[].price_amount', type: 'int', desc: '单价(最小货币单位)' },
|
||
{ name: 'list[].stock', type: 'int', desc: '库存,-1 表示不限' },
|
||
{ name: 'list[].status', type: 'enum', desc: '商品状态,active 可售' },
|
||
{ name: 'list[].product', type: 'object', desc: '平台商品目录快照' },
|
||
{ name: 'total', type: 'int', desc: '总条数' },
|
||
{ name: 'page', type: 'int', desc: '当前页码' },
|
||
{ name: 'size', type: 'int', desc: '每页条数' },
|
||
],
|
||
},
|
||
{
|
||
key: 'create-order',
|
||
method: 'POST',
|
||
path: '/api/client/v1/orders',
|
||
title: '创建订单',
|
||
summary: '幂等创建订单并扣款(成功返回 201,重复请求返回 200 且 idempotent=true)',
|
||
scope: 'orders:write',
|
||
bodyParams: [
|
||
{ name: 'client_order_no', type: 'string', required: true, desc: '调用方幂等单号,同一商户下唯一', example: 'shop-10001' },
|
||
{ name: 'sku', type: 'string', required: true, desc: '商品标识,取自商品列表', example: 'suit_pink_sheep' },
|
||
{ name: 'quantity', type: 'int', desc: '数量,默认 1', example: '1' },
|
||
{ name: 'buyer_reference', type: 'string', desc: '买家标识/备注', example: 'buyer-001' },
|
||
{ name: 'data', type: 'object', desc: '透传给发货流程的业务数据(区服、账号等),原样存储并回显' },
|
||
],
|
||
requestExample: `{
|
||
"client_order_no": "shop-10001",
|
||
"sku": "suit_pink_sheep",
|
||
"quantity": 1,
|
||
"buyer_reference": "buyer-001",
|
||
"data": { "server": "ios-wechat", "uid": "player-id" }
|
||
}`,
|
||
responseExample: `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"idempotent": false,
|
||
"order": {
|
||
"order_no": "FO20260730000123",
|
||
"client_order_no": "shop-10001",
|
||
"order_status": "paid",
|
||
"can_ship": true,
|
||
"cannot_ship_reason": "",
|
||
"product": { "sku": "suit_pink_sheep", "name": "套装-糯粉咩咩" },
|
||
"quantity": 1,
|
||
"base_amount": 100,
|
||
"fee_type": "rate",
|
||
"service_fee_amount": 5,
|
||
"amount": 105,
|
||
"currency": "POINT",
|
||
"buyer_reference": "buyer-001",
|
||
"data": { "server": "ios-wechat", "uid": "player-id" },
|
||
"provider_order_no": "",
|
||
"failure_reason": "",
|
||
"created_at": "2026-07-30T10:00:00+08:00",
|
||
"delivered_at": null,
|
||
"cancelled_at": null
|
||
}
|
||
}
|
||
}`,
|
||
responseFields: [
|
||
{ name: 'idempotent', type: 'bool', desc: 'true 表示该 client_order_no 已存在,返回既有订单(未重复扣款)' },
|
||
{ name: 'order', type: 'object', desc: '订单完整对象,字段见下方「订单字段说明」' },
|
||
],
|
||
notes: [
|
||
'client_order_no 是唯一幂等单号,请保证同一商户下不重复;重复请求不会重复扣款,返回 idempotent=true 及既有订单。',
|
||
'当前无真实支付流程,下单成功即扣款并进入 order_status=paid,余额不足返回 400。',
|
||
'下单成功后请保存 order_no,后续查询/发货/回调均以 order_no 为准;client_order_no 仅用于幂等。',
|
||
'data 字段会原样存储并回显,可用于透传区服、账号等业务数据。',
|
||
],
|
||
},
|
||
{
|
||
key: 'query-order',
|
||
method: 'GET',
|
||
path: '/api/client/v1/orders/{order_no}',
|
||
title: '查询订单',
|
||
summary: '查询订单状态与详情',
|
||
scope: 'orders:read',
|
||
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
|
||
responseExample: orderResponseExample,
|
||
responseFields: orderFields,
|
||
},
|
||
{
|
||
key: 'cancel-order',
|
||
method: 'POST',
|
||
path: '/api/client/v1/orders/{order_no}/cancel',
|
||
title: '取消订单',
|
||
summary: '取消未发货订单并退款(仅 paid/ship_failed 可取消)',
|
||
scope: 'orders:write',
|
||
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
|
||
bodyParams: [{ name: 'reason', type: 'string', desc: '取消原因(可选)', example: '买家主动取消' }],
|
||
requestExample: `{ "reason": "买家主动取消" }`,
|
||
responseExample: orderResponseExample,
|
||
responseFields: orderFields,
|
||
notes: [
|
||
'取消成功后 order_status 变为 cancelled,金额退回商户钱包。',
|
||
'已进入 delivering/delivered 的订单不可取消,返回 400。',
|
||
],
|
||
},
|
||
{
|
||
key: 'delivery-link',
|
||
method: 'GET',
|
||
path: '/api/client/v1/orders/{order_no}/delivery-link',
|
||
title: '获取发货链接',
|
||
summary: '获取签名发货链接,适合直接跳转平台 Web 发货页',
|
||
scope: 'orders:read 或 shipping:read',
|
||
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
|
||
responseExample: deliveryLinkResponseExample,
|
||
responseFields: [
|
||
{ name: 'order_no', type: 'string', desc: '平台订单号' },
|
||
{ name: 'delivery_url', type: 'string', desc: '可直接打开的发货链接' },
|
||
{ name: 'expires_at', type: 'datetime', desc: '链接过期时间' },
|
||
{ name: 'exp', type: 'int', desc: '过期 Unix 秒时间戳' },
|
||
{ name: 'sign', type: 'string', desc: '签名,供链接校验使用' },
|
||
],
|
||
notes: [
|
||
'适合商户系统只想“返回一个链接”给终端用户的场景。',
|
||
'delivery_url 可以直接给用户打开,sign 仅供后端或调试使用,不建议展示。',
|
||
],
|
||
},
|
||
{
|
||
key: 'delivery-order',
|
||
method: 'GET',
|
||
path: '/api/client/v1/orders/{order_no}/delivery',
|
||
title: '查询发货数据',
|
||
summary: '查询发货页结构化数据,适合商户自建发货界面',
|
||
scope: 'orders:read 或 shipping:read',
|
||
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
|
||
responseExample: deliveryOrderResponseExample,
|
||
responseFields: [
|
||
{ name: 'order_no', type: 'string', desc: '平台订单号' },
|
||
{ name: 'status', type: 'string', desc: '订单状态' },
|
||
{ name: 'can_ship', type: 'bool', desc: '是否可以发货' },
|
||
{ name: 'cannot_ship_reason', type: 'string', desc: '不可发货原因' },
|
||
{ name: 'product', type: 'object', desc: '商品信息' },
|
||
{ name: 'buyer_name', type: 'string', desc: '买家名称 / 备注' },
|
||
{ name: 'amount', type: 'int', desc: '订单金额' },
|
||
{ name: 'created_at', type: 'datetime', desc: '创建时间' },
|
||
{ name: 'shipped_at', type: 'datetime', desc: '发货时间' },
|
||
{ name: 'ship_fail_reason', type: 'string', desc: '最近一次失败原因' },
|
||
{ name: 'game_channel', type: 'string', desc: '区服渠道' },
|
||
{ name: 'game_uid', type: 'string', desc: 'UID / 角色编号' },
|
||
{ name: 'role_name', type: 'string', desc: '角色名' },
|
||
{ name: 'pay_score', type: 'int', desc: '消耗积分' },
|
||
{ name: 'good', type: 'object', desc: '上游商品详情' },
|
||
],
|
||
notes: [
|
||
'该接口返回的是结构化数据,适合商户自有页面渲染。',
|
||
'如果只是想直接给用户一个发货页链接,优先用 delivery-link。',
|
||
],
|
||
},
|
||
{
|
||
key: 'delivery-bind',
|
||
method: 'POST',
|
||
path: '/api/client/v1/orders/{order_no}/delivery/bind',
|
||
title: '发起绑定',
|
||
summary: '发起绑定,返回绑定跳转链接与二维码地址',
|
||
scope: 'orders:write',
|
||
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
|
||
bodyParams: [
|
||
{ name: 'game_account', type: 'string', required: true, desc: '玩家编号 / UID', example: '4808146277' },
|
||
],
|
||
requestExample: `{
|
||
"game_account": "4808146277"
|
||
}`,
|
||
responseExample: deliveryBindResponseExample,
|
||
responseFields: [
|
||
{ name: 'bind_uuid', type: 'string', desc: '绑定凭证,提交发货时使用' },
|
||
{ name: 'bind_url', type: 'string', desc: '绑定跳转地址' },
|
||
{ name: 'qr_url', type: 'string', desc: '绑定二维码地址' },
|
||
],
|
||
notes: [
|
||
'适合商户自建页面先让用户确认 UID,再去拿绑定结果。',
|
||
'同一订单重复绑定时,请以最新返回的绑定凭证为准。',
|
||
],
|
||
},
|
||
{
|
||
key: 'delivery-submit',
|
||
method: 'POST',
|
||
path: '/api/client/v1/orders/{order_no}/delivery/submit',
|
||
title: '提交发货',
|
||
summary: '提交发货到上游并进入发货中状态',
|
||
scope: 'orders:write',
|
||
pathParams: [{ name: 'order_no', type: 'string', required: true, desc: '平台订单号', example: 'FO20260730000123' }],
|
||
bodyParams: [
|
||
{ name: 'game_account', type: 'string', required: true, desc: '玩家编号 / UID', example: '4808146277' },
|
||
{ name: 'bind_uuid', type: 'string', required: true, desc: '绑定凭证', example: 'bind-123' },
|
||
],
|
||
requestExample: `{
|
||
"game_account": "4808146277",
|
||
"bind_uuid": "bind-123"
|
||
}`,
|
||
responseExample: deliverySubmitResponseExample,
|
||
responseFields: [
|
||
{ name: 'order_no', type: 'string', desc: '平台订单号' },
|
||
{ name: 'status', type: 'string', desc: '提交后的订单状态' },
|
||
{ name: 'message', type: 'string', desc: '处理结果说明' },
|
||
{ name: 'provider_order_no', type: 'string', desc: '上游订单号' },
|
||
{ name: 'game_account', type: 'object', desc: '上游绑定结果' },
|
||
{ name: 'upstream_order', type: 'object', desc: '上游创建结果' },
|
||
],
|
||
notes: [
|
||
'同一订单重复提交应直接返回已有结果,不应重复创建上游队列。',
|
||
'提交成功后,最终成功/失败仍以 `ship_notify` 回传结果为准。',
|
||
],
|
||
},
|
||
{
|
||
key: 'get-wallet',
|
||
method: 'GET',
|
||
path: '/api/client/v1/wallet',
|
||
title: '余额查询',
|
||
summary: '查询商户钱包余额',
|
||
scope: 'wallet:read',
|
||
responseExample: `{
|
||
"code": 0,
|
||
"message": "ok",
|
||
"data": {
|
||
"id": 1,
|
||
"merchant_id": 2,
|
||
"currency": "POINT",
|
||
"available_balance": 895,
|
||
"frozen_balance": 0
|
||
}
|
||
}`,
|
||
responseFields: [
|
||
{ name: 'currency', type: 'string', desc: '货币,默认 POINT' },
|
||
{ name: 'available_balance', type: 'int', desc: '可用余额(最小货币单位)' },
|
||
{ name: 'frozen_balance', type: 'int', desc: '冻结余额' },
|
||
],
|
||
},
|
||
]
|