钱包兑换码
APay 内置钱包兑换码能力(redeem_codes 与 redeem_redemptions),用户兑换后直接充入钱包 cash(充值余额)池。
- 记账币种强一致:兑换码币种固定为站点记账币种(
getTopupRules().accountingCurrency,读取settings.currency)。生成与兑换阶段若币种与站点记账币种不一致,均以 HTTP409拒绝,防止跨币种 1:1 误入账。 - 并发闸门与幂等键:
redeem_redemptions.code_id唯一键作为并发抢占闸门(一码一兑);redeem_redemptions.event_id(redeem:<codeId>)作为入账幂等键;(batch_id, user_id)唯一键保证同一非空批次每位用户限兑一次。 - 钱包后端路由:本地钱包模式(
APAY_WALLET_BACKEND=local)经changeBalance(actionType: 'redeem',balanceType: 'cash')入账;外部钱包模式(APAY_WALLET_BACKEND=ainode)经主题walletAdapter(transaction.credit,kind: 'redeem')入账。
用户兑换接口(POST /api/users/redeem)
需用户登录会话,按用户限流每分钟最多 10 次。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 兑换码字符串(服务端自动去空白并转大写) |
{
"code": "RC-2345-6789-ABCD"
}
响应示例
{
"code": 0,
"message": "兑换成功,已到账 10 USD",
"data": {
"amount": 10,
"currency": "USD",
"balance": 25.5,
"alreadyProcessed": false
}
}
若同一用户重复提交本人已完成兑换的兑换码,服务端将直接短路返回 alreadyProcessed: true,不会再次触发下游钱包入账请求。
后台管理接口
受管理员权限 redeem(redeem:view / redeem:edit)保护。
查询兑换码列表(GET /api/admin/redeem-codes)
查询参数:page、pageSize(最大 200)、status(all | active | used | disabled)、batchId、keyword。
列表查询自动左连接权威占用表 redeem_redemptions 对齐展示状态、使用者与使用时间。
批量生成兑换码(POST /api/admin/redeem-codes)
需 redeem:edit 权限。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
count | number | 是 | 生成数量(1–1000) |
amount | number | 是 | 单张面值(> 0,单位为站点记账币种) |
currency | string | 否 | 可选;若传入则必须与站点记账币种一致 |
batchId | string | 否 | 批次号(可选,同一批次每人限兑一次) |
expiresAt | string | 否 | 有效期(可选,无时区时间按站点 settings.timezone 解析为 UTC) |
remark | string | 否 | 备注(可选,最长 200 字符) |
作废兑换码(DELETE /api/admin/redeem-codes/:id)
需 redeem:edit 权限。仅允许作废未使用(active)的兑换码;若 redeem_redemptions 已存在占用记录或状态为 used,返回 HTTP 409。