openapi: 3.0.3 # 契约 #1 API | 模块:trade(game-module-trade,Wave2 变现域「分账/结算/提现」)| owner:WS5 主笔,全员 review # 职责(架构 Doc B):把多源收入按规则分账、归集、结算、对账;钱包打款/提现门槛与打款执行属 trade(不属 pay)。不做支付收单(归 pay)。 # MVP 钱财闭环(决策 D3):广告收入 → trade 分账(创作者得 80%)→ 创作者收益账户 → 提现(满 5 元)。会员订阅/支付收单=P1,本轮不做。 # 配置化(决策 D3,全配置走 Nacos/DB,严禁硬编码): # - trade.creator-share = 0.80(MVP 全档统一 80% 分账;分层结构 creator_share_tier 保留、值后期填,见 V7.0.0 注释) # - trade.withdraw-min = 500(单位:分,满 5 元可提现) # 上述为 Nacos 配置项,本契约不建死值;接口内 ≥withdraw-min 校验读 Nacos。 # 上游 Feign 依赖:消费 ad 模块 AdRevenueApi(com.wanxiang.huijing.game.module.ad.api.AdRevenueApi)—— # getUnsettledRevenue(statDate) 拉未结算广告收入、markSettled(revenueIds) 回标已结算(seam 契约详见 ad.yaml x-feign-contracts)。 # trade 侧 T+1 SettlementJob 据此分账入账;跨模块只依赖 ad 的 -api,禁依赖 ad 的 -server。 # 端:/app-api(产品端 game-studio,创作者,用户 Token + DataPermission 只见自己) /admin-api(管理端 game-admin,RBAC);前缀由 huijing 框架按 controller.app/admin 包名自动添加 # 错误码段:trade = 1-106-***-***(本模块独占,禁止与他模块重叠:100 project/101 aigc/102 runtime/103 feed/104 telemetry/111 ad) # 响应统一 Huijing CommonResult 信封:{ code, data, msg };code=0 成功 # 资金严谨:金额一律用「分」(BIGINT),禁浮点;比例用 DECIMAL;所有资金写入有幂等键(biz_no/source_ref + uk);状态机用 tinyint,非法流转服务端拒绝。 # 与契约#5 对齐:不重造 income_settled 事件(那是 telemetry 分析埋点 props:amount/source);本模块落账走 game_trade_income,income_settled 仅为埋点镜像。 info: title: 绘境AI trade 模块 API version: 1.0.0 description: >- 分账/结算/提现域。消费上游 ad 未结算广告收入 → 按 creator_share(Nacos 0.80) T+1 分账入创作者收益账户 → 创作者发起提现(满 withdraw-min 分)→ 管理端审核 → mock 打款。 收益账户为流水物化汇总(余额变更必有流水);MVP 全 mock 打款,状态机/幂等/对账为真实接入留位。 前端据此 vite-plugin-mock 自动生成 mock。 servers: - url: http://localhost:48080 description: 本地(Swagger/Knife4j http://localhost:48080/doc.html) paths: # =========================================================================== # 产品端 /app-api(创作者,用户 Token + DataPermission:只见自己账户/流水/提现) # =========================================================================== /app-api/trade/account/mine: get: tags: [app-trade] summary: 我的收益账户(T-TRD-01;余额/累计收益/累计提现/冻结中,金额单位分) description: >- 返回当前创作者收益账户(game_trade_account,DataPermission 限本人 user_id)。 账户不存在时返回零值账户(首次有收益或首次查询时由服务端按需初始化,余额全 0)。 金额字段一律单位「分」。 responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultTradeAccount' } /app-api/trade/income/page: get: tags: [app-trade] summary: 我的收益流水分页(T-TRD-02;source=ad/tip、gross/net/share_rate、结算日) description: 返回当前创作者收益流水(game_trade_income,DataPermission 限本人)。供创作者查看每笔分账明细与对账。 parameters: - { name: source, in: query, required: false, schema: { type: integer, enum: [1, 2] }, description: '收入来源过滤:1广告 2打赏' } - { name: settleDateStart, in: query, required: false, schema: { type: string, format: date }, description: 结算日起(yyyy-MM-dd) } - { name: settleDateEnd, in: query, required: false, schema: { type: string, format: date }, description: 结算日止(yyyy-MM-dd) } - { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } } - { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } } responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultIncomePage' } /app-api/trade/my/income-summary: get: tags: [app-trade] summary: 我的收益汇总(T-TRD-09;看板「收益」转真,聚合已结算收入,金额单位分) description: >- 返回当前创作者的收益汇总(聚合 game_trade_income,DataPermission 限本人 user_id=getLoginUserId())。 供产品端创作者看板「收益」卡片由桩转真:累计实得净额(Σnet_amount)、累计原始收入(Σgross_amount)、 累计入账笔数;不含未结算广告收入(那在 ad 台账 settle_status=0,未分账入账即不计)。 归属隔离硬边界:聚合谓词强制 user_id=当前登录创作者,禁前端传入 userId。 responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultIncomeSummary' } /app-api/trade/withdraw/apply: post: tags: [app-trade] summary: 发起提现(T-TRD-03;幂等键=bizNo,校验 ≥withdraw-min 且 ≤可提现余额,冻结余额) description: >- 创作者发起提现申请。服务端校验: 1) amount ≥ Nacos trade.withdraw-min(满 5 元=500 分,否则 1-106-002-001 未达提现门槛); 2) amount ≤ game_trade_account.balance 可提现余额(否则 1-106-002-002 余额不足); 3) bizNo 幂等(对应 game_trade_withdraw.uk_biz_no,重复提交同一 bizNo 返回已有申请、不重复冻结,1-106-002-003 为非幂等的并发冲突时抛)。 校验通过:原子地从 balance 扣减并加到 frozen(冻结中=提现处理中),落 game_trade_withdraw(status=0 待审核),返回提现单 ID。 余额变更必有流水/可追溯:冻结即提现申请记录本身,打款/驳回时再调整 frozen 与 total_withdraw。 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/WithdrawApplyReqVO' } responses: '200': description: 返回新建提现申请 ID content: application/json: schema: { $ref: '#/components/schemas/CommonResultLong' } /app-api/trade/withdraw/page: get: tags: [app-trade] summary: 我的提现记录分页(T-TRD-04;状态机/金额/审核驳回理由) description: 返回当前创作者提现申请记录(game_trade_withdraw,DataPermission 限本人)。 parameters: - { name: status, in: query, required: false, schema: { type: integer, enum: [0, 1, 2, 3, 4] }, description: '提现状态过滤(见 status 状态机)' } - { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } } - { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } } responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultWithdrawPage' } # =========================================================================== # 管理端 /admin-api(运营/管理员 RBAC):提现审核 + 收益流水查询 + 平台营收报表 # =========================================================================== /admin-api/trade/withdraw/page: get: tags: [admin-trade] summary: 提现审核队列分页(T-TRD-05;跨创作者,按状态/用户筛选) description: 管理端查看提现申请(默认取 status=0 待审核),用于审核处理。 parameters: - { name: status, in: query, required: false, schema: { type: integer, enum: [0, 1, 2, 3, 4] }, description: '默认取 status=0 待审核' } - { name: userId, in: query, required: false, schema: { type: integer, format: int64 }, description: 按创作者用户 ID 筛选 } - { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } } - { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } } responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultWithdrawPage' } /admin-api/trade/withdraw/audit: post: tags: [admin-trade] summary: 提现审核(T-TRD-06;通过/驳回,驱动状态机;非法流转服务端拒绝) description: >- 管理端审核提现申请,驱动状态机(合法流转见 components.x-state-machines): - 通过(decision=1):status 0→1(审核通过打款中),随即经打款渠道(Nacos trade.payout-channel)发起转账。 【M4 真实化】转账「发起」与「终态」解耦:审核事务内仅置 1(打款中)并发起转账单(异步), 资金终态由打款回调驱动——回调成功 1→2(frozen→total_withdraw)、失败 1→4(打款失败,frozen 退回 balance)。 渠道=mock 时同步直通:发起即视为成功,但仍走 status CAS(1→2)置终态,不抄近路直接置 2。 渠道=wxpay/alipay 时委托 pay 模块 PayTransferApi.createTransfer 发起企业付款,等 pay 异步 notify 回调驱动终态。 红线:真实渠道注入失败时 fail-fast 报错挂起提现单(status 留 1),严禁静默降级到 mock 假打款吞真钱。 - 驳回(decision=2):status 0→3(驳回),把冻结金额 frozen 退回 balance(资金原路退回,必填 rejectReason)。 仅 status=0 待审核可审核;非法流转(如已打款再审核)服务端拒绝(1-106-003-001 非法状态流转)。 幂等:以提现单 id + 当前 status 乐观校验,重复审核同一单不二次发钱/二次退款。 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/WithdrawAuditReqVO' } responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultBoolean' } /admin-api/trade/withdraw/payout-notify: post: tags: [admin-trade] summary: 打款回调(T-TRD-10;驱动提现终态 1→2/1→4;服务端内部回调,非前端入口) description: >- 【M4 真实化】打款异步终态回调入口。打款渠道(mock 同步直通 / wxpay/alipay 经 pay notify)完成后调用本端点驱动终态: - success=true:status CAS 1→2(已打款),账户 frozen→total_withdraw(settlePaid); - success=false:status CAS 1→4(打款失败),账户 frozen→balance(refundFrozen,可由创作者重新发起新单)。 幂等:以提现单 id + 当前 status=1 乐观 CAS(重复回调命中 0 行即跳过,不二次发钱/二次退款); 透传 transferRef(pay 转账单号)落库供对账。仅服务端内部/可信边界调用(mock 渠道本进程直调、真实渠道由 pay notify 转发),不暴露给 app 端。 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/WithdrawPayoutNotifyReqVO' } responses: '200': description: 成功(含幂等命中——重复回调返回 true 不重复处理) content: application/json: schema: { $ref: '#/components/schemas/CommonResultBoolean' } /admin-api/trade/income/page: get: tags: [admin-trade] summary: 收益流水查询分页(T-TRD-07;跨创作者对账,按来源/创作者/结算日筛选,只读) description: >- 管理端查看全量收益流水(game_trade_income),供平台对账。 对账锚点:source_ref(广告收入=game_ad_revenue.id)+ trace_id 与 ad 侧台账对齐。 parameters: - { name: userId, in: query, required: false, schema: { type: integer, format: int64 }, description: 按创作者用户 ID 筛选 } - { name: source, in: query, required: false, schema: { type: integer, enum: [1, 2] }, description: '收入来源:1广告 2打赏' } - { name: settleDate, in: query, required: false, schema: { type: string, format: date }, description: 结算日(yyyy-MM-dd)筛选 } - { name: pageNo, in: query, required: false, schema: { type: integer, default: 1 } } - { name: pageSize, in: query, required: false, schema: { type: integer, default: 10 } } responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultIncomePage' } /admin-api/trade/report/revenue: get: tags: [admin-trade] summary: 平台营收报表聚合(T-TRD-08;按日聚合 gross/net/平台分成/提现,只读) description: >- 平台营收报表(只读聚合):按结算日聚合收益流水的原始收入(gross)、创作者实得(net)、平台分成(gross-net), 及提现申请的打款总额。供运营看平台收入与分账结构。 parameters: - { name: statDateStart, in: query, required: true, schema: { type: string, format: date }, description: 统计起始日(yyyy-MM-dd) } - { name: statDateEnd, in: query, required: true, schema: { type: string, format: date }, description: 统计结束日(yyyy-MM-dd) } responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultRevenueReport' } # =========================================================================== # U2 经济:admin 赋余额 / admin 赋订阅(admin 端,RBAC) # =========================================================================== /admin-api/trade/account/grant: post: tags: [admin-trade] summary: admin 赋余额(U2;运营手动给创作者账户充值,real 入账+流水可对账) description: >- 运营手动给创作者收益账户赋余额(活动奖励/客诉补偿等)。 - 幂等:以 bizNo 命中 game_trade_grant.uk_biz_no 去重——同一 bizNo 重复提交返回已有流水 ID(不重复入账、不重复发钱); - 流水佐证:赋余额「不」走 game_trade_income(避免污染营收报表 gross/net 聚合),单独记 game_trade_grant 流水可对账; - 账户:余额原子增(balance += amount、total_income += amount,维持不变式 balance+frozen+total_withdraw=total_income); - 操作人:取自 token(getLoginUserId()),落 game_trade_grant.operator_user_id(审计可溯,非前端入参); - 校验:amount 必须 > 0(赋余额只增不减,1-106-005-000)。 RBAC:@ss.hasPermission('trade:account:grant')。返回赋余额流水 ID。 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/GrantBalanceReqVO' } responses: '200': description: 成功(含幂等命中——重复 bizNo 返回已有流水 ID) content: application/json: schema: { $ref: '#/components/schemas/CommonResultLong' } /admin-api/trade/subscription/grant: post: tags: [admin-trade] summary: admin 赋订阅(U2;运营/B 端手动开通会员,三档套餐 + 续期叠加) description: >- 运营/B 端手动给用户开通会员订阅(MVP 不接真实支付收单,归 pay 日历闸门;本轮 admin 手动赋)。 - 套餐 plan:1 月度 / 2 季度 / 3 年度(仅标识权益档位,到期时间由 durationDays 决定;非法 1-106-006-000); - 续期叠加:已有未过期订阅则新到期 = max(now, 旧 expire) + durationDays 天(不丢未用时长);已过期/无记录从 now 起算; - 幂等:以 bizNo 内联在订阅行 last_grant_biz_no——同一 bizNo 重复提交不重复延长时长(重试安全); - 一用户一行(game_trade_subscription.uk_user),upsert 同一行(CAS 防并发续期 lost-update); - 操作人取自 token,落 operator_user_id(审计可溯);durationDays 必须 > 0(1-106-006-001)。 RBAC:@ss.hasPermission('trade:subscription:grant')。返回赋值后到期时间(expireTime)。 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/GrantSubscriptionReqVO' } responses: '200': description: 成功(返回最新到期时间;同 bizNo 幂等返回当前到期时间不重复延长) content: application/json: schema: { $ref: '#/components/schemas/CommonResultLocalDateTime' } # =========================================================================== # U2 经济:C 端查订阅态(app 端,用户 Token,归属隔离) # =========================================================================== /app-api/trade/subscription/mine: get: tags: [app-trade] summary: 我的会员订阅态(U2;是否有效/套餐/到期时间) description: >- 返回当前用户会员订阅态(game_trade_subscription,DataPermission 限本人 user_id)。 有效性以 expire_time>now 实时判定(active=true 即会员权益开启,effectiveStatus=0 生效中/1 已过期)—— 不依赖 cron 物化过期也能正确反映过期态。从未订阅时返回空态(subscribed=false、active=false),不返 null。 responses: '200': description: 成功 content: application/json: schema: { $ref: '#/components/schemas/CommonResultSubscription' } components: schemas: # ---- Huijing CommonResult 信封 ---- CommonResultBoolean: type: object properties: code: { type: integer, description: '0=成功,非0=错误码 1-106-***-***' } data: { type: boolean } msg: { type: string } CommonResultLong: type: object properties: code: { type: integer } data: { type: integer, format: int64, description: 新建提现申请 ID } msg: { type: string } CommonResultTradeAccount: type: object properties: code: { type: integer } data: { $ref: '#/components/schemas/TradeAccountRespVO' } msg: { type: string } CommonResultIncomePage: type: object properties: code: { type: integer } data: type: object properties: list: { type: array, items: { $ref: '#/components/schemas/IncomeRespVO' } } total: { type: integer, format: int64 } msg: { type: string } CommonResultWithdrawPage: type: object properties: code: { type: integer } data: type: object properties: list: { type: array, items: { $ref: '#/components/schemas/WithdrawRespVO' } } total: { type: integer, format: int64 } msg: { type: string } CommonResultRevenueReport: type: object properties: code: { type: integer } data: { $ref: '#/components/schemas/RevenueReportVO' } msg: { type: string } CommonResultLocalDateTime: type: object properties: code: { type: integer } data: { type: string, format: date-time, description: '赋订阅后到期时间(expireTime)' } msg: { type: string } CommonResultSubscription: type: object properties: code: { type: integer } data: { $ref: '#/components/schemas/SubscriptionRespVO' } msg: { type: string } # ---- 请求 VO ---- WithdrawApplyReqVO: type: object description: 发起提现入参。bizNo 为幂等键,amount 单位分。 required: [amount, bizNo] properties: amount: { type: integer, format: int64, minimum: 1, description: '提现金额(单位:分)。服务端校验 ≥Nacos trade.withdraw-min(500=5元) 且 ≤可提现余额 balance' } bizNo: { type: string, minLength: 1, description: '提现业务单号 = 幂等键(前端生成唯一,对应 game_trade_withdraw.uk_biz_no;重复提交返回已有申请、不重复冻结)' } remark: { type: string, maxLength: 255, description: 备注(可空) } WithdrawAuditReqVO: type: object description: 提现审核入参(管理端)。decision 驱动状态机;驳回必填 rejectReason。 required: [id, decision] properties: id: { type: integer, format: int64, description: 提现申请 ID } decision: { type: integer, enum: [1, 2], description: '审核决策:1通过(0→1→mock 打款 2) 2驳回(0→3,冻结退回余额)' } rejectReason: { type: string, maxLength: 255, description: '驳回理由(decision=2 必填,回填创作者)' } remark: { type: string, maxLength: 255, description: 审核备注(可空) } GrantBalanceReqVO: type: object description: admin 赋余额入参(U2,管理端)。bizNo 幂等键;amount 单位分、只增不减。操作人取自 token,非入参。 required: [userId, amount, bizNo] properties: userId: { type: integer, format: int64, description: 被赋余额的创作者用户 ID } amount: { type: integer, format: int64, minimum: 1, description: '赋余额金额(单位:分,只增不减,>0)' } bizNo: { type: string, minLength: 1, description: '赋余额业务单号 = 幂等键(调用端生成唯一,对应 game_trade_grant.uk_biz_no;重复提交返回已有流水、不重复入账)' } remark: { type: string, maxLength: 255, description: '赋余额备注(赋值原因,可空)' } GrantSubscriptionReqVO: type: object description: admin 赋订阅入参(U2,管理端)。bizNo 幂等键(同 bizNo 不重复延长);plan 套餐;durationDays 续期叠加。操作人取自 token,非入参。 required: [userId, plan, durationDays, bizNo] properties: userId: { type: integer, format: int64, description: 订阅用户 ID } plan: { type: integer, enum: [1, 2, 3], description: '订阅套餐:1 月度 / 2 季度 / 3 年度' } durationDays: { type: integer, minimum: 1, description: '赋订阅时长(天,续期叠加,>0)' } bizNo: { type: string, minLength: 1, description: '赋订阅业务单号 = 幂等键(调用端生成唯一;同 bizNo 不重复延长时长,重试安全)' } remark: { type: string, maxLength: 255, description: '订阅备注(开通原因,可空)' } # ---- 响应 VO ---- TradeAccountRespVO: type: object description: >- 创作者收益账户(game_trade_account 物化汇总)。金额一律单位「分」。 约束:balance/frozen/total_income/total_withdraw 由分账入账与提现流程维护,余额变更必有对应流水/单据。 properties: userId: { type: integer, format: int64, description: 创作者用户 ID } balance: { type: integer, format: int64, description: '可提现余额(单位:分)' } frozen: { type: integer, format: int64, description: '冻结中(提现处理中占用,单位:分)' } totalIncome: { type: integer, format: int64, description: '累计入账(= 分账入账 + admin 赋余额之和,单位:分;U2 起含 admin 赋余额,维持 balance+frozen+total_withdraw 恒等式)' } totalWithdraw: { type: integer, format: int64, description: '累计已提现(已打款成功总额,单位:分)' } updateTime: { type: string, format: date-time } IncomeRespVO: type: object description: >- 收益流水(game_trade_income,分账后入账)。net = gross × shareRate(创作者实得)。 对账锚点:source + sourceRef 唯一(广告=ad + game_ad_revenue.id),与 ad 台账对齐。 properties: id: { type: integer, format: int64, description: 收益流水 ID } userId: { type: integer, format: int64, description: 创作者用户 ID } source: { type: integer, enum: [1, 2], description: '收入来源:1广告(ad) 2打赏(tip)' } sourceRef: { type: string, description: '来源单号(对账锚点):广告=game_ad_revenue.id;打赏=打赏单 id。与 source 组成唯一键防重复分账' } grossAmount: { type: integer, format: int64, description: '原始收入(分账前,单位:分)' } shareRate: { type: number, format: double, description: '分账比例(DECIMAL,如 0.8000=创作者得 80%;来自 Nacos trade.creator-share 快照)' } netAmount: { type: integer, format: int64, description: '创作者实得 = gross × shareRate(单位:分;落账时定格,不随配置变动)' } settleDate: { type: string, format: date, description: 'T+1 结算日(yyyy-MM-dd)' } traceId: { type: string, description: '全链路 traceId(广告来源透传自 game_ad_revenue.trace_id,供对账)' } createTime: { type: string, format: date-time } WithdrawRespVO: type: object description: 提现申请(game_trade_withdraw)。status 状态机见 x-state-machines;金额单位分。 properties: id: { type: integer, format: int64, description: 提现申请 ID } userId: { type: integer, format: int64, description: 创作者用户 ID } amount: { type: integer, format: int64, description: '提现金额(单位:分)' } status: { type: integer, enum: [0, 1, 2, 3, 4], description: '状态机:0待审核 1审核通过打款中 2已打款 3驳回 4打款失败' } bizNo: { type: string, description: '提现业务单号(幂等键)' } channel: { type: string, description: '打款渠道(MVP=mock;真实 later 接 pay 渠道)' } applyTime: { type: string, format: date-time, description: 申请时间 } auditTime: { type: string, format: date-time, description: 审核时间(审核后写入) } auditUserId: { type: integer, format: int64, description: 审核人用户 ID(管理端) } payTime: { type: string, format: date-time, description: 打款时间(打款成功后写入) } rejectReason: { type: string, description: 驳回理由(status=3 时) } remark: { type: string, description: 备注 } transferRef: { type: string, description: '打款转账单号(M4:渠道发起转账后回填,对账锚点;mock 渠道为本地 mock 单号)' } RevenueReportVO: type: object description: 平台营收报表聚合(按统计区间汇总;金额单位分) properties: statDateStart: { type: string, format: date, description: 统计起始日 } statDateEnd: { type: string, format: date, description: 统计结束日 } totalGross: { type: integer, format: int64, description: '区间内原始收入合计(分账前,单位:分)' } totalNet: { type: integer, format: int64, description: '区间内创作者实得合计(单位:分)' } totalPlatform: { type: integer, format: int64, description: '区间内平台分成合计 = totalGross - totalNet(单位:分)' } totalWithdrawPaid: { type: integer, format: int64, description: '区间内已打款提现合计(单位:分)' } # ---- U2 新增:会员订阅态(C 端查) ---- SubscriptionRespVO: type: object description: >- 会员订阅态(game_trade_subscription)。有效性以 expire_time>now 实时判定(active=true 即会员权益开启)—— 不依赖 cron 物化过期。从未订阅时返回空态(subscribed=false、active=false),其余字段为 null。 properties: subscribed: { type: boolean, description: '是否曾订阅过(有订阅记录):false=从未订阅' } active: { type: boolean, description: '是否当前有效(expire_time>now 实时判定):C 端据此开启会员权益' } plan: { type: integer, enum: [1, 2, 3], description: '订阅套餐:1 月度 / 2 季度 / 3 年度(未订阅为 null)' } effectiveStatus: { type: integer, enum: [0, 1], description: '有效状态(按 expire_time 实时回算):0 生效中 / 1 已过期(未订阅为 null)' } startTime: { type: string, format: date-time, description: '本次订阅生效起始时间(未订阅为 null)' } expireTime: { type: string, format: date-time, description: '到期时间(权威字段:expire_time>now 即有效;未订阅为 null)' } # ---- M4 新增:我的收益汇总(T-TRD-09 看板转真) ---- IncomeSummaryVO: type: object description: 创作者收益汇总(聚合 game_trade_income 本人已结算收入;金额单位分) properties: totalNet: { type: integer, format: int64, description: '累计创作者实得净额(Σnet_amount,单位:分)' } totalGross: { type: integer, format: int64, description: '累计原始收入(Σgross_amount,单位:分)' } incomeCount: { type: integer, format: int64, description: '累计入账笔数(已结算分账流水条数)' } CommonResultIncomeSummary: type: object properties: code: { type: integer } data: { $ref: '#/components/schemas/IncomeSummaryVO' } msg: { type: string } # ---- M4 新增:打款回调入参(T-TRD-10 驱动提现终态 1→2/1→4) ---- WithdrawPayoutNotifyReqVO: type: object description: 打款回调入参(服务端内部/可信边界调用;驱动提现终态) required: [withdrawId, success] properties: withdrawId: { type: integer, format: int64, description: '提现单 ID(game_trade_withdraw.id)' } success: { type: boolean, description: 'true=打款成功(1→2);false=打款失败(1→4 退回 balance)' } transferRef: { type: string, description: '渠道转账单号(对账锚点;mock 渠道为本地 mock 单号,可空)' } failReason: { type: string, description: '打款失败原因(success=false 时回填,可空)' } # =========================================================================== # 状态机契约(合法流转;非法流转由 Service 服务端拒绝,DO 层不承载校验) # =========================================================================== x-state-machines: description: trade 模块资金状态机定义,供两侧 agent 实现一致;非法流转一律服务端拒绝(1-106-003-001)。 withdraw: field: 'game_trade_withdraw.status' states: '0': 待审核(apply 落库初始态;冻结金额已从 balance 转入 frozen) '1': 审核通过打款中(审核通过 0→1 后发起转账;mock 渠道同步直通经 CAS 置 2,真实渠道等待 pay 异步回调驱动终态) '2': 已打款(终态成功;1→2:frozen 扣减、total_withdraw 累加) '3': 驳回(终态;0→3:frozen 退回 balance,资金原路退回) '4': 打款失败(终态;1→4:frozen 退回 balance,可由创作者重新发起新单) legalTransitions: - '0 → 1(审核通过)' - '0 → 3(审核驳回,frozen→balance)' - '1 → 2(打款成功,frozen→total_withdraw)' - '1 → 4(打款失败,frozen→balance)' illegalExamples: - '2 → * (已打款为终态,不可再变)' - '3 → * / 4 → * (驳回/失败为终态)' - '0 → 2(未经审核直接打款,拒绝)' adRevenueSettle: field: 'game_ad_revenue.settle_status(ad 侧表,trade 经 AdRevenueApi.markSettled 驱动)' states: '0': 未结算(ad 计费落台账初始态) '1': 已结算(trade 分账入账成功后经 markSettled 回标) legalTransitions: - '0 → 1(trade 分账成功回标,幂等:已是 1 跳过)' note: '该状态位权威表在 ad 模块 game_ad_revenue,trade 只经 Feign markSettled 驱动,不直接写 ad 表' # =========================================================================== # 上游 Feign 依赖(消费 ad/community/pay 模块;跨模块只依赖对方 -api 走 Feign) # =========================================================================== x-feign-dependencies: description: trade 消费其它模块 -api 的 Feign 契约。权威签名见各模块 -api,此处仅声明依赖与用法;跨模块禁依赖对方 -server。 dependsOn: # ① ad:T+1 分账结算(权威 seam 在 ad.yaml x-feign-contracts) - module: ad apiInterface: 'com.wanxiang.huijing.game.module.ad.api.AdRevenueApi' operations: - 'getUnsettledRevenue(LocalDate statDate) → List:拉指定归集日未结算广告收入' - 'markSettled(List revenueIds) → boolean:分账入账成功后回标 ad 台账 settle_status=1(幂等)' usedBy: 'com.wanxiang.huijing.game.module.trade.job.SettlementJob(T+1 结算定时任务)' flow: >- SettlementJob:getUnsettledRevenue(T 日) → 按 creatorUserId 聚合 gross → ×Nacos trade.creator-share(0.80) 得 net → 写 game_trade_income(uk(source, source_ref) 防重) + 累加 game_trade_account.balance → markSettled 回标 ad。 全链路 trace_id(ad→trade) + source_ref(ad.id→trade) 可对账。 补偿:入账成功但 markSettled 失败时,下轮重拉但被 trade uk 拦截重复入账,仅重试回标,不重复发钱。 # ② community:reward 现金发放衔接(权威 seam 在 community.yaml x-feign-contracts;M4 新增) - module: community apiInterface: 'com.wanxiang.huijing.game.module.community.api.CommunityNotifyApi' operations: - 'listPendingCashRewards() → List:拉 status=0 且 reward_type=2(现金) 的待发放奖励' - 'markRewardGranted(Long rewardId, String grantedRef) → boolean:发放入账成功后回写 granted_ref + status 0→1(CAS 幂等)' usedBy: 'com.wanxiang.huijing.game.module.trade.job.RewardPayoutJob(reward 现金发放,M4 新增)' flow: >- RewardPayoutJob:listPendingCashRewards() → 逐条 IncomeService.recordIncome(source=TIP=2, source_ref=reward.id, gross=reward_amount, shareRate=1.0) 入账到创作者账户 → markRewardGranted 回写 granted_ref(=income.id)+status 0→1。 幂等:recordIncome uk_source(2, reward.id) 兜底 + reward.status CAS(0→1),复跑不重复发钱;发放失败 reward 留 status=0 下轮重试。 # ③ pay:真实打款渠道(权威 seam 在 pay-api PayTransferApi;M4 接线,渠道=wxpay/alipay 时启用) - module: pay apiInterface: 'com.wanxiang.huijing.module.pay.api.transfer.PayTransferApi' operations: - 'createTransfer(PayTransferCreateReqDTO) → PayTransferCreateRespDTO:发起企业付款到零钱(异步,merchantTransferId=提现 bizNo 幂等)' - 'getTransfer(Long id) → PayTransferRespDTO:查转账单状态(对账补偿 job 主动补驱动用)' usedBy: 'com.wanxiang.huijing.game.module.trade.framework.payout.WxPayoutClient/AlipayPayoutClient(M4,仅 Nacos trade.payout-channel=wxpay/alipay 时;mock 渠道不依赖 pay)' condition: '仅真实渠道启用;需 huijing-module-pay-server 接入 huijing-server。mock 渠道(默认)走 trade 本地 MockPayoutClient 同步直通,零 pay 依赖。'