文档状态:目标需求基线
基线日期:2026-09-16
适用范围:Commission(代码中的WorkTask)取消、改价、退款债权、退款池、财务决议与运营恢复
详细接口契约:../api-changes/2026-09-09_commission_cancellation_and_refunds.md
本文是产品与业务规则的唯一长期说明。它回答“系统必须提供什么能力、用户看到什么结果、哪些规则不能破坏”,不记录历史实现过程、临时兼容方案或代码重构步骤。
Commission 支持多阶段付款,一张 Commission 可能包含多笔 Order,并混合网关现金、普通 Credit 与 Open Call Commission Credit。不同 Order 还可能使用 Stripe、PayPal、Alipay,以及不同的收款币种和结算路径。
取消或降价后,业务决定可以立即生效,但退款可能需要用户选择去向、等待 Provider、处理关联账户资金回收或进入人工复核。系统因此必须把以下事实分开:
commission-refund-notifications 实现,新增 8 个 SystemNotificationScene 场景与 2 个 WorktaskPageEventListType,模板初始数据由 CommissionRefundNotificationTemplateSeeder 提供(12 行);对外说明见 2026-09-21_commission_refund_notifications.md。fulfilled),不是作废(voided)。该入口本轮不实现,只固化它与 voided 的领域边界;实现时必须记录人工履约凭证。refund-auto-destination 实现,新增可配置等待时限(默认 168 小时)与默认去向优先级,到期按优先级在债权可选去向内自动选定并复用既有退款执行链路;对外说明见 2026-09-22_commission_refund_auto_destination.md。charges.create(source: connected_account) 等具体 Provider 行为与 Alipay 成功响应边界仍需在测试环境用样本验证,未取得证据前不修改判定逻辑。| 参与方 | 能力 |
|---|---|
| 用户 | 发起取消、响应画师取消、发起或响应改价、选择退款去向、查看退款池 |
| 画师 | 发起取消、响应用户取消、发起或响应改价、查看退款和结算进度 |
| 系统 | 计算金额、冻结资金事实、创建退款债权、异步执行、恢复、对账和结算 |
| 运营管理员 | 查询财务对象,在服务端资格校验通过后重试、恢复或对账 |
用户是退款去向的最终决定者。画师发起取消或降价时不能提交退款去向;用户可以在发起自己的请求、接受对方请求或退款池中表达选择。
| 对象 | 职责 |
|---|---|
WorkTask | Commission 合同、履约和支付入口 |
WorkTaskPriceChange | 新旧价格、发起方、审批和补款状态 |
CommissionCancellation | 取消协商及取消编排状态 |
CommissionFinancialResolution | 一次已生效业务决定对应的不可变资金决议 |
CommissionRefund | 平台对客户的一笔退款债权 |
CommissionRefundAllocation | 债权对原 Order 和资金来源的预留与履行事实 |
CommissionRefundStep | Provider、Credit、钱包与资金回收的幂等执行步骤 |
OutboxMessage | 与业务事务同时持久化的异步执行意图 |
一张 Commission 可以同时存在多笔尚未完成的退款债权。每笔债权只占用自己的资金来源,退款池负责汇总,不把多笔债权合成一条可变记录。
业务记录、退款债权和 Outbox 意图必须在同一数据库事务中创建。业务事务提交后,可以用直接派发降低延迟,但恢复能力必须依赖持久化 Outbox,而不是依赖一次队列调用成功。
| 模式 | 条件 | 结果 |
|---|---|---|
direct | Commission 未付款且当前状态允许取消 | 统一通过取消申请接口立即完成,无需对方确认 |
negotiated | Commission 已付款且当前状态允许协商取消 | 创建申请,等待对方接受、拒绝或发起方撤回 |
unavailable | 有支付中 Order、活动改价、活动取消、支付事实不完整或状态不允许 | 禁止发起并返回稳定原因 |
不再提供独立的旧直接取消接口。两种可用模式都使用 cancellation/preview 与 cancellation/request。
awaiting_destination。pending。接受取消后,取消业务事实立即成立。随后创建 Financial Resolution,并按需要创建退款债权和取消结算步骤。
退款失败不会把双方已经接受的取消恢复成待协商;取消对象进入相应处理或失败状态,运营人员只能恢复原决议,不能创建第二次相同资金操作。
| 发起方与方向 | 初始状态 | 对方动作 | 价格生效时点 | 退款或补款 |
|---|---|---|---|---|
| 用户加价 | wait_pay | 无需画师批准 | 补款成功后 | 创建补款 Order |
| 画师加价 | pending | 用户批准 | 补款成功后 | 创建补款 Order |
| 用户降价 | pending | 画师批准 | 批准事务内 | 按需创建退款债权 |
| 画师降价 | paid | 无需用户批准 | 创建事务内 | 按需创建退款债权 |
改价状态 paid 表示新价格已经应用,不表示客户退款已经到账。退款状态只能从关联的 CommissionRefund 查询。
refund_destination,它只是偏好。连续降价按累计目标退款计算,只创建尚未被已完成退款和开放债权预留覆盖的差额:
因此前一笔退款等待选择、执行中或人工复核时,后续降价仍可形成另一笔独立债权,但不能重复使用已被预留的资金。
所有金额使用整数最小单位。业务币种由 Commission 决定,渠道原币和 Credit 原币分别保存在资金来源和执行步骤中。
设:
P:已完成支付的业务金额;F:已实际产生并折算到业务币种的支付手续费;H:已完成退款金额;V:开放退款债权已经预留的金额;R:本次退款金额。剩余标准上限与绝对上限为:
有效上限由整张 Commission 的收款能力决定:
默认建议退款额是标准上限。支付手续费默认由用户承担,因此普通场景不能超过标准上限。只有所有已付款 Order 都是信息完整、没有钱包混付的 Stripe Destination Charge 时,才允许在标准上限与绝对上限之间退款;超出的部分通过 Stripe 关联账户资金调整处理。
平台手续费比例来自画师数据库配置,不写死为 5%。平台费、Open Call 减免、PlatFee Wallet 和画师预计净收入必须沿用 WorkTask 正常结算规则,由服务端统一计算。
客户退款步骤、画师资金回收步骤和平台资金回收步骤必须分开记录。客户已经收到退款后,画师回收或平台对账仍可能失败;此时客户债权是已履行,Financial Resolution 仍可处于处理中或人工复核。
| 去向 | 说明 | 条件 |
|---|---|---|
original | 按原 Order 和原资金来源退款 | 计算完整且 Provider/钱包支持 |
credit | 将本次债权转为用户 Credit | 整张 Commission 不含 Stripe 关联账户 Order,且支付事实完整 |
只要存在 Stripe 关联账户 Order,就不允许退到 Credit。原因是 Stripe Destination Charge 的资金归属和关联账户回收必须保持原渠道事实,不能由平台用 Credit 替代。
多笔待选择债权可以批量选择同一去向,但服务端必须计算这些债权允许去向的交集:
WorkTask 详情返回退款池摘要,用于展示:
退款池只是摘要读模型。逐笔金额、去向、执行状态和步骤必须查询退款列表或详情。
退款必须使用双轴状态:
obligation_status:open、fulfilled、voided;execution_status:awaiting_destination、ready、processing、partially_completed、retryable_failed、manual_review、completed。不提供旧的单轴 status。前端判断客户是否收到退款只看 obligation_status;展示执行进度看 execution_status 和 execution.steps。
所有公开金额统一返回:
同一事实只返回一次。退款债权的正式去向只返回 cash_destination,允许去向只返回 cash_destinations;不提供 destination、退款对象上的 destinations 或扁平金额镜像。
processing 或 partially_completed 的客户退款会阻止继续支付。fulfilled 只表示客户退款已完成;若画师或平台回收未完成,Financial Resolution 不能完成。manual_review,禁止猜测结果或重新发起另一笔退款。request_id 不会重复动钱:同条款成功重放返回已保存结果,条款不同或上一次仍未收尾时返回冲突错误码 22020,要求人工核对该资源。retry_after 是连接级参数,必须大于同一连接上所有 Job 的最大 timeout;恢复扫描的陈旧阈值必须高于 retry_after 与步骤租约。fulfilled 并进入 manual_review。obligation_status = voided + execution_status = not_required,不显示为可重试失败。withdrawn;同一幂等键的合法重放不再因 accept 后去向变化而被判定为不同条款。CommissionRefund.status、CommissionRefund.destination。/work_tasks/cancel 路由和旧 refund:retry-commission 命令。next_retry_at / last_notified_at 的任何契约描述。php artisan migrate 升级,无需 migrate:fresh;无法证明的数据会失败关闭并输出数量与样例 ID。