Appearance
02 · 货币与钱包系统
0. 文档说明
| 项 | 内容 |
|---|---|
| 版本 | V1.0 |
资金底座。03 充值、04 提现、07 俸禄、08 返水、09 活动、10 任务、11 返佣、12 游戏聚合的任何金额进出都经本系统记账。本篇确立**「一租户一本位币站」**的记账与展示宪法。
一、定位与职责
- 定义平台支持的币种目录 currencyCatalog(法币 / 加密)及其展示符号、位置与精度;
- 定义站型:一租户 = 一「本位币站」,站内全链路单币种、单账户;
- 定义用户的钱包账户模型:本位币单账户、可用/彩金/冻结三个余额桶;
- 定义账变操作枚举(全平台资金进出的规范动作集,13 账本直接继承);
- 定义两个钱包边界换算点(充值锁价折入、提现锁价折出)与「站内不提供币币兑换」;
- 对上层提供统一的记账、余额查询、金额表达接口。
核心产品决策:
- 一站一本位币:每个租户只有一个本位币
baseCurrency,站内自始至终只见本位币符号,站内单账户、无第二币种、无站内换算、只显示本位币。一期两种站型:CNY 站、USDT 站。 - 换算只发生在两个钱包边界(充值 / 提现),均锁价 + 快照 + 审计;站内展示与账本永不换算。
- 一个商户可开多个站服务多国市场(一站一国、无限开站,包网引擎卖点);平台跨站层面是多币种(不同站不同本位币),平台看板 / 跨租户对账「按币种分列」;单站内部为单币种口径。
二、核心概念与数据模型
2.1 币种目录 currencyCatalog(平台级)
平台维护统一币种目录,某站型的本位币与其可收付通道币种均引用它:
| 字段 | 说明 | 示例 |
|---|---|---|
code | 币种代码 | CNY / USDT / ETH / BTC |
kind | FIAT 法币 / CRYPTO 加密 | 决定走法币通道还是链上 |
symbol | 展示符号 | ¥ / USDT / ETH / BTC |
position | 符号位置 | CNY 前缀 ¥8,888.88;USDT/ETH/BTC 后缀 1,688.88 USDT |
decimals | 展示精度 | CNY 2 / USDT 2 / ETH 6 / BTC 8 |
networks | 加密专属:可用链 | TRC-20 / ERC-20(USDT)、Ethereum(ETH)、Bitcoin(BTC) |
icon | 图标资源 | 金 ¥ 徽标 / usdt.svg / eth.svg / btc.svg |
标准精度与符号(一期):
| code | kind | 符号/位置 | decimals | 网络 |
|---|---|---|---|---|
CNY | FIAT | ¥ 前缀 | 2 | — |
USDT | CRYPTO | USDT 后缀 | 2 | TRC-20 / ERC-20 |
ETH | CRYPTO | ETH 后缀 | 6 | Ethereum(ERC-20) |
BTC | CRYPTO | BTC 后缀 | 8 | Bitcoin 主网 |
币种目录属平台级;某站的本位币由
wallet.baseCurrency指定(必 ∈ 目录),可收付的通道币种集由wallet.depositChannelCurrencies/wallet.withdrawChannelCurrencies指定(§四)。
2.2 钱包账户 WalletAccount(本位币单账户核心)
用户在一个站内只有一个钱包账户,币种恒为该站本位币,三个余额桶:
| 字段 | 说明 |
|---|---|
userId / tenantId | 归属(按租户强隔离) |
currency | 恒 = 站的 baseCurrency(单账户,无第二币种) |
available | 可用余额(可投注/可提现) |
bonus | 彩金余额(受打码约束,解锁后转可用) |
locked | 仅提现审核占用(申请提现即从 available 平移至 locked,通过后扣减、拒绝回滚)。投注不产生 locked——投注即时扣减 available,结算再入账 |
version | 乐观锁版本号(§3.6) |
updatedAt | 最后账变时间 |
余额展示口径:界面「余额」默认 = available + bonus(可由租户配置 wallet.balanceFormula 改口径);可用与彩金在钱包详情分列。原型:WBAL(总额)、WCASH(可用)、WBONUS(彩金)。金额恒带本位币符号(§七展示铁律)。
2.3 账变操作枚举(全平台规范,13 账本直接继承)
| 操作码 | 触发方 | 余额桶 | 方向 | 幂等键 |
|---|---|---|---|---|
DEPOSIT_CREDIT | 03 充值(锁价折入本位币) | available | + | 充值单号 |
WITHDRAW_LOCK | 04 提现申请 | available → locked | 平移 | 提现单号 |
WITHDRAW_CONFIRM | 04 审核通过 | locked | − | 提现单号 |
WITHDRAW_ROLLBACK | 04 拒绝/失败 | locked → available | 平移 | 提现单号 |
BET_DEBIT | 12 投注 | available | − | 注单号 |
PAYOUT_CREDIT | 12 结算派彩 | available | + | 注单号+结算序号 |
REWARD_CREDIT | 07/08/09/10/11 领取(目标=可用) | available | + | claimId |
BONUS_GRANT | 07/09/10 发放(目标=彩金) | bonus | + | claimId |
BONUS_UNLOCK | 10 打码引擎(达标) | bonus → available | 平移 | grantId |
BONUS_FORFEIT | 09 违规没收 / 14 风控 | bonus | − | grantId+原因 |
BONUS_EXPIRE | 定时任务(过期) | bonus | − | grantId |
ADJUST_MANUAL | 15 后台人工(双人复核) | 任意 | ± | 工单号 |
规则:
- 一切金额均以本位币记账,每条流水逐笔带
currency(= 本位币,出生币种); - 领取入
available还是bonus由活动配置rewardTarget决定(09/07/08/10 各自声明),对应REWARD_CREDIT/BONUS_GRANT两个操作码; - 所有操作必须带幂等键,库层 UK(
tenant_id,op_code,biz_key) 兜底; - 新增资金动作必须先在本表登记操作码,禁止私造。
2.4 彩金授予 BonusGrant(状态机)
每笔发放到 bonus 的奖励是一条独立授予记录,打码进度按本位币单口径核算(细则归 10 任务打码)。
打码进度权威(全库口径):彩金打码进度的唯一权威账本 = 10 wagering_obligations(
source=BONUS_UNLOCK,10 订阅BetSettled推进);本篇bonus_grants.wagering_progress(§八)为冗余展示快照,以grantId关联,供钱包/彩金详情直读,不作对账权威。达标判定与解锁触发均由 10 执行(下表)。
BONUS_GRANT(claimId)
● ─────────────────► WAGERING(打码中) ──打码达标──► UNLOCKED(已解锁)
│ │ · BONUS_UNLOCK: bonus→available
│ │
到期未达标│ │违规/主动放弃/按配置提现清空
▼ ▼
EXPIRED FORFEITED
· BONUS_EXPIRE · BONUS_FORFEIT| 流转 | 触发 | 账变 |
|---|---|---|
| → WAGERING | 领取发放(rewardTarget=bonus) | BONUS_GRANT +bonus |
| WAGERING → UNLOCKED | 10 判定 wagering_obligations 达标(10 订阅 BetSettled 推进,进度权威 = 10 wagering_obligations) | 10 调 02 执行 BONUS_UNLOCK bonus→available;由 10 发 BonusUnlocked 事件(幂等键 grantId,→ 00 §4.6) |
| WAGERING → EXPIRED | expire_at 到期 | BONUS_EXPIRE −bonus |
| WAGERING → FORFEITED | 风控判违规 / 用户放弃 / 未打码先提现(按租户配置) | BONUS_FORFEIT −bonus |
UNLOCKED为终态、不回退:彩金一旦解锁(资金已放),后续注单BetCancelled不回退该解锁——BetCancelled只冲正返水(08)与成长值growthBase(06),不追回已解锁彩金及随之的可提现资格。防套利靠validTurnover取整(§3.7)+ 风控事后画像(→ 14),不靠回退资金。
2.5 账变流水
任何余额桶变化写一条不可变流水(操作码、幂等键、变动金额、变动后三桶快照),用于对账与「交易记录」展示,归 13 记录与账变,表结构见 §九。
三、钱包模型与边界换算(单账户制)
3.1 站内单币种,永不换算(无汇率铁律)
- 引擎内金额一律
{ amount, currency },currency恒为本位币;站内不出现第二币种、不换算、不比较跨币、不加总跨币; - API 金额一律字符串(禁 float,→ 00 §4.8);前端只做格式化,禁止写死汇率,禁止展示「≈ ¥xxx」参考小字;
- 各类活动奖金、返水、俸禄额都是本位币的数值,单口径直配,无「同基数按汇率折算」问题;
- 无汇率铁律的边界:「站内永不换算」约束的是站内展示与账本(余额/奖励/统计/等级进度)。换算只发生在两个钱包边界(§3.2),边界事件用「该单已锁价」快照——该快照仅作该笔记账/统计,不构成站内可复用汇率,站内不缓存、不复用、不对外展示为币对牌价。
3.2 两个钱包边界换算点(锁价 + 快照 + 审计)
| 边界 | 方向 | 规则 | 归属 |
|---|---|---|---|
| 充值 | 通道币种 → 本位币 | 站可收非本位币通道(CNY 站收 USDT/ETH/BTC;USDT 站收 CNY 等);下单时点锁价折入本位币入账(credited = paid × lockedRate)。通道币种与账户币种解耦(全站型全通道) | 03 充值 |
| 提现 | 本位币 → 通道币种 | 本位币按提现时点锁价折出到目标通道币种;提交前明示到账数,快照进提现单 | 04 提现 |
lockedRate:边界事件发生时点从平台汇率源取的快照汇率,存单据、可审计;订单有效期内以此结算,关单后失效,重新下单重新询价;同币种(通道币种 = 本位币)lockedRate = 1,无换算行。- 账本只记本位币的最终入账/出账结果;逐笔流水带本位币符号。
3.3 站内不提供币币兑换(合规 + 敞口,明确「不做」)
站内不提供钱包互换 / 币币兑换 / 站内换汇任何入口。理由:合规风险 + 汇率敞口。用户改变持币结构只能经「提现到目标币种通道 / 用目标币种通道充值」两个边界完成,均锁价留痕。
3.4 三个余额桶的资金动线
- 充值(锁价折入本位币)、派彩、
rewardTarget=available的领取 → 入available; rewardTarget=bonus的发放 → 入bonus,按 §2.4 状态机走到解锁/没收/过期;- 投注即时扣减
available(BET_DEBIT),结算入账(PAYOUT_CREDIT);不经过 locked; - 提现只动
available:申请即平移入locked,审核通过扣减、拒绝回滚;bonus不可提。
3.5 幂等记账
一切账变携带幂等键(§2.3),重复回调/重复点击不重复记账;库层 UK(tenant_id,op_code,biz_key) 兜底(→ 00 §4.4)。
3.6 并发与负余额
- 每账户
version乐观锁(或按账户串行队列),并发账变冲突即重试; - 任何扣减前校验余额充足,任何余额桶不得为负;不足即拒绝(投注失败/提现驳回),不允许透支。
3.7 精度与舍入
- 存储精度
DECIMAL(32,8)高于展示精度(本位币decimals); - 派发类计算(返水/俸禄/佣金)统一向下取整(截断)到本位币精度,避免超发;差额留存不累计给用户;
- 展示只格式化不改值(不四舍五入进位)。
四、★ 租户可配置项(wallet 命名空间)
jsonc
"wallet": {
"baseCurrency": "CNY", // 站型 = 本位币:站内唯一记账与展示币种(须 ∈ 平台币种目录)
// CNY 站 → "CNY";USDT 站 → "USDT"(一期两种站型)
"balanceFormula": "available+bonus", // 「余额」展示口径(默认 available+bonus)
// ↓ 通道币种集:充值/提现通道对外收付的币种,可 ≠ baseCurrency(边界锁价,→ 03/04)
"depositChannelCurrencies": ["CNY", "USDT", "ETH", "BTC"], // 该站充值可收的通道币种
"withdrawChannelCurrencies": ["CNY", "USDT"], // 该站提现可付的通道币种
"catalogOverride": { // 各币种展示覆盖(可选);icon=币种图标(余额类展示=图标+纯数值,符号身份由图标承担)
"CNY": { "symbol": "¥", "position": "prefix", "decimals": 2, "icon": "coin-cny" },
"USDT": { "symbol": "USDT", "position": "suffix", "decimals": 2, "icon": "usdt", "networks": ["TRC-20","ERC-20"] },
"ETH": { "symbol": "ETH", "position": "suffix", "decimals": 6, "icon": "eth" },
"BTC": { "symbol": "BTC", "position": "suffix", "decimals": 8, "icon": "btc" }
}
}一期样例站型(与 admin 样例一致):
global= CNY 站:baseCurrency:"CNY",收 USDT/ETH/BTC 充值(边界锁价折入 CNY)。sea01= USDT 站:baseCurrency:"USDT";场馆链路 route 由平台超管按申报口径配置(原生 / CNY 映射,商户不可见不可改,→ 12 场馆链路);账本恒记本位币真值。gold88= CNY 站(SETUP)。
Schema 校验(由本系统提供,发布前强制,→ 01 §4.3):baseCurrency ∈ 平台目录;depositChannelCurrencies / withdrawChannelCurrencies ⊆ 平台目录(可含 ≠ baseCurrency 的币种,边界锁价合法);合规层可强制剔除某币种。baseCurrency 运营中途变更属高危(牵动等级 growthBase 重放,→ 06),后台加二次确认 + 平台审批,首版建议锁定不可改。
五、与其他系统的关系 / 接口
| 交互方 | 关系 |
|---|---|
| 03 充值 / 04 提现 | 成功入账 / 锁定-扣减-回滚(§2.3 对应操作码);边界锁价折算(§3.2) |
| 07/08/09/10/11 | 领取派发按 rewardTarget 走 REWARD_CREDIT 或 BONUS_GRANT,均本位币 |
| 10 打码引擎 | 推进 BonusGrant 打码进度,达标触发 BONUS_UNLOCK |
| 12 游戏聚合 | BET_DEBIT / PAYOUT_CREDIT;场馆链路 route 由 12 定义,账本恒记本位币真实值 |
| 13 账变 | 消费本系统全部账变流水 |
| 14 风控 | 入账前置校验;违规触发 BONUS_FORFEIT / 账户冻结 |
| 06 等级 | 订阅 DepositSucceeded(跨币充值按该单 lockedRate 折入 growthBase)/ BetSettled |
| 17 看板 | 平台跨站按币种分列(单站内部单币种) |
对外接口(引擎级):
| 接口 | 调用方 | 幂等键 | 说明 |
|---|---|---|---|
getWallet(userId) | 展示层 /me | — | 本位币单账户三桶余额(字符串金额) |
credit / debit(userId, bucket, amount, opCode, bizKey) | 03/04/07–12/15 | bizKey 必填 | 唯一记账入口(币种恒本位币),写流水+发事件 |
transfer(bucket→bucket)(锁定/解锁/回滚) | 04/10 | 同上 | locked 平移与 bonus 解锁 |
format({amount}) | 各处 | — | 按本位币目录+租户覆盖格式化 |
站内单账户,故无
switchActive之类接口。边界锁价接口(询价/入账/出账)归 03/04。
六、运营后台能力(→ 15 汇总)
| 侧 | 能力 |
|---|---|
| 平台控制台 | 币种目录维护(新增币种/网络/精度);跨站(跨租户)资金总览按币种分列 |
| 商户控制台 | 查用户钱包三桶余额与账变流水;人工调整(发起工单→双人复核→执行 ADJUST_MANUAL,全程留痕);冻结/解冻用户钱包;本站站型(baseCurrency)与通道币种集配置(白名单内,改 wallet.*;baseCurrency 变更走平台红区审批) |
提现审核归 04;对账报表归 13/17。
七、展示契约(→ 原型映射)
展示铁律(用户端 + 后台通用):
- 站内所有金额恒本位币符号;禁止「数字不变只换符号」的渲染(原型
applyCur只格式化本位币,不做符号替换)。 - 已发生金额永远带出生币种(= 本位币),不重标、不换算(逐笔流水带币种)。
- 等级 / 俸禄 / 任务进度 / 签到 / 里程碑:本位币单口径(单币种站天然无歧义,金额与进度同为本位币数值,→ 06)。
- 边界换算的「参考到账/到手」仅在充值/提现页出现,且明示为该单锁价快照(非站内牌价)。
| UI 元素 | 数据 | 原型载体(app.js) | 系统契约 |
|---|---|---|---|
| 顶部余额胶囊 | 本位币余额 + 图标 | renderBalance()、.balance-pill(自管币种,排除 wrapAmounts) | 数据 = /me.wallet + bootstrap.wallet;单账户单币种 |
| 全站金额文本 | {amount} 按本位币渲染 | money(base)=fmtIn(base);wrapAmounts 包 ¥ 为 .cur-amt[data-base] | 接口返回字符串金额,前端只 format(),不算汇率、不换符号 |
| 我的·钱包区 | 三桶余额/统计,.no-cur 自管 | WBAL/WCASH/WBONUS/WSTAT,renderMe() | /me.wallet 单账户返回 |
| 充值/提现入口 | 按站型的通道币种集切通道 | renderWallet() 按币种通道切法币/加密 | 通道列表来自 03/04 配置(通道币种可 ≠ 本位币,边界锁价) |
关键实现约定(标准化):
.balance-pill、.no-cur排除出wrapAmounts(自管币种,避免被整站渲染逻辑覆写);- 金额渲染幂等,
applyCur时机在 i18n 之后(setTimeout(0)+window load);applyCur只负责格式化本位币,不做符号替换; - 顶栏为单账户单币种形态,无币种选择
▾。
八、数据库设计(全表含 tenant_id;金额 DECIMAL(32,8))
| 表 | 关键字段 | 约束/索引 |
|---|---|---|
currencies(平台级) | code PK, kind, symbol, position, decimals, networks JSON, icon | — |
wallet_accounts | id PK, tenant_id, user_id, currency(= 站本位币), available, bonus, locked, version, updated_at | UK(tenant_id,user_id)(一租户一用户一账户,币种恒本位币) |
wallet_ledger | id PK, tenant_id, user_id, currency, op_code, biz_key, bucket, amount, available_after, bonus_after, locked_after, event_id, remark, created_at | UK(tenant_id,op_code,biz_key);IDX(tenant_id,user_id,created_at) |
bonus_grants | grant_id PK, tenant_id, user_id, currency, source(系统/活动ID), claim_id, amount, wagering_multiple, wagering_required(解锁门槛快照), wagering_progress(冗余展示快照,权威在 10 wagering_obligations,按 grant_id 关联), status(WAGERING/UNLOCKED/EXPIRED/FORFEITED), granted_at, expire_at, finished_at | UK(tenant_id,claim_id);IDX(tenant_id,user_id,status) |
- 站内单账户:
wallet_accounts以(tenant_id,user_id)唯一,currency列冗余存本位币便于流水对齐,不作分桶键; wallet_ledger不可变(只插不改);三个_after快照列用于对账与「交易记录」直读;- 边界锁价的通道币种/
lockedRate存充值单 / 提现单(03/04 表),wallet_ledger只记本位币结果; - 「交易记录/领取记录」等展示由 13 基于
wallet_ledger+ 各业务单据组织。
单账户无选中币种持久化:不设
user_wallet_prefs(last_active_wallet)一类选中币种持久化表——币种恒为本位币。
九、扩展点与开放问题
- 新增币种:平台目录加定义 → 站型可选它作本位币,或加入充/提通道币种集。
- 稳定币多网络:同一 USDT 跨 TRC-20/ERC-20,充提选网络、费率不同,归 03/04。
- 通道币种全景:各通道币种的路由、限额、手续费、锁价询价在 03/04 展开。
- 余额口径:默认
available+bonus;部分站只显可用 ——wallet.balanceFormula控制。 - 未打码先提现的处置:清空彩金(FORFEIT)还是拒绝提现,按租户配置,细则归 04/10。
- 本位币变更:高危,牵动等级
growthBase重放(→ 06),首版锁定;开放后走平台红区审批。
十、验收要点
- [ ] 全链路(接口/前端/DB)搜不到任何站内汇率常量;站内无第二币种符号、无换符号渲染;
- [ ] 钱包账户对
(tenant_id,user_id)唯一(单账户);全站单账户单币种; - [ ] API 金额均为字符串;前端仅格式化,精度按本位币
decimals(CNY2/USDT2/ETH6/BTC8); - [ ] 同一
biz_key重复回调/重复点击只入账一次(UK 生效); - [ ] 投注即扣
available,提现申请入locked、拒绝原额回滚;任何桶不出现负数; - [ ] 彩金达标自动
bonus→available,bonus_grants.status与两条账变(GRANT/UNLOCK)对得上; - [ ] 充值可收 ≠ 本位币通道(锁价折入本位币),提现可付 ≠ 本位币通道(锁价折出);账本恒记本位币,
lockedRate存单据可审计; - [ ] 站内无币币兑换入口;边界快照不作站内牌价复用。