后台充值订单实付口径、发放泥点列、用户累计充值与兑换码单位
Project CI / AI game creator shell Rust smoke (push) Successful in 2m17s
Project CI / Backend tests (push) Successful in 4m59s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m29s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m40s
Project CI / Frontend tests (push) Successful in 1m59s
Project CI / AI game creator shell Rust crates (push) Successful in 9m16s
Project CI / AI game creator shell web tests (push) Successful in 1m41s
Project CI / Repository checks (push) Successful in 4m4s
Project CI / Native shell tests (push) Successful in 7m1s

- 新增 AdminRechargeOrderEntryPayload.paidAmountCents:只有 paid_at 存在的订单才有实付,未支付 / 已关闭 / 已过期固定为 0
- 充值管理列表把「金额 / 泥点」拆成「实付」「发放泥点」两列,未支付行实付显示「未支付」并附订单金额小字;退款面板「订单实付」改读同一字段
- 用户详情充值订单表新增「发放泥点」列,商品列只保留商品名,实付同样按 paidAmountCents 展示
- 用户详情新增 cumulativeRechargedCents:api-server 按 user_id 读取 profile_recharge_order,只累加 paid_at 存在的订单金额(退款不回减),单次上限 500 行,读取失败或命中上限返回 null
- 用户详情身份区新增「累计充值」,读不到时显示「未知」,不用用户详情最多 20 条订单在 BFF 或前端近似重算
- 兑换码奖励单位收口为泥点:输入标签与列表列头改「奖励泥点」,单元格带「泥点」单位,避免被当成元
- 新增后端 2 条累计充值口径单测与前端 2 条用例(未支付不显示实付且发放泥点独立成列、累计充值未知态)
- 同步后端架构数据契约(实付口径 / 累计充值来源与上限 / 兑换码奖励单位)与决策记录
- 验证:cargo check -p api-server;cargo test -p api-server --bin api-server admin(138 passed / 0 failed / 1 ignored);npm run admin-web:typecheck;npx vitest run apps/admin-web/src(220 passed);cargo fmt --all --check、check:encoding、check:doc-index、git diff --check 通过
This commit is contained in:
kdletters
2026-09-23 20:10:20 +08:00
parent 0dbd279dfc
commit 1b05a1d05e
11 changed files with 219 additions and 16 deletions
@@ -9277,3 +9277,14 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 边界(本次不改):用户详情「历史花费」(`profile_wallet_consumption_total` 投影与手动对账)维持既有「退款不冲减」决策,仍只累计负向消费流水;若要改成净额,必须单独走投影语义 + 对账口径变更,不能顺手改这一处。充值退款追回、余额重置、赠送和 hold 继续不计入消耗。
- 影响范围:`server-rs/crates/api-server/src/admin.rs`、`server-rs/crates/shared-contracts/src/admin.rs`、`apps/admin-web/src/api/adminApiTypes.ts`、`apps/admin-web/src/pages/AdminDashboardPage.tsx`、对应用例与 `docs/technical/【后台管理】Dashboard运营看板方案-2026-06-23.md`。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml --bin api-server dashboard_consumption`(新增 4 条净额用例全绿);`cargo test -p api-server --manifest-path server-rs/Cargo.toml --bin api-server admin`(136 passed / 0 failed / 1 ignored);`npm run admin-web:typecheck`;`npx vitest run apps/admin-web/src`(218 passed);`npm run check:encoding`、`git diff --check`。
## 2026-09-23 后台充值订单实付口径、发放泥点列、用户累计充值与兑换码单位
- 背景:充值管理列表与用户详情把订单金额当实付展示,未支付订单也显示非 0 实付;发放泥点挤在「金额 / 泥点」一格或商品列小字里;用户详情看不到该用户累计充值额度;兑换码页的奖励数字没有单位,运营无法判断是元还是泥点。
- 决策(实付只有支付过的订单才有):`AdminRechargeOrderEntryPayload` 新增 `paidAmountCents`,由 api-server 按订单 `paid_at` 是否存在判定——存在才等于订单金额,未支付 / 已关闭 / 已过期固定为 0。后台前端实付列显示 `未支付`(并附订单金额小字),退款面板「订单实付」读同一字段;订单金额 `amountCents` 不再被当作实付。
- 决策(发放泥点独立成列):充值管理列表的表头由「金额 / 泥点」拆成「实付」与「发放泥点」两列,用户详情充值订单表同样新增「发放泥点」列,商品列只保留商品名;未支付订单发放为 0 泥点,与实付口径一致。
- 决策(累计充值由后端算):用户详情新增 `cumulativeRechargedCents`,api-server 按 `user_id` 读取 `profile_recharge_order`、只累加 `paid_at` 存在的订单金额(退款不回减),单次读取上限 500 行;读取失败或命中上限返回 `null`,前端显示「读取失败」,不用用户详情最多 20 条订单在 BFF 或前端近似重算。此次只新增 BFF 字段,未改 SpacetimeDB 表结构与 procedure。
- 决策(兑换码奖励是泥点):兑换码 `rewardPoints` 是奖励泥点(兑换成功按 `redeem_code_reward` 流水进钱包),后台输入标签改为「奖励泥点」、列表列头与单元格都带「泥点」单位。
- 影响范围:`server-rs/crates/api-server/src/{admin.rs,admin_recharge.rs}`、`server-rs/crates/shared-contracts/src/admin.rs`、`apps/admin-web/src/api/adminApiTypes.ts`、`apps/admin-web/src/pages/{AdminRechargeOrderPage.tsx,AdminRedeemCodePage.tsx}`、`apps/admin-web/src/components/AdminUserDetailDialog.tsx`、对应三个用例文件与后端架构数据契约文档。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml --bin api-server cumulative_recharge`(2 条新用例)与 `cargo check -p api-server`;`npm run admin-web:typecheck`;`npx vitest run apps/admin-web/src`(220 passed),其中三个定向文件 33 passed(新增「未支付订单不显示实付金额,发放泥点单独成列」与「累计充值读取不到时展示未知,不用订单列表近似」)。
- 边界(未验证):未连真实生产库核对历史订单的累计充值数值,也未跑真实栈 API smoke。
@@ -176,7 +176,7 @@ npm run check:server-rs-ddd
22. 后台主动退款只支持 `wechat_mp`、`wechat_jsapi`、`wechat_h5`、`wechat_native` 普通 V3 泥点订单。`api-server` 必须先按正式支付渠道和商品类型拦截不支持的订单,再做微信支付订单查单预检,然后调用 SpacetimeDB procedure 原子创建退款 hold;只有 hold 成功才允许调用微信退款。`wechat_mp_virtual`、历史非正式渠道值、会员、未支付、对账未完成、退款已满额、人工冻结、退款欠账或永久泥点不足必须在调用普通 V3 provider 前 fail-closed。
23. `profile_recharge_refund_hold` 以稳定 `out_refund_no` 为主键,保存订单、用户、本次退款金额、占用永久泥点、管理员、原因和 `active / settled / released` 状态。重试只有在订单、`out_refund_no`、退款金额、管理员和归一化原因全部与原 hold 一致时才可复用,任一不一致都按幂等内容冲突 fail-closed,不能用新原因调用微信后保留旧审计。部分退款的 hold 在累计应追回增量之外额外保留 1 泥点并发舍入缓冲,全额退款不加缓冲;活动 hold 不改变钱包总额,但普通钱包消费必须预留全部活动 hold;成功退款 observation 扣款并结算匹配 hold,关闭退款释放 hold,外部退款追回不得消耗其他活动 hold。
24. 退款欠账继续以 `profile_recharge_order_refund_settlement.unrecovered_points` 为唯一真相;不新增平行 debt 累计。`profile_wallet_manual_restriction` 只保存人工冻结,普通消费同时检查人工冻结与退款欠账。后续永久泥点到账后继续偿还欠账,每日免费与会员周期泥点不参与;解除人工冻结不得清除退款欠账限制。
25. 管理员充值订单、用户详情、历史花费手动对账、退款预检/执行、应急退款号登记、退款人工复核和钱包冻结接口只留在 `api-server` 管理员鉴权路由。用户详情中的历史花费泥点数读取 `profile_wallet_consumption_total` 投影;已有投影时,每次 `asset_operation_consume` 负向流水落账在同一事务内按主键 O(1) 原子累加,退款不回减,充值退款追回、余额重置、赠送和 hold 均不计入。首次上线必须在停止业务写入的维护窗口内,由 owner 调用 `POST /admin/api/profile/users/initialize-consumption-projections`,一次扫描全部权威钱包流水,为每个已有钱包流水的用户初始化存量投影;接口成功后才能恢复流量。维护遗漏或新用户缺行时,首次消费按该用户索引一次性重建(当前消费流水已经在同一事务中,不能重复加本次金额);钱包详情首次读取也保留同一按用户兜底。`POST /admin/api/profile/users/reconcile-consumption` 是显式手动对账入口:owner 始终可用,member 必须单独持有 `profile-wallet-consumption-reconcile` 操作权限,任何一级 Tab 都不自动附带;用户详情只在后端返回 `canReconcileConsumption=true` 时展示按钮。操作经二次确认后扫描该用户全部权威钱包流水、比较并校准投影,同时记录管理员和对账时间。退款人工复核 BFF 必须从管理员会话写入操作人,要求非空原因,返回微信退款交易号、订单总额以及获批错误码等正式审计字段,并调用 runtime service identity 受限 procedure;后台确认面板必须展示这些后端事实,前端不得自行改 settlement、钱包冻结或消费累计。外部微信副作用由 `platform-wechat` 执行,退款/hold/钱包事务留在 `spacetime-module`,后台前端只展示 BFF 返回的正式状态。
25. 管理员充值订单、用户详情、历史花费手动对账、退款预检/执行、应急退款号登记、退款人工复核和钱包冻结接口只留在 `api-server` 管理员鉴权路由。用户详情中的历史花费泥点数读取 `profile_wallet_consumption_total` 投影;已有投影时,每次 `asset_operation_consume` 负向流水落账在同一事务内按主键 O(1) 原子累加,退款不回减,充值退款追回、余额重置、赠送和 hold 均不计入。首次上线必须在停止业务写入的维护窗口内,由 owner 调用 `POST /admin/api/profile/users/initialize-consumption-projections`,一次扫描全部权威钱包流水,为每个已有钱包流水的用户初始化存量投影;接口成功后才能恢复流量。维护遗漏或新用户缺行时,首次消费按该用户索引一次性重建(当前消费流水已经在同一事务中,不能重复加本次金额);钱包详情首次读取也保留同一按用户兜底。`POST /admin/api/profile/users/reconcile-consumption` 是显式手动对账入口:owner 始终可用,member 必须单独持有 `profile-wallet-consumption-reconcile` 操作权限,任何一级 Tab 都不自动附带;用户详情只在后端返回 `canReconcileConsumption=true` 时展示按钮。操作经二次确认后扫描该用户全部权威钱包流水、比较并校准投影,同时记录管理员和对账时间。退款人工复核 BFF 必须从管理员会话写入操作人,要求非空原因,返回微信退款交易号、订单总额以及获批错误码等正式审计字段,并调用 runtime service identity 受限 procedure;后台确认面板必须展示这些后端事实,前端不得自行改 settlement、钱包冻结或消费累计。外部微信副作用由 `platform-wechat` 执行,退款/hold/钱包事务留在 `spacetime-module`,后台前端只展示 BFF 返回的正式状态。充值订单实付金额(`paidAmountCents`)同样由后端判定:只有 `paid_at` 存在的订单才有实付,未支付、已关闭、已过期订单固定为 `0`,后台前端不得拿订单金额(`amountCents`)顶替实付;订单发放泥点(`pointsDelta`)必须与实付分开成列展示。用户详情的累计充值金额(`cumulativeRechargedCents`)由 api-server 按 `user_id` 受控读取 `profile_recharge_order`、只累加 `paid_at` 存在的订单 `amount_cents`(退款不回减)得出;读取失败或命中单次读取上限时返回 `null`,前端按未知展示,不得用用户详情最多 20 条订单在 BFF 或前端近似重算。
## 用户钱包与编辑器生成扣费契约
@@ -767,7 +767,7 @@ Responses 的终态载荷既是工具调用的恢复源,也是正文的恢复
- Rust 结构体:`ProfileRedeemCode`
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
- 生效时间:复用邀请码时间窗口语义,`starts_at` / `expires_at` 均可为空;两者同时存在时必须满足 `starts_at < expires_at`。开始时刻计入有效区间,截止时刻不计入有效区间。用户兑换时以后端接收的 `redeemed_at_micros` 判定:未到开始时间拒绝为“兑换码未生效”,到达或超过截止时间拒绝为“兑换码已过期”。
- 后台契约:`AdminUpsertProfileRedeemCodeRequest` 通过可空 `startsAt` / `expiresAt` 接收 RFC3339 时间,列表与保存响应同步返回这两个字段;后台页只负责输入、回填和显示,真正兑换判定留在后端事务路径。
- 后台契约:`AdminUpsertProfileRedeemCodeRequest` 通过可空 `startsAt` / `expiresAt` 接收 RFC3339 时间,列表与保存响应同步返回这两个字段;后台页只负责输入、回填和显示,真正兑换判定留在后端事务路径。`reward_points` 是奖励泥点(兑换成功后按 `redeem_code_reward` 流水进入泥点钱包),不是金额;后台奖励输入与列表必须带泥点单位。
### `profile_redeem_code_usage`