Commission 取消、改价与退款接口文档(2026-09-09 变更)

适用分支:feature/refund-preview-calculation;计算版本 14 面向读者:前端(用户端 + 画师端)、联调、测试 状态:开发阶段契约。退款相关载荷已按「每个事实只出现一次 + 金额一律自包含」重构,不再保留旧扁平镜像字段,也不再保留单轴退款状态等旧兼容字段。

勘误说明(2026-09-28):can_submit 的原描述「等价于试算是否完整」已过时。改价试算(work_task_price_changes/preview)现除计算完整性外,还会因退款政策拒绝而返回 can_submit = false(如 below_minimum_amount、final_refund_must_be_remaining、write_off_cap_exceeded、exchange_rate_unavailable、below_payment_fee),disabled_reason 也新增了这些政策取值;取消试算在计算完整且有可退额度时可用,整笔剩余低于最低退款金额时仍可选择全退(R = M,不受门槛限制)。完整政策字段、取值与示例见 2026-09-26_small_refund_threshold_write_off.md。

更新说明(2026-09-16,第三轮):补充部署与领域不变量。数据库通过正向 migration 2026_09_16_000001 增量收敛到最终 schema:已有测试数据库执行普通 php artisan migrate 即可保留数据并删除旧列,不需要 migrate:fresh;「不兼容旧 API」与「数据库必须可增量部署」是两件事。voided 退款债权是不可执行终态,陈旧执行消息只做 no-op。客户已 fulfilled 时若出现 allocation 完整性异常,客户双轴状态保持不变,异常记录在 Refund 诊断与 Financial Resolution 人工复核(原因码 allocation_integrity_invalid)。详见 §10。

更新说明(2026-09-17,第四轮):修复取消接受与改价终态的并发覆写、补齐终态退款的自动收尾,并把人工财务操作持久化审计。数据库新增 2026_09_17_000001~2026_09_17_000004 四个正向 migration(含删除两个无读者的历史列),已有测试数据库同样只需要普通 php artisan migrate。voided 债权新增专用 execution_status = not_required,不再投影成 retryable_failed;/api/internal 四个受控恢复接口开始把「谁、因何、对哪个资源、结果如何」写入 commission_financial_operation_logs,并新增内部请求冲突错误码 22020。详见 §4.8、§9、§13.2、§13.3。

更新说明(2026-09-16):Commission 取消/改价/退款完成开发阶段的破坏性清理,本文档已按当前代码逐项复核。退款债权不再返回单轴 status、destination 与重复的 destinations:进度只读 obligation_status + execution_status 双轴,去向能力只读 cash_destinations.allowed / cash_destinations.unavailable。同时移除旧 CommissionRefundStatus 枚举、已废弃且不再保留定义的旧退款冲突错误码、POST /work_tasks/cancel 与 POST /api/artist_center/work_tasks/cancel 旧直接取消端点、refund:retry-commission 与 commission-refund:backfill-allocations 命令、以及 Outbox 主题 commission.financial_resolution.recorded。本版不承诺兼容旧 API 字段;数据库结构由正向 migration 增量收敛(见 §10)。

更新说明(2026-09-14):本文已合并退款池、多笔退款债权、财务决议及执行可靠性约定,作为 Commission 取消、改价和退款对接的唯一文档。同日按当前代码复核修正了 refund_destination 必填性、改价 Preview 字段范围、role_view 角色互斥、60001、结算步骤类型与 execution.readiness/blocked_reason 取值等事实。

更新说明(2026-09-15):改价领域补齐专用错误码。重复发起待处理改价改为返回 60003 WorkTaskPriceChangeAlreadyPending;approve/reject/cancel 以及补款完成时的状态冲突改为返回 60004 WorkTaskPriceChangeStateInvalid。改价入口不再返回不含 code 的 HTTP 400,前端按 code 分支即可(见 §9)。

同日补充:refund_pool(WorkTask 详情)与退款列表 summary 新增 action_required_refund_ids,与 bulk_allowed_destinations 是同一集合,可直接作为批量选择去向的 refund_ids;退款池仍是摘要读模型,逐笔明细读 commission_refunds/list(见 §4、§6.5)。

建议阅读顺序:第 1 章(上手)→ 第 4 章(字段字典)→ 第 6 章(你要对接的端点)→ 第 7 章(流程)→ 第 8 章(字段绑定)。第 3 章是金额口径,金额相关问题都源自它。遇到「为什么这个字段是这样」先看第 12 章 FAQ。

目录

章内容什么时候看
1. 一分钟上手三条流程 + 八条铁律刚拿到文档
2. 领域模型与基础约定领域、名词、金额、币种与技术术语需要对齐概念
3. 退款业务规则三层上限、收款资格矩阵、去向、平台费金额算不明白时
4. 统一字段字典每个字段的含义与前端用途写绑定前
5. 端点总览端点清单与通用约定找接口
6. 端点详解每个端点的参数与要点对接具体接口
7. 对接流程流程图、轮询与失败处理串流程
8. UI 字段绑定对照表UI 项 → 字段写页面
9. 错误处理错误码与处置联调排错
10. 开发阶段破坏性变更不承诺兼容 + 正向增量 migration从旧开发环境升级
11. 前端现有绑定迁移对照表逐条旧→新绑定改前端
12. 常见误解(FAQ)高频疑问卡住时
13. 执行可靠性与发布异步恢复、人工审核、部署要求联调与发布
附录 A / B完整示例 / 参数速查抄示例

1. 一分钟上手

1.1 三条流程

1.2 前端必须记住的八条铁律

  1. 退款额输入框上限只用一个字段:financials.refund_limits.maximum。不要用 standard,不要判断 Stripe 场景。
  2. 退款去向只能用返回值:取消 Preview / 取消对象 / 改价试算读 destinations.available,退款债权读 cash_destinations.allowed(被禁用的去向不展示、不置灰,直接不出现在选项里);不要根据支付渠道推断能不能退 Credit。
  3. 每个金额都是 { amount, currency }:包括 role_view 中的估算额与网关/Credit 投影。币种对象自带 is_zero_decimal,前端按它决定小数位,不需要站点币种表。
  4. 按领域提交业务输入:取消领域提交业务币种退款额与去向;改价领域提交新价格,用户降价时可附带去向偏好;退款领域只在债权待选去向时提交去向。金额分摊、手续费、汇率、平台费全部由后端计算。
  5. 业务失败看 code(HTTP 400),字段格式错误是 HTTP 422,权限/不存在是 HTTP 404;不要依赖 message 文案。改价领域同样遵循该约定:重复发起待处理改价是 60003,approve/reject/cancel 等动作的状态冲突是 60004(见 §5.4、§9)。
  6. 提交幂等键自己生成并在重试时复用:idempotency_key;改金额或改去向后必须换新键。
  7. 改价手续费分开绑定:financials.payment_fee 是已付款 Order 的实际手续费;financials.future_payment_fee_estimate 是变更后剩余全部未付款金额的手续费预估,二者不能复用同一字段。
  8. 改价状态与退款状态分开读取:price_change.status 只表示改价协商、待补款或价格已应用;退款是否创建、选好去向或到账,要通过 commission_refund_id 查询退款领域对象。

2. 领域模型与基础约定

本章作用:统一术语与金额表示法,避免同一个数字在两端被理解成不同东西——尤其是「所有金额都是最小单位整数」与「每笔金额自带币种」。

2.1 本文档的称呼约定

称呼指出现在
取消 PreviewPOST …/cancellation/preview 的响应用户端/画师端取消试算
取消对象CommissionCancellation:active_cancellation(摘要)与 cancellation/info(完整)取消流程全程
退款对象CommissionRefund:commission_refunds/list、/info、select_destination 的元素退款执行与查询
改价对象WorkTaskPriceChange:work_task_price_changes/* 的响应元素、WorkTask 详情的 price_change改价流程
业务币种Commission 的结算币种(画师币种)所有业务金额
网关币种实际支付的渠道币种(可能不同,需汇率)role_view.gateway_refunds

2.2 名词

名词说明
Commission对外的“委托/约稿”,后端模型是 WorkTask。
Order一次支付单。一个 Commission 可以有多笔已付 Order(分阶段付款、加价补款)。
Cancellation取消申请,资源字段见 cancellation/info。
WorkTaskPriceChange改价领域的业务对象,记录加价/降价、审批、待补款和价格应用;其状态不表示退款进度。
Financial Resolution财务决议领域的不可变业务财务事实,衔接业务决定与资金义务;它不是退款执行记录。
CommissionRefund退款领域的独立退款债权,取消与降价退款共用;负责去向、预留、执行和履约状态。
Credit用户站内余额(钱包)。原订单可能由 Credit + 网关共同支付。
Connected Account画师的 Stripe 关联账户(destination charge 时收款方)。
业务币种Commission 的结算币种(画师币种),退款额一律用它的最小单位。
支付币种实际支付的网关币种,可能与业务币种不同,需要汇率换算。

2.3 金额与币种

  • 所有金额都是最小货币单位的整数:CNY 12380 = CN123.80;USD 1883 = US$18.83。
  • 金额一律写成 { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },不存在“裸整数 + 同级币种字段”的写法。
  • 展示规则(is_zero_decimal 决定小数位,前端不得按 code 猜):
is_zero_decimal展示例
false金额 ÷ 100,固定两位小数12380 + CN → CN123.80
true直接显示整数12380 + JP¥ → JP¥12380
  • 负数必须保留符号:-200 CNY 显示为 -CN2.00。可能出现负数的字段只有两个(且仅纯 Stripe 关联账户场景):financials.artist_settlement_basis.amount、financials.estimated_artist_net.amount。
  • 需要按币种分列展示的退款额(用户视角的原网关退款、原 Credit 退款)是 Money 列表,例如 role_view.gateway_refunds = [{ "amount": 1883, "currency": { "code": "USD", ... } }],币种信息在元素内,前端不需要额外映射。
  • role_view.estimated_display_amount 是用户偏好币种的估算展示值(Money),不参与任何计算;缺少汇率时整体为 null。

2.4 核心资源对象

Cancellation(取消申请) 有两种返回形态:

形态出现在包含
摘要WorkTask 详情 active_cancellationfinancials、refund_policy、destinations、role_view、can_withdraw/accept/reject、失败信息、created_at/updated_at
完整cancellation/info、request、withdraw、reject、accept 的 data摘要的全部字段 + currency_id、refund(退款对象或 null)、settlement(结算步骤)、accepted_at、completed_at

CommissionRefund(退款债权):id、work_task_id、trigger_type(cancellation|price_change)、trigger_id、obligation_status、execution_status、cash_destination、cash_destinations、preference_applied、currency_id、financials、refund_policy、execution、action_required_at、created_at、completed_at。

  • 本次退款额读 financials.refund(资源不再重复返回一份标量金额)。
  • 一张 Commission 可以同时存在多笔独立退款债权;每笔债权单独预留对应资金,后续取消或降价只创建尚未覆盖的差额。
  • cash_destination 是已经应用的退款去向(尚未选择时为 null);cash_destinations.allowed 是债权创建时冻结的业务允许去向,cash_destinations.unavailable 给出被禁用去向的原因码。退款债权不返回单轴 status、destination,也不返回重复的 destinations。
  • execution 结构:
{
  "attempts": 1,
  "total_steps": 2,
  "succeeded_steps": 1,
  "steps": [
    {
      "type": "stripe_refund",
      "status": "succeeded",
      "financial_effect": "customer_fulfillment"
    }
  ],
  "readiness": "ready",
  "blocked_reason": null,
  "failure_code": null,
  "manual_review_reason": null
}

Refund Pool(退款池摘要) 出现在 WorkTask 详情和退款列表响应中:

{
  "open_count": 2,
  "action_required_count": 1,
  "action_required_refund_ids": [101],
  "financials": {
    "open_amount": {
      "amount": 7200,
      "currency": { "id": 31, "code": "JPY", "symbol": "JP¥", "is_zero_decimal": true }
    }
  },
  "bulk_allowed_destinations": ["original"]
}
  • open_count 用于退款池角标,action_required_count 表示需要用户选择去向的债权数。
  • action_required_refund_ids 是与 bulk_allowed_destinations 同一集合的债权 id,按升序返回:把这些 id 原样作为 refund_ids 提交 commission_refunds/select_destinations,就是用该去向交集做批量选择。它覆盖全部待选择债权,与 commission_refunds/list 的分页无关;空数组表示当前没有需要选择去向的债权。
  • bulk_allowed_destinations 是全部待选择债权的去向交集;空数组是合法值,此时禁用批量入口。
  • 退款池是摘要读模型,不含逐笔明细。渲染每一笔债权的金额、cash_destinations.allowed、execution.readiness 必须读 commission_refunds/list 的 data[](其 summary 与本对象同构)。

Financial Resolution(财务决议摘要) 出现在 WorkTask 详情:id、status、trigger_type、refund_obligations_open、action_required。它表示业务决定产生的资金义务是否已经全部收尾,不等同于客户退款状态。

WorkTaskPriceChange(改价):id、work_task_id、currency_id、initiator_type/initiator_id、approver_type/approver_id、old_price、new_price、need_pay_amount、work_task_paid_amount(以上四个价格/金额都是 Money)、status、approved_at、paid_at、commission_refund_id、refund_destination、financials、refund_policy、destinations、created_at、updated_at。

  • 改价产生的退款额读 financials.refund(加价时为 0)。
  • 改价对象的 financials 比取消/退款对象多一个 future_payment_fee_estimate(Money),表示变更后尚未支付部分的手续费预估。
  • financials.refund、refund_policy 与 destinations 在改价对象中属于改价试算投影:用于说明该改价生效后预计产生多少退款及可填写哪些去向偏好;它们不是退款执行状态。
  • refund_destination 在用户发起降价时是批准前的去向偏好。降价生效后,后端仅在该偏好仍可用时把它应用到退款债权;否则债权进入 awaiting_destination。它不是退款债权 cash_destination 的兼容别名或副本,也不表示退款已经执行。
  • commission_refund_id 是改价领域指向退款领域的关联键。只有降价已生效且实际产生正数退款债权时才有值;前端据此调用退款接口。
  • commission_financial_resolution_id 是改价领域指向财务决议的关联键。它表示该次降价的财务事实已经记录,不表示退款已经完成。

内部字段(refund_calculation_snapshot、refund_calculation_fingerprint、calculation_snapshot、request_idempotency_key、last_error、Provider 明细)永不返回。

2.5 领域与技术术语白话解释

先区分“领域”和“技术设施”:领域表示一类业务事实由谁负责,例如改价领域负责价格是否已经生效,退款领域负责客户是否收到退款;技术设施用于保证这些业务在断网、重复请求或进程重启时仍能可靠执行,本身不是一种新的业务状态。

业务与资金术语

术语所属领域白话解释前端需要做什么
业务决定取消 / 改价领域双方已经确认取消,或者新价格已经应用。这件事一旦成功落库,不会因为后续退款暂时失败而撤销。根据取消或改价对象自己的状态更新业务 UI。
Financial Resolution(财务决议)财务决议领域一张“这次业务决定最终应该怎样动钱”的总任务单。它把退款、画师资金回收、平台对账和结算串在一起。只用摘要状态判断整件财务工作是否收尾;不要把它当作客户退款状态。
退款债权 / CommissionRefund退款领域平台已经确认“欠客户这一笔退款”。每次取消或降价可能产生一笔独立债权。查询金额、选择去向,并通过双轴状态判断客户退款进度。
退款池 / Refund Pool退款领域的查询模型当前 Commission 所有未完成退款债权的汇总视图,不是一笔新的退款。展示数量、待选择数量、合计金额;批量操作只用后端返回的去向交集。
资金来源 / Funding Source支付与退款领域原订单的钱来自哪里,例如 Credit、PayPal、支付宝或 Stripe。一次付款也可能由多个来源组成。不自行拆分;展示后端给出的网关和 Credit 退款明细。
资金预留 / Reservation Ledger退款领域的内部账本债权创建后,先把对应 Order/Credit 的可退额度“占住”,避免下一次降价或取消重复使用同一笔钱。它不是冻结用户银行卡。无直接字段;看到未完成债权仍占用可退额度属于正常结果。
执行计划与步骤退款 / 结算领域后端把一笔财务工作拆成原路退款、退 Credit、回收画师资金等可独立核对的步骤。如需展示进度,读取 execution.steps;不要按支付渠道自行生成步骤。
Provider外部支付领域实际处理收款或退款的第三方,例如 Stripe、PayPal、支付宝。Provider 暂未确认时保持轮询,不要让用户重复发起退款。
对账 / Reconciliation外部支付与财务决议领域后端向 Provider 或内部钱包账本核实“钱是否真的动了、金额是否一致”。接口调用成功不一定等于 Provider 最终入账。processing 可以持续一段时间;以接口状态为准。

后台可靠性术语

术语所属层次白话解释前端需要做什么
Outbox基础设施 / 可靠性和业务记录一起写入数据库的“待办消息”。例如降价已经生效时,同时记下“需要创建并执行退款”的待办,避免业务成功但队列消息因服务重启而丢失。不调用、不展示;只需知道 HTTP 200 后资金流程可能继续异步执行。
Queue Job / Worker基础设施 / 可靠性后台任务和执行它的工作进程。Outbox 会唤醒这些任务去调用 Provider、更新步骤并继续财务决议。按返回状态轮询,不因页面超时重复提交业务请求。
幂等 / IdempotencyAPI 与执行可靠性同一个业务动作即使因网络重试提交多次,也只产生一份业务结果或一笔资金操作。同一次提交重试复用 idempotency_key;业务内容改变后生成新键。
快照 / Snapshot财务计算可靠性在业务决定当时保存参与计算的订单、费率、币种和金额,后续执行按当时事实核对,避免配置变化悄悄改变结果。不读取内部快照;重新 Preview 后只使用最新公开响应。
指纹 / Fingerprint财务计算可靠性对快照关键内容计算的校验值,类似封条。执行前发现订单、金额或预留发生变化时,后端会停止自动动钱。无直接处理;发生冲突时刷新 Preview 或按错误码提示。
依赖 Gate财务决议领域的执行控制一道统一检查:前置退款、对账或资金回收没有满足条件时,后续结算暂不执行。等待状态不是失败;继续读取退款和财务决议状态。
租约 / Lease基础设施 / 并发控制Worker 对一条消息或步骤取得的短时独占处理权,避免两个进程同时执行;进程退出后租约到期,可由其他 Worker 接手。完全内部机制,无需展示。
恢复扫描 / Recovery Sweep基础设施 / 可靠性定时查找长时间没有推进的 Outbox、退款或财务决议,并重新唤醒执行。无需触发;保持正常轮询即可。
Finalizer(收尾器)退款与财务决议的内部服务在所有必要步骤完成后,统一确认债权履约、释放或核销预留,并推进财务决议终态。不直接感知;最终读取 obligation_status、execution_status 和 financial_resolution.status。
manual_review退款 / 财务决议状态自动流程无法安全证明下一步该怎样动钱,系统停止自动尝试并交给人工核对。它不一定表示客户退款失败。停止自动操作,展示处理中或联系支持;客户是否已退款仍看 obligation_status。

可以把整套关系理解为:取消或改价先产生业务决定;Financial Resolution 把决定翻译成资金义务;CommissionRefund 表示其中对客户的退款债权;Outbox、队列、租约和恢复扫描只负责让这些义务可靠地执行下去。


3. 退款业务规则(金额口径)

本章作用:把退款金额是怎么算出来的讲清楚(上限从哪来、什么时候能超上限、能退到哪里、平台费与画师收入如何受影响)。金额相关的疑问基本都能在本章找到答案。

所属领域:退款领域的金额与执行规则。 改价领域只在试算和改价对象中返回这些规则的投影;降价生效并创建 CommissionRefund 后,正式债权金额、去向与执行状态以退款领域对象为准。

为什么需要这一章:退款额不是“已付金额减新价格”。它受 三层上限、收款资格 和 历史退款 共同约束。

3.1 三层退款上限

P = Commission 累计已付业务金额(paid_amount,终身累计,不因退款减少)
F = 累计实际支付手续费(各 Order 支付手续费之和)
R = 历史已完成退款合计
N = 降价后的新稿酬

standard_maximum = max(0, P - F - R)      # 普通场景的硬上限,为支付手续费预留资金
absolute_maximum = max(0, P - R)          # 理论上限,仅纯 Stripe 关联账户场景可达
effective_maximum = 所有有效已付 Order 均为 Stripe 关联账户收款 ? absolute : standard
字段含义前端用途
financials.refund_limits.standard标准上限默认建议额;向双方解释“标准退款范围”
financials.refund_limits.maximum有效上限退款额输入框唯一上限
financials.refund_limits.absolute绝对上限解释纯 Stripe 场景为什么能多退

数值示例(P = CN135.00,F = CN11.20,R = 0):

场景standardmaximumabsolute说明
平台收款(支付宝/PayPal/Stripe 平台收款)123801238013500只能退到 standard;要求更高金额 → HTTP 400 / 22004
纯 Stripe 关联账户收款123801350013500可退到 absolute,超出 standard 的部分 CN11.20 由画师承担
  • 提交金额超过 maximum 一律拒绝(HTTP 400,错误码 22004),前端直接用 maximum 限制输入。
  • 取消 Preview 的 refund_entry.presets 与上限的关系:standard = refund_limits.standard、full = refund_limits.maximum、unfinished_stages = max(0, maximum − 已完成节点金额合计)。

3.2 收款资格矩阵(决定“能否超退”和“能否退 Credit”)

参与判断的是 Commission 全部有效的已付资金 Order:正业务金额、status = paid;已部分或全部退款的历史 Order 仍然参与;零金额改价审计 Order、未支付/已取消 Order 不参与。

一个 Order 属于「Stripe 对 Stripe(关联账户收款)」需同时满足:

payment.channel == stripe
collection_mode == destination_charge
connected_account 信息完整
不存在非 Stripe 的实际资金来源(无 Credit 抵扣)
有效 Order 构成可超过 standard可选择 credit
全部为 Stripe 关联账户收款是否
完全不含 Stripe 关联账户收款否是
两类收款混合否否
存在未知支付或收款信息否否

判断逻辑(结果随 refund_policy 返回,前端不要自己算):

can_exceed_standard  = 全部有效 Order 都是 Stripe 对 Stripe
can_refund_to_credit = 不存在 Stripe 对 Stripe Order 且不存在未知 Order

设计原因:Stripe 关联账户允许结算为负,所以超退部分可以让画师承担;平台内部钱包记为负等于平台先垫付手续费,因此只允许在关联账户场景发生。同理,一旦存在关联账户收款,退款就不能转成 Credit。

3.3 退款去向

目的地行为
original按原资金来源退回:原 Credit 部分回到 Credit,原网关部分退回原支付渠道
credit原 Credit 部分回到 Credit,原网关部分转换为 Credit;仅当 Commission 中不存在 Stripe 对 Stripe Order 时可用
  • 取消 Preview、取消对象与改价试算/改价对象用 destinations.available 作为唯一依据(只渲染其中的选项);destinations.unavailable.credit 给出稳定原因码,用于说明文案与排查,见 §4.4。
  • 退款债权(commission_refunds/*)不返回 destinations,同一能力用 cash_destinations.allowed(可用去向)/ cash_destinations.unavailable(原因码)表达,语义与 destinations 一致。
  • 退款额为 0 时 available / allowed 为空数组,前端不展示去向选择。
  • 资金来源与退款目的地是两件事:订单里有 Credit ≠ 用户选择 credit。

提前去向偏好:refund_destination 在业务决定生效前只是可选偏好,不是正式去向。正式去向只有退款债权的 cash_destination。

场景是否接收 refund_destination语义
用户发起取消可选提前偏好
画师发起取消否画师不能决定客户去向
用户接受画师取消可选接受时提前偏好
画师接受用户取消不新增选择沿用用户偏好
用户发起降价可选提前偏好
画师发起降价否退款进入池后由用户选择
  • 业务生效时必须按当时冻结的 Commission 级 refund_capabilities 重新校验偏好;偏好缺失或已不可用时,取消/改价仍然生效,新债权进入 awaiting_destination。
  • refund_destination 不保证被采用,也不能当作 cash_destination 的第二份事实来源。

3.4 平台费与画师承担额

artist_basis_before_platform_fee = P - F - R - X        # X = 本次退款额
artist_fee_coverage              = max(0, X - standard_maximum)
platform_fee_basis               = max(0, artist_basis_before_platform_fee)
platform_fee                     = 平台费政策(platform_fee_basis)
artist_estimated_net             = artist_basis_before_platform_fee - platform_fee
  • artist_fee_coverage > 0 只可能出现在纯 Stripe 关联账户场景,含义是“超出标准上限、由画师承担的手续费”(前端可用它替代 payment_fee_bearer 判断:大于 0 即画师承担)。
  • artist_estimated_net < 0 也只可能出现在该场景,负数由画师 Connected Account 承接;执行阶段通过关联账户扣款落实,不会产生平台内部钱包负数。
  • 执行顺序:先对原 Connected Account 扣款(步骤 stripe_connected_account_debit),确认成功后再发起用户退款;需要画师补足时使用 stripe_connected_account_credit。对账阶段读取 Provider 账本核对金额:金额不符 → 退款 failed;Provider 尚未结算或数据不可用 → 退款保持 processing 并自动重试。

3.5 资金分配(后端内部,前端无需实现)

  • Credit 与网关现金按各资金来源「下单时锁定的业务币种价值」和「剩余可退款余额」分配。
  • Credit 使用下单时锁定的换算关系反算来源币种金额;网关现金使用退款时渠道适用汇率或 Provider 试算。
  • 整数最小单位的分配误差使用最大余数法处理,合计与目标金额严格一致。
  • 多次退款时每个资金来源不会超过自己的剩余可退款金额;历史退款与本地退款记录不会重复累计。

3.6 改价后的最终结算预测

改价接口会在现有退款试算之外,再按 Commission 正常完成结算规则预测变更后的平台费、后续支付手续费与画师净收入。该预测不会改变退款上限、退款去向或订单退款分摊。

O = 改价前稿酬
N = 改价后稿酬
K = 本次退款后保留金额
C = min(N, O, K)                              # 已覆盖的新稿酬
U = max(0, N - C)                             # 变更后仍未支付的全部金额
E = ceil(U × artist.pay_fee_ratio)            # 后续支付手续费预估
PF = 平台费政策(N, artist.worktask_plat_fee_ratio)
  • financials.payment_fee:已经发生的实际手续费,来自现有已付款 Order;不会用配置费率反推。
  • financials.future_payment_fee_estimate:对 U 的预估。U 包括本次改价需要补缴的金额和以后尚未支付的节点金额,不能只用 need_pay_amount 计算。
  • financials.artist_settlement_basis:改价场景固定为 N,即变更后的 Commission 总稿酬。
  • financials.platform_fee:按 N 计算;费率来自画师数据库配置,不固定为 5%,并继续应用 Open Call 减免和平台费钱包抵扣政策。
  • financials.estimated_artist_net:在当前退款结算结果上加入未付款金额、扣除后续手续费预估,并把平台费调整为按 N 计算后的金额。没有历史退款或手续费转移时,可理解为 N − 实际支付手续费 − 后续支付手续费预估 − 平台费。
  • 后续手续费只是预估;最终结算仍以各 Order 实际产生的手续费为准。
  • 所有计算使用业务币种的整数最小单位;JPY 等零小数币种也不做 / 100。

4. 统一字段字典(对接核心)

取消 Preview、active_cancellation、改价 Preview/创建/列表/详情、WorkTask 详情 price_change 共用 financials、refund_policy、destinations 这三个顶层结构。financials 共用基础字段;改价载荷额外包含 future_payment_fee_estimate。

设计约定:

  • 每个业务事实只出现在一个地方:业务金额只在 financials(退款/取消/改价资源自身不再重复返回标量金额);角色专属的派生信息只在 role_view;UI 录入辅助只在 refund_entry。
  • financials 不按角色裁剪:同一资源的用户端与画师端返回相同结构,包含画师结算相关字段(artist_settlement_basis、platform_fee、estimated_artist_net、artist_fee_coverage)与手续费;是否展示由 UI 决定。改价资源额外包含后续支付手续费预估。需要角色专属信息时读 role_view(画师平台费拆解、用户偏好币种估算与网关/Credit 投影)。

4.1financials

作用:本次(或本次试算)全部业务金额事实的唯一来源——集中回答“已付多少、历史上退了多少、这次退多少、退款后还留多少、上限是多少、手续费/平台费/画师收入各是多少”。所有资源返回相同的基础字段;改价 Preview/创建/列表/详情以及 WorkTask 详情的 price_change 额外返回 future_payment_fee_estimate。同一资源在用户端与画师端内容相同(不做角色裁剪)。

零值语义:取消/退款在未付款或已无可退额度时仍返回基础结构(各金额 0、币种照常)。改价在未付款时,退款相关字段为 0,但 artist_settlement_basis、platform_fee、future_payment_fee_estimate、estimated_artist_net 会按新总价预测,因此可能是非零值。前端不需要处理 null。

字段类型含义前端用途可为负
paidMoney累计已付业务金额 P(终身累计,退款后不回写)展示“客户已支付”否
previous_refundedMoney历史已完成退款合计 R展示“历史已退”否
refundMoney本次业务退款额 X展示/回填「本次退款额」;退款资源取金额也用它否
retainedMoney退款后保留在 Commission 的金额 P − R − X展示“退款后保留”否
refund_limits.standardMoney标准上限 P − F − R默认建议额,对应 UI「标准可退款」否
refund_limits.maximumMoney有效上限(纯 Stripe 关联账户时 = absolute,其余场景 = standard)对应 UI「输入框上限」;唯一上限,超过它提交返回 22004否
refund_limits.absoluteMoney绝对上限 P − R解释纯 Stripe 场景为何可以多退;不要当输入上限否
payment_feeMoney累计已发生的实际支付手续费 F对应 UI「支付手续费」或改价 UI「已产生支付手续费」否
future_payment_fee_estimateMoney仅改价载荷存在;变更后全部未付款金额的手续费预估对应改价 UI「后续支付手续费预估」;不得绑定到 payment_fee否
artist_fee_coverageMoney本次由画师承担的手续费 max(0, X − standard)对应 UI「画师承担手续费」;大于 0 即由画师承担否
artist_settlement_basisMoney取消/退款:退款后的画师结算基数;改价:变更后总稿酬对应 UI「退款后余额(画师)」或改价 UI「变更后稿酬」是
platform_feeMoney取消/退款:退款后平台费;改价:按变更后总稿酬预测的平台费对应 UI「平台服务费」否
estimated_artist_netMoney取消/退款后的画师净收入,或改价后预计最终净收入对应 UI「预计画师净收入」;为负说明由关联账户承接是

约定:

  • 所有 Money 的 currency 都是 Commission 业务币种。
  • 取消 Preview、取消对象和退款对象不返回 future_payment_fee_estimate;该字段只属于改价资源。
  • financials 不含计算快照、指纹、Provider 明细与内部执行步骤。

4.2refund_entry(仅取消 Preview 返回)

作用:退款额输入的录入辅助——给前端提供三个快捷金额与手续费口径说明,本身不承载结算事实(金额事实一律读 financials)。未付款 Preview 同样返回该块(各金额为 0)。

字段类型含义前端用途
presets.unfinished_stagesMoney未完成节点稿酬 = max(0, 有效上限 − 已完成节点金额合计)快捷按钮“退还未完成节点”
presets.standardMoney标准上限(= financials.refund_limits.standard)默认填值 / 快捷按钮“标准退款”
presets.fullMoney有效上限(= financials.refund_limits.maximum)快捷按钮“全额退款”(纯 Stripe 场景可大于 standard)
fee_policystring手续费策略标识,如 payment_fee_standard_cap_then_post_refund_platform_fee仅用于展示/日志,不解析

4.3refund_policy

作用:本次退款能力的判定结果快照——由后端对“Commission 全部有效已付 Order”的收款构成计算得出,用于解释“为什么能/不能超过标准上限、能不能退到 Credit”。前端只消费结论,不要自行判断收款方式。

字段类型含义前端用途
maximum_policystringstandard 或 stripe_connected_absolute(后者才可能超过标准上限)区分“标准可退”与“可超标准退款”的文案/埋点
can_exceed_standardbool是否允许超过 standard(等价于全部有效 Order 都是 Stripe 关联账户收款)对应 UI「是否允许超标准上限」;决定画师承担提示是否展示
can_refund_to_creditbool是否允许退到 Credit参考用;取消/改价的可选值以 destinations.available 为准,退款债权以 cash_destinations.allowed 为准
has_stripe_connected_orderbool是否存在 Stripe 对 Stripe Order诊断与说明文案
all_orders_stripe_connectedbool是否全部有效 Order 都是 Stripe 对 Stripe诊断与说明文案
has_unknown_orderbool是否存在无法确认支付/收款信息的 Order数据异常提示(可引导联系支持)

4.4destinations(仅取消 Preview / 改价能力集合)

适用范围:本节的 destinations.available / destinations.unavailable 只出现在取消 Preview、取消对象与改价试算/改价对象上。退款债权不返回 destinations,其能力集合是 cash_destinations.allowed(可用去向数组)与 cash_destinations.unavailable(原因码对象),取值与语义和本节完全一致。

作用:本次退款的去向集合——告诉前端“钱可以退到哪里”。业务上只展示 available 中的选项:被禁用的去向不渲染为置灰项;unavailable 的原因码仅用于提示文案与排查。退款额为 0 时 available 为空数组,此时不展示去向选择。

全部去向可用时:

{
  "available": ["original", "credit"],
  "unavailable": {}
}

存在被禁用去向时(前端只渲染 available 里的 original):

{
  "available": ["original"],
  "unavailable": { "credit": "stripe_connected_account_present" }
}
字段类型含义前端用途
availablestring[]可提交的去向,取值 original、credit对应 UI「可用退款去向」;只渲染这些选项,提交时原样回传其中一个
unavailableobject映射:键为被禁用的去向,值为稳定原因码供「去向不可用说明」文案与排查使用;不渲染置灰选项
原因码触发条件前端文案建议
stripe_connected_account_presentCommission 存在 Stripe 关联账户收款(destination charge)Order“该委托含 Stripe 关联账户收款,只能原路退回”
refund_payment_data_incomplete存在无法确认的支付/收款数据“支付数据待确认,暂不可退到 Credit”
refund_to_credit_not_supported防御性默认:能力快照存在但未给出原因(正常流程不会出现)“该委托的退款只能原路退回”

兜底约定:遇到本表以外的原因码时按通用文案处理(例如“该去向暂不可用”),不要直接把原始原因码展示给用户。缺失 refund_capabilities 的损坏债权返回空能力集合(available 为空),既不伪造原因码,也禁止提交去向。

为什么 unavailable 是对象、而 available 是数组:两者分工不同——available 是要渲染的有序选项列表,unavailable 是按去向查原因的索引(unavailable.credit 一次取值就能拿到说明文案,不必遍历)。没有禁用项时返回 {} 而不是 [],保证该字段的 JSON 类型恒定(前端无需 Array.isArray 分支)。

4.5 提交状态:can_submit 与disabled_reason

作用:回答两个相互独立的问题——“这次请求能不能提交”(can_submit)与“退款额输入为什么不能用”(disabled_reason)。未付款或已无可退额度时输入被禁用,但零退款取消/改价仍然可以提交。

字段维度说明
can_submit流程本次请求能否提交;计算不完整时为 false,不由退款上限推导(对应 UI「提交按钮」)。改价试算另有政策拒绝分支(低于最低退款金额、次数用完、第二次未退剩余全部、本次请求碎额超限、缺少汇率、新价低于不可退支付手续费),此时也为 false(其中低于最低退款金额仅适用于非全退的部分降价,R = M 全退豁免门槛),见 2026-09-26_small_refund_threshold_write_off.md
disabled_reason退款额输入输入框不可用的原因,与 can_submit 相互独立(对应 UI「退款额输入是否可用」);除下表取值外,退款政策另有 below_minimum_amount、refund_attempts_exhausted、final_refund_must_be_remaining、write_off_cap_exceeded、exchange_rate_unavailable、below_payment_fee(below_minimum_amount 现在只由改价入口返回;取消入口整笔剩余低于门槛时仍可选择 R = M 全退),见 2026-09-26_small_refund_threshold_write_off.md
场景can_submitdisabled_reason前端处理
有可退额度truenull退款额输入可用
未付款,或已无可退额度(历史退款=已付)trueno_refundable_amount禁用退款额输入;仍允许零退款取消、改价
试算数据不完整falsecalculation_incomplete禁用输入并禁止提交,展示 issues

所有情况都返回 HTTP 200;不要用失败响应表达“无可退额度”。

命名空间区分:顶层 disabled_reason 只描述退款输入;cancellation_entry.disabled_reason 只描述取消入口可用性(见 §5.3),两者可能同时出现在一个响应里。

4.6role_view

作用:承载角色专属、且不在 financials 中的派生信息——用户视角回答“这笔钱以什么形式、什么币种退回去”,画师视角回答“平台费是怎么算出来的”。已付款与未付款形状一致(未付款时全部为零值 Money)。

用户视角(回答“这笔钱以什么形式、什么币种退回去”):

字段类型含义前端用途
estimated_display_amountMoney | null用户偏好币种下的估算退款额;缺汇率时为 null对应 UI「偏好币种估算(用户)」;为 null 时不展示该行
gateway_refundsMoney[]按网关币种分列的预计退款(原路退回部分)对应 UI「网关退款明细(用户)」;多币种逐条渲染
credit_refundsMoney[]按币种分列的 Credit 退款(含 credit 目的地转换部分)对应 UI「Credit 退款明细(用户)」
estimate_noticestring说明文案(英文常量)作为脚注展示,需前端本地化

画师视角(回答“平台费怎么算出来的”):

字段类型含义前端用途
platform_fee_breakdown.basisMoney平台费基数(退款后的正余额)对应 UI「平台费拆解(画师)」——基数
platform_fee_breakdown.grossMoney平台费(Open Call 减免前)对应 UI「平台费拆解(画师)」——减免前金额
platform_fee_breakdown.open_call_fee_waiverMoneyOpen Call 手续费减免额对应 UI「平台费拆解(画师)」——减免行;为 0 时可省略
platform_fee_breakdown.wallet_appliedMoney平台费钱包抵扣额对应 UI「平台费拆解(画师)」——抵扣行;为 0 时可省略

画师视角不再重复返回支付手续费、退款额、退款后余额、最终平台费与预计净收入——这些都在 financials 中(payment_fee、refund、artist_settlement_basis(=退款后余额)、platform_fee、estimated_artist_net)。

4.7warnings 与issues

作用:试算过程的两类附带信息——可提示的提醒与必须处理的数据问题,让前端在“能提交但需说明”与“不能提交”之间做区分。

  • warnings:可提示用户但不阻塞,例如“已从 Stripe 账本归集历史退款”。
  • issues:计算数据问题,通常伴随 can_submit = false,此时禁止提交。
  • 两者元素结构:{ code, message, order_id? };message 仅用于排查,前端按 code 分支。

4.8 状态枚举

作用:取消、改价、退款债权与财务决议的状态字典——前端按状态决定按钮可用性、是否轮询、以及失败时的引导文案;不要在状态之外自行推导流程阶段。

退款进度只读 obligation_status 与 execution_status 双轴,退款债权不存在单轴 status。不能用 execution_status = retryable_failed / manual_review 判断客户是否收到退款,因为客户退款完成后,画师/平台回收失败仍可能让财务决议进入 manual_review。

取消申请 CommissionCancellation.status:

状态含义前端处理
pending等待对方确认按 can_withdraw/can_accept/can_reject 展示按钮
refund_processing正在执行退款禁用重复操作并轮询
settlement_processing正在结算画师侧禁用重复操作并轮询
completed完成刷新 Commission 与退款记录
failed失败展示 failure_code/failure_stage,引导联系支持
rejected对方拒绝结束轮询
withdrawn发起方撤回结束轮询

取消对象同时返回两个事实字段:

字段取值含义
business_statuspending / accepted / rejected / withdrawn / cancelled双方协商与 Commission 业务事实
financial_statusnot_started 或 Financial Resolution 状态退款、追缴、对账和画师结算的总体进度

退款债权使用双轴状态:

字段状态含义
obligation_statusopen对客户的退款义务仍未履行
obligation_statusfulfilled客户退款已经履行
obligation_statusvoided债权已作废
execution_statusawaiting_destination等待用户选择退款去向
execution_statusready去向与计划已确定,等待执行
execution_statusprocessing正在执行或等待 Provider 对账
execution_statuspartially_completed客户退款步骤只完成了一部分
execution_statusretryable_failed暂时失败,后端仍可自动恢复
execution_statusmanual_review自动恢复耗尽或事实无法安全核对
execution_statusnot_required债权被补偿性业务决议关闭,无需执行;与 obligation_status = voided 稳定配对
execution_statuscompleted客户退款履行步骤全部完成

退款债权只有上述双轴状态,没有单轴 status,也不存在 CommissionRefundStatus 枚举;两轴取值都是非空字符串。

Financial Resolution 状态:

状态含义
recorded业务决议已记录,等待资金执行
processing正在执行资金步骤
partially_completed部分义务已完成,仍有依赖未收尾
completed所有退款、追缴、对账与结算依赖完成
manual_review自动恢复无法继续,需要人工处理

备注:CommissionFinancialResolution.status 的 partially_completed 是可达状态——部分义务已完成但仍有依赖未收尾(写入点见 CommissionFinancialResolutionService 与 DispatchPendingCommissionResolutionsCommand)。该枚举没有 voided:需要撤销决议时应先定义补偿业务与审计规则,当前模型不包含它。

另注:CommissionRefund.execution_status 也有同名 partially_completed,二者含义不同,不要混用。

execution_status 只聚合客户退款履行步骤。obligation_status 与 execution_status 都是非空字段(数据库列均为 NOT NULL,obligation_status 默认 open),前端不需要处理 null,也不需要任何“旧值兜底”。客户已经收到退款、但 Transfer 冲正或画师追缴失败时,obligation_status 仍为 fulfilled,问题体现在 financial_resolution.status = manual_review;前端不能把它展示为“客户退款失败”。

voided 是不可执行终态:作废债权不再接受去向选择,也不执行任何步骤;指向它的陈旧执行消息只做幂等 no-op,不会重放 Provider 操作。作废时两轴在同一事务内写为 obligation_status = voided + execution_status = not_required,因此它既不是“退款失败”,也不是“人工已付款”,前端不要显示为可重试失败。not_required 只表示“无需执行”,isCustomerFulfilled() 仍只对 completed 成立。allocation_integrity_invalid 场景见 §10:open 债权进入 manual_review,已 fulfilled 的债权保持客户双轴状态并记录到 Refund 诊断与关联 Financial Resolution。

改价 WorkTaskPriceChange.status:pending(等待对方确认)、wait_pay(改价已同意,等待客户补款)、paid(新价格已应用)、rejected、canceled。降价不需要实际收款,但应用新价格后也使用 paid;因此 paid 不是“退款已支付”。退款进度必须通过 commission_refund_id 查询退款对象。

4.9 执行步骤类型(execution.steps[].type)

作用:退款执行进度的步骤字典——用于把 execution.steps 翻译成用户可读的进度文案(例如“正在退回原支付渠道”),并定位失败发生在哪一步。

类型说明
stripe_refundStripe 退款
stripe_transfer_reversalStripe Transfer 冲正(关联账户资金回收)
stripe_connected_account_debit对画师关联账户扣款(承担超出标准上限的手续费)
stripe_connected_account_credit向画师关联账户补足
paypal_refund / alipay_refund网关原路退款
user_wallet_refund退回用户 Credit 钱包
user_credit_refund退款额转为用户 Credit
artist_wallet_recovery从画师钱包回收已结算资金

取消对象的 settlement.steps[].type 使用另一组取值:artist_wallet_adjustment(画师钱包调整)、plat_fee_wallet_adjustment(平台费钱包调整)。

步骤状态:pending、processing、succeeded、failed。financial_effect 区分 customer_fulfillment、artist_recovery、platform_recovery;前端判断客户是否收到退款时只看 customer_fulfillment 及债权双轴状态。


5. 端点总览

本章作用:先给全景——有哪些端点、分别干什么、哪些是画师独有的;具体参数与响应在 §6。

端点领域速查:

路径或字段所属领域前端用它判断什么
/work_tasks/cancellation/*、active_cancellation取消领域取消协商、接受/拒绝、取消业务是否完成
/work_task_price_changes/*、price_change改价领域新旧价格、审批、待补款、价格是否已经应用
/commission_refunds/*、refund_pool退款领域退款债权、去向选择、客户退款是否履行
financial_resolution财务决议领域退款、追缴、对账和结算义务是否全部收尾
补款 Order 与支付回调支付领域加价差额是否实际支付

领域之间只通过关联字段衔接:改价对象的 commission_financial_resolution_id 指向财务决议,commission_refund_id 指向退款债权。不要使用一个领域的 status 推断另一个领域的进度。

5.1 用户端(前缀/api)

方法路径用途幂等
GET/work_tasks/info详情,含取消、改价、refund_pool 与 financial_resolution—
POST/work_tasks/cancellation/preview取消试算—
POST/work_tasks/cancellation/info最近一次取消申请(含终态)—
POST/work_tasks/cancellation/request发起取消申请idempotency_key
POST/work_tasks/cancellation/withdraw撤回自己的申请—
POST/work_tasks/cancellation/reject拒绝对方申请—
POST/work_tasks/cancellation/accept接受对方申请(可带去向)—
POST/work_task_price_changes/preview改价试算—
POST/work_task_price_changes/create发起改价—
POST/work_task_price_changes/list改价记录—
POST/work_task_price_changes/approve批准画师改价—
POST/work_task_price_changes/reject拒绝画师改价—
POST/work_task_price_changes/cancel取消自己的改价申请—
POST/commission_refunds/list退款记录—
POST/commission_refunds/info退款详情—
POST/commission_refunds/select_destination选择退款去向—
POST/commission_refunds/select_destinations为多笔待选择债权批量选择同一去向—

5.2 画师端(前缀/api/artist_center)

与用户端结构一致,差异:

  • 取消:/work_tasks/cancellation/{preview,info,request,withdraw,reject,accept};画师发起 request 不接收 refund_destination(画师不能决定客户去向);画师 accept 也不新增去向选择,沿用用户在申请中提交的偏好。
  • 改价:多一个 /work_task_price_changes/info(详情,仅画师端提供);画师 create 不接收 refund_destination。
  • 退款:只有 /commission_refunds/{list,info},没有 select_destination。

5.3 WorkTask 详情扩展字段

字段说明
cancellation_entry入口状态:{ mode, can_start, disabled_reason }
active_cancellation仍占用该 Commission 的取消申请摘要,没有则 null
price_change仍处于 pending/wait_pay 的改价对象,没有则 null
refund_pool未完成退款债权汇总:数量、待操作数量、待选择债权 id、合计金额与批量去向交集
financial_resolution当前财务决议摘要;没有则 null

cancellation_entry.mode:

  • direct:可直接取消(无已付订单且状态为 pending/wait_pay)——仍调用统一 preview 与 request;不存在独立的直接取消端点;
  • negotiated:需要对方确认;
  • unavailable:入口不可用,用稳定 disabled_reason 决定提示文案。

cancellation_entry.disabled_reason 可能取值:active_cancellation、payment_processing、active_price_change、payment_data_incomplete、status_not_cancellable。不存在 active_refund 取值。

active_cancellation 只包含非终态(pending、refund_processing、settlement_processing、failed);completed/rejected/withdrawn 视为历史,通过 cancellation/info 查询。

5.4 通用响应与错误约定

情况HTTPBody
成功200{ "data": ... }(列表为 { "data": [...], "total": n })
业务规则失败400{ "code": 22004, "message": "..." }
字段格式错误422Laravel 校验结构
不存在 / 无权访问404—

没有 202;异步流程统一用 200 + 资源自身的状态字段表达(取消对象是 status,退款债权是 obligation_status / execution_status 双轴,改价对象是 status)。

改价领域的业务失败同样返回带 code 的 HTTP 400:重复发起待处理改价是 60003 WorkTaskPriceChangeAlreadyPending,approve/reject/cancel 以及补款完成时的状态冲突是 60004 WorkTaskPriceChangeStateInvalid(见 §9)。前端按 code 分支,无需再兼容无 code 的 400。


6. 端点详解

本章作用:逐个端点的参数表与行为要点(返回什么、失败会怎样)。响应字段的含义与用途见 §4。

6.1POST /api/work_tasks/cancellation/preview(用户端)

作用:打开取消弹窗时调用,拿到建议退款额、可退上限、可用去向与角色视角金额。能否发起取消由 WorkTask 详情的 cancellation_entry 决定,不要用本接口判断。

参数类型必填说明
idinteger是WorkTask ID,最小 1
business_refund_amountinteger否业务币种最小单位,最小 0;省略时返回默认建议额(= refund_limits.standard);显式传 0 表示不退款并试算画师结算

响应为取消 Preview(见 §4 与附录 A.1)。要点:

  • mode = direct 时展示“立即取消”说明;negotiated 时展示协商表单,两种模式都提交到 cancellation/request。
  • 未付款时 payment_state = unpaid,financials 为零值,can_submit = true、disabled_reason = no_refundable_amount。
  • 本次退款额读 financials.refund;建议额读 refund_limits.standard(或 refund_entry.presets.standard,两者相同)。
  • 画师端 POST /api/artist_center/work_tasks/cancellation/preview 参数相同,role_view 为画师视角;画师调整金额时防抖重新试算并丢弃过期响应。

6.2POST /api/work_tasks/cancellation/request(用户端)

作用:发起取消申请(negotiated)或直接完成取消(direct),写入退款额与去向并返回完整取消对象。

参数类型必填说明
idinteger是WorkTask ID
business_refund_amountinteger条件已付款时必须显式提交(可为 0);未付款传 0 或省略
refund_destinationstring否original 或 credit;可省略,省略时债权进入 awaiting_destination 等待用户选择
idempotency_keystring是8–128 字符;同一次逻辑提交的重试必须复用;改金额/改去向换新键
  • 返回完整取消对象(结构见 §2.4)。
  • mode = direct:立即完成,返回 mode = direct、status = completed,无需 accept。
  • mode = negotiated:创建 pending 申请,等待对方处理。
  • 同一幂等键 + 相同条款返回同一记录;同一幂等键 + 不同条款返回 22010。
  • 画师端 request 不传 refund_destination。

6.3POST /api/work_tasks/cancellation/info

作用:查看最近一次取消申请并轮询进度——当前处于哪个阶段、退款与结算各自执行到哪一步。

请求 { "id": <work_task_id> },返回该 Commission 最近一次取消申请(含终态);无记录时 { "data": null }。用于轮询详情(含 refund、settlement、business_status 与 financial_status)。

与 active_cancellation 的区别:后者只返回仍占用 Commission 的申请摘要;本接口返回最近一次(即便已结束)。

6.4POST /api/work_tasks/cancellation/withdraw | reject | accept

作用:申请生成后的三种处置动作;按钮是否展示以取消对象的 can_withdraw / can_accept / can_reject 为准。

端点参数说明
withdrawrequest_id发起方撤回自己的 pending 申请
rejectrequest_id对方拒绝 pending 申请
accept(用户端)request_id、refund_destination(可选)接受画师申请;省略去向时债权进入 awaiting_destination
accept(画师端)request_id接受用户申请;沿用用户在发起时提交的偏好,未提交时债权进入 awaiting_destination

三者都返回完整取消对象。接受后业务决议立即生效;存在未完成退款债权不会回滚取消。资金部分进入退款池和异步结算,前端结合 business_status、financial_status 及关联退款的双轴状态轮询。

6.5 退款端点

所属领域:退款领域(Commission Refund)。 这些端点查询和操作退款池。取消与降价产生的每笔差额都是独立债权;未选择去向、执行中、暂时失败和人工处理中的债权都会保留并占用自己的资金预留。它们不会修改改价状态或 WorkTask 价格。

端点参数说明
POST /commission_refunds/listwork_task_id(必填)、page(默认 1)、size(默认 15,1–50)按 ID 倒序返回 { summary, data, total }
POST /commission_refunds/infoid单个退款详情,结构与列表元素相同
POST /commission_refunds/select_destinationid、refund_destination(必填)仅 awaiting_destination 状态可调用;成功后自动进入执行队列
POST /commission_refunds/select_destinationswork_task_id、refund_ids、refund_destination为多笔 awaiting_destination 债权原子地选择同一去向;refund_ids 取 refund_pool.action_required_refund_ids 或 list 的 data[].id;返回 { data, summary },refund_ids 含不属于该 Commission 的债权返回 404
  • 退款额读 financials.refund;单笔业务允许去向读 cash_destinations.allowed,当前执行能力读 execution.readiness(ready / blocked / manual_review)与 execution.blocked_reason(no_refundable_amount / no_available_destination / refund_payment_data_incomplete)。
  • select_destination 状态不符返回 22015,去向不可用返回 22016。债权进入 ready 后不能再改去向。
  • select_destinations 的 refund_ids 至少一个且不能重复。服务端重新计算所有选中债权的去向交集;任一债权状态不符返回 22015,所选去向不在交集内返回 22018,整批要么全部生效、要么全部不生效。
  • refund_ids 的取法:worktask/info 的 refund_pool.action_required_refund_ids,或 commission_refunds/list 的 data[].id(只取 execution_status = awaiting_destination 的元素)。前者与 refund_pool.bulk_allowed_destinations 是同一集合,成对使用即可,不需要自己跨页拼 id。
  • refund_pool.bulk_allowed_destinations = [] 是正常查询结果,表示当前没有共同去向;前端禁用批量入口,不需要显示错误。
  • 画师端仅有 list 与 info;list 的 work_task_id 必须属于该画师。

退款列表关键结构:

{
  "summary": {
    "open_count": 2,
    "action_required_count": 1,
    "action_required_refund_ids": [101],
    "financials": {
      "open_amount": {
        "amount": 7200,
        "currency": { "id": 31, "code": "JPY", "symbol": "JP¥", "is_zero_decimal": true }
      }
    },
    "bulk_allowed_destinations": ["original"]
  },
  "data": [
    {
      "id": 101,
      "work_task_id": 2210,
      "trigger_type": "cancellation",
      "trigger_id": 91,
      "obligation_status": "open",
      "execution_status": "awaiting_destination",
      "cash_destination": null,
      "preference_applied": false,
      "currency_id": 31,
      "cash_destinations": { "allowed": ["original", "credit"], "unavailable": {} },
      "execution": {
        "attempts": 0,
        "total_steps": 0,
        "succeeded_steps": 0,
        "steps": [],
        "readiness": "ready",
        "blocked_reason": null,
        "failure_code": null,
        "manual_review_reason": null
      },
      "financials": {
        "paid": { "amount": 7200, "currency": { "id": 31, "code": "JPY", "symbol": "JP¥", "is_zero_decimal": true } },
        "previous_refunded": { "amount": 0, "currency": { "id": 31, "code": "JPY", "symbol": "JP¥", "is_zero_decimal": true } },
        "refund": { "amount": 3000, "currency": { "id": 31, "code": "JPY", "symbol": "JP¥", "is_zero_decimal": true } },
        "retained": { "amount": 4200, "currency": { "id": 31, "code": "JPY", "symbol": "JP¥", "is_zero_decimal": true } }
      },
      "refund_policy": {
        "maximum_policy": "standard",
        "can_exceed_standard": false,
        "can_refund_to_credit": true,
        "has_stripe_connected_order": false,
        "all_orders_stripe_connected": false,
        "has_unknown_order": false
      },
      "action_required_at": "2026-09-11T09:00:00+00:00",
      "created_at": "2026-09-11T08:59:00+00:00",
      "completed_at": null
    }
  ],
  "total": 2
}

退款债权不再返回 status、destination 和重复的 destinations:进度读 obligation_status + execution_status,去向能力读 cash_destinations.allowed / cash_destinations.unavailable。上例 financials 仅节选基础金额字段,其余金额与币种对象同样按 §4.1 返回。

批量选择请求示例(refund_ids 直接取 summary.action_required_refund_ids,refund_destination 取 summary.bulk_allowed_destinations 之一):

{
  "work_task_id": 2210,
  "refund_ids": [101],
  "refund_destination": "original"
}

6.6 改价端点

所属领域:改价领域(WorkTask Price Change)。 这些端点负责试算新价格、记录双方改价意愿、审批、待补款及把新价格应用到 WorkTask。它们不会执行退款。

端点参数说明
POST /work_task_price_changes/previewwork_task_id、price(整数,最小 0)加价或无需退款时 financials.refund 为 0,仍返回完整 financials
POST /work_task_price_changes/create(用户端)work_task_id、price(数值,最小 0)、refund_destination(可选)用户降价等待画师批准;用户加价直接进入 wait_pay
POST /work_task_price_changes/create(画师端)work_task_id、price画师降价立即应用;画师加价等待用户批准
POST /work_task_price_changes/listwork_task_id、page、size{ data, total },元素为改价对象
POST /api/artist_center/work_task_price_changes/infoid仅画师端
approve / reject / cancelid改价状态迁移;权限按发起方与方向判定

refund_destination 在用户降价请求中是可选的提前去向偏好,属于改价请求附带信息:

  • 不传偏好不会阻止创建或批准改价。
  • 降价生效时,财务决议重新校验偏好;仍可用则退款债权直接进入 ready,不可用或未填写则进入 awaiting_destination。
  • 批准后后端会把该次退款债权实际采用的去向写入 price_change.refund_destination;它记录的是“偏好/已应用去向”,不是退款债权 cash_destination 的兼容别名或第二份事实来源。
  • 画师端 create 不接收去向;画师降价立即应用后,由用户在退款池选择。
  • 不要把 price_change.refund_destination 当作退款状态或最终到账证明。

四种发起路径与当前代码一致:

发起方与方向create 后改价状态对方动作新价格何时应用是否进入退款领域
用户加价wait_pay不需要画师批准客户完成补款后变为 paid否
画师加价pending用户 approve 后变为 wait_pay客户完成补款后变为 paid否
用户降价pending画师 approve批准事务内立即变为 paid 并更新 WorkTask 价格正退款额时创建债权
画师降价paid无需用户批准create 事务内立即更新 WorkTask 价格正退款额时创建债权

这里的 paid 属于改价状态,含义是“新价格已应用”。画师降价没有补款,也会直接进入 paid。

同一 Commission 同时只能存在一笔待处理改价:create 时若已存在 pending 或 wait_pay 的改价申请,用户端与画师端都会返回 HTTP 400 与 code=60003(见 §9),并且不会新增改价记录。approve/reject/cancel 等动作遇到不允许的状态时返回 HTTP 400 与 code=60004。

退款额试算:本次退款 = max(0, 有效上限 − 新稿酬)。把价格降到有效上限时退款为 0,仍返回完整财务摘要;降价生效后会记录 Financial Resolution,但不会创建零金额 CommissionRefund。连续改价会结合历史已完成退款与尚未完成债权的资金预留,只新增未覆盖的差额。

  • 未完成的旧退款债权不会阻断新的降价决议;本次只创建“累计目标退款 − 已完成退款 − 已预留退款”的差额债权。
  • 业务改价先落库,支付渠道随后异步执行。提前提交的去向偏好如果对新债权不可用,改价仍然生效,债权进入 awaiting_destination 等待用户重新选择。
  • 重复批准同一改价不会重复创建 Financial Resolution 或退款债权;但会因改价已非 pending 返回 HTTP 400 与 code=60004(见 §9),因此不要把 approve 当作可重试的幂等接口。

改价财务展示:

  • financials.payment_fee 是已付 Order 的实际手续费。
  • financials.future_payment_fee_estimate 是变更后全部剩余待付稿酬的手续费预估,包含本次补款和后续节点;不要用 need_pay_amount × 费率 在前端重算。
  • financials.artist_settlement_basis 等于 new_price。
  • financials.platform_fee 按 new_price × artist.worktask_plat_fee_ratio 进入平台费政策计算,不使用“扣除支付手续费后的余额”作为基数。
  • financials.estimated_artist_net 已同时考虑实际手续费、后续手续费预估和按新总价计算的平台费,前端直接展示。
  • 上述 financials 是改价后的结算预测。真正的退款债权金额与执行状态,在降价生效后以 commission_refunds/info 返回为准。

旧直接取消端点 POST /api/work_tasks/cancel 与 POST /api/artist_center/work_tasks/cancel 已删除(调用返回 404)。未付款直接取消与已付款协商取消现在都只有统一入口:cancellation/preview + cancellation/request(未付款时 mode = direct 立即完成,已付款时 mode = negotiated 等待对方确认)。


7. 对接流程

本章作用:把端点串成流程——先调什么、谁来确认、什么时候开始轮询、失败如何处理。

7.1 用户发起(画师接受)

7.2 画师发起(用户接受并选择去向)

同 §7.1 镜像:画师 request 不带去向 → 用户 preview(带申请金额)取得 destinations → 用户 accept 时传 refund_destination。

7.3 改价与退款:领域边界

前端应把同一次降价看成三个相邻但独立的领域对象:

所属领域对象与接口负责什么不负责什么
改价领域WorkTaskPriceChange、work_task_price_changes/*新旧价格、发起/审批、待补款、价格是否已应用、改价后的财务预测不表示退款是否到账
财务决议领域CommissionFinancialResolution、WorkTask 的 financial_resolution把已生效降价转换为不可变资金事实,协调退款与后续资金依赖不接收用户的退款去向操作
退款领域CommissionRefund、commission_refunds/*、refund_pool退款债权、资金预留、去向选择、Provider 执行、客户是否收到退款不决定新价格是否生效
支付领域WorkTask 补款 Order加价后的实际收款及支付手续费事实不参与降价退款去向选择

跨领域只通过关联键衔接:price_change.commission_financial_resolution_id 指向财务决议,price_change.commission_refund_id 指向该次降价产生的退款债权。状态不能跨对象解释。

读取规则:

  • price_change.status = paid 后停止等待改价审批;如果 commission_refund_id 非空,再进入退款领域查询。
  • price_change.financials.refund 是改价试算/记录中的预计退款金额;退款债权创建后,以 commission_refunds/info.data.financials.refund 和双轴状态为准。
  • price_change.refund_destination 只是批准前的去向偏好(批准后写入该次债权实际采用的去向),不表示 Provider 已受理;最终去向看退款对象的 cash_destination。
  • WorkTask 详情的 price_change 只返回 pending / wait_pay 改价。进入 paid 后,应从改价列表取得 commission_refund_id,再查询退款详情或退款池。

7.4 轮询与失败处理

场景建议
取消处理中每隔数秒轮询 cancellation/info;completed 后刷新 WorkTask 详情
退款处理中轮询 commission_refunds/info;processing 可能因 Provider 未结算而停留,属正常,不要重复提交
退款 retryable_failed后端恢复任务会按限速策略继续处理;前端保持轮询,不重复提交退款或去向选择
退款或财务决议 manual_review停止自动操作,展示处理中/联系支持;债权仍占用对应资金,不能当作已释放
取消 failed展示 failure_stage + failure_code,停止自动重试
收到 400 冲突类错误刷新 WorkTask、取消申请、退款、改价后重新渲染可用操作

7.5 并发限制

  • 未完成退款债权本身不再阻断后续改价、取消或完成;后端会先预留债权对应资金,并让相关画师结算等待依赖完成。
  • 活跃取消申请、活跃改价申请及支付中 Order 仍按各接口返回的能力字段和错误码互斥。
  • 存在支付中订单时:不能取消。
  • 冲突以后端错误码为准(22008、21012 等)。

8. UI 字段绑定对照表

本章作用:写页面时的查表——UI 上的每一项去哪个字段取数;字段含义与用途见 §4。

路径基准:本表「取消 Preview」列省略响应根 data.(即 data.financials.paid 写作 financials.paid);active_cancellation.* 表示 WorkTask 详情中该对象的路径。

8.1 取消领域

UI 展示项取消 Previewactive_cancellation
客户已支付financials.paidactive_cancellation.financials.paid
历史已退financials.previous_refundedactive_cancellation.financials.previous_refunded
本次退款额financials.refundactive_cancellation.financials.refund
退款后保留financials.retainedactive_cancellation.financials.retained
标准可退款financials.refund_limits.standardactive_cancellation.financials.refund_limits.standard
输入框上限financials.refund_limits.maximumactive_cancellation.financials.refund_limits.maximum
支付手续费financials.payment_feeactive_cancellation.financials.payment_fee
画师承担手续费financials.artist_fee_coverage同名字段
平台服务费financials.platform_fee同名字段
预计画师净收入financials.estimated_artist_net同名字段
退款后余额(画师)financials.artist_settlement_basis同名字段
可用退款去向destinations.availableactive_cancellation.destinations.available
去向不可用说明(可选文案;不渲染置灰项)destinations.unavailable同名
退款额输入是否可用disabled_reason—(申请已提交,不可改额)
提交按钮can_submitactive_cancellation.can_accept / can_reject / can_withdraw
是否允许超标准上限refund_policy.can_exceed_standard同名
网关退款明细(用户)role_view.gateway_refundsactive_cancellation.role_view.gateway_refunds
Credit 退款明细(用户)role_view.credit_refunds同名
偏好币种估算(用户)role_view.estimated_display_amount同名
平台费拆解(画师)role_view.platform_fee_breakdown同名

role_view 按视角互斥:画师视角只返回 platform_fee_breakdown,用户视角只返回 gateway_refunds / credit_refunds / estimated_display_amount / estimate_notice;不要假设两类字段同时存在。

8.2 改价领域

改价对象(列表元素)与 WorkTask 详情 price_change 使用以下绑定;表中路径同样省略响应根 data.。注意:work_task_price_changes/preview 只返回 work_task_id、old_price、new_price、financials、refund_policy、destinations、can_submit、disabled_reason、warnings、issues,不返回下表的 status、need_pay_amount、work_task_paid_amount、refund_destination、commission_refund_id、commission_financial_resolution_id;Preview 阶段只做金额试算与去向预览。

UI 展示项改价字段说明
改价前稿酬old_priceMoney
改价后稿酬new_priceMoney
改价业务状态status只表示改价协商、待补款或价格已应用
本次待补缴need_pay_amount只表示当前改价需要支付的金额,不代表全部剩余待付款
客户累计已支付financials.paid已完成退款不会减少该值
降价预计退款额financials.refund改价试算投影;不表示退款债权已创建或已到账
已产生支付手续费financials.payment_fee已付款 Order 的实际手续费
后续支付手续费预估financials.future_payment_fee_estimate对全部剩余待付款的预估;不要复用 payment_fee
变更后稿酬/平台费基数financials.artist_settlement_basis等于 new_price
预计平台服务费financials.platform_fee按变更后总价及画师配置费率计算
预计画师最终净收入financials.estimated_artist_net已包含实际手续费、未来手续费预估和平台费
提前去向偏好refund_destination改价批准前用户填写的偏好;批准后由后端写入该次退款债权实际采用的去向;不能用来判断退款执行状态
退款债权关联commission_refund_id非空后转到退款领域查询;为空不代表改价失败
财务决议关联commission_financial_resolution_id表示降价财务事实已经记录,不表示退款完成

8.3 退款与财务决议领域

WorkTask 详情与退款池使用以下绑定:

UI 展示项字段说明
未完成退款数量refund_pool.open_count可用于入口角标
需要选择去向数量refund_pool.action_required_count大于 0 时提示用户处理
未完成退款总额refund_pool.financials.open_amountMoney,直接使用内嵌币种展示
待选择债权 idrefund_pool.action_required_refund_ids与 bulk_allowed_destinations 同一集合;直接作为 select_destinations 的 refund_ids,空数组表示无需选择
批量可选去向refund_pool.bulk_allowed_destinations空数组时禁用批量入口
当前财务进度financial_resolution.status与客户退款义务状态分开显示
单笔债权状态obligation_status + execution_status双轴状态;退款债权不再有单轴 status
单笔允许去向cash_destinations.allowed只渲染返回的选项
单笔当前阻塞execution.readiness + execution.blocked_reason用于处理中或联系支持提示

说明:

  • 币种来源:所有金额对象自带 currency({ id, code, symbol, is_zero_decimal }),渲染时取该金额同级的 currency 即可;gateway_refunds / credit_refunds 是 Money 列表,元素内自带币种,不需要站点币种表。
  • financials 是唯一业务金额来源;退款/取消/改价资源对象不再返回 business_refund_amount 等标量镜像(读 financials.refund)。
  • role_view 只含角色专属派生信息;financials 对双方都可见,因此画师净收入等字段不再在 role_view 重复。
  • 同名 financials.refund 必须结合所属对象理解:在 PriceChange 上是改价投影,在 CommissionRefund 上是已创建债权金额。
  • WorkTask 详情里 price_change 只代表 pending/wait_pay 的改价申请;改价进入 paid 后要查看退款进度,请用改价列表取得 commission_refund_id 再查退款。

9. 错误处理

本章作用:联调排错——按 code 分支处理,不要解析 message 文案。

改价入口的业务失败与其它领域一致,返回带 code 的 HTTP 400:重复发起待处理改价是 60003,approve/reject/cancel 以及补款完成时的状态冲突是 60004。字段格式错误仍由 422 返回(见 §5.4)。

{ "code": 22015, "message": "Commission refund state invalid" }
错误码枚举触发场景前端建议
22001RefundSubjectNotFound退款主体不存在刷新页面
22002RefundSubjectUnsupported主体类型不支持隐藏入口
22003RefundNoPaidOrders无可退款订单刷新支付状态
22004RefundAmountInvalid金额超过有效上限 / 无剩余额度下提交非零金额用 refund_limits.maximum 重新限制输入
22005RefundPaymentDataIncomplete支付/对账数据不完整、存在支付中订单禁止提交并联系支持
22006RefundAllocationInvalid资金分配或历史分摊不自洽禁止提交并联系支持
22007RefundWorkTaskStatusInvalidCommission 状态不允许取消刷新状态
22008CommissionCancellationAlreadyPending已有进行中的取消展示已有申请
22009CommissionCancellationNotFound取消申请不存在刷新
22010CommissionCancellationStateInvalid状态不允许该操作 / 幂等键复用条款不同刷新状态;检查幂等键
22011CommissionCancellationOwnRequest发起人不能响应自己的申请仅保留撤回按钮
22012CommissionCancellationTermsChanged提交时事实已变化重新试算并重新发起
22013CommissionRefundExecutionIncomplete存在执行中(processing / partially_completed)的退款债权时尝试继续支付展示退款池并等待处理
22015CommissionRefundStateInvalid退款状态不允许该操作(如非 awaiting_destination 选去向)刷新退款状态
22016CommissionRefundDestinationInvalid所选去向不在可用集合内(退款债权读 cash_destinations.allowed,取消/改价读 destinations.available),或降价退款未选择去向按最新可用去向重选
22017CommissionCancellationActionUnavailable取消动作与当前模式不匹配(如对已付款 Commission 走直接取消),或取消入口不可用刷新 cancellation_entry,统一改用 cancellation/preview + cancellation/request
22018CommissionRefundNoCommonDestination批量选择的去向不在所选债权的共同允许集合内刷新退款池;交集为空时禁用批量入口
22019CommissionRefundReconciliationUnavailable对账资格校验不通过:债权未完成、执行计划缺失、步骤未全部成功,或 Stripe recovery/reversal 配对与 Provider 引用不完整按最新状态刷新后再对账(对账为管理后台受控操作,接口细节见 pipipen-api 代码)
22020CommissionFinancialOperationRequestConflict内部人工财务操作的 X-Request-Id 复用但请求条款不同,或上一次请求仍停在 processing / 已 failed不要自动重放;先核对目标资源当前事实,再决定是否用新的 X-Request-Id 重试
22021CommissionCancellationSettlementPlanInvalid取消结算时无法把冻结快照落成结算步骤:缺少平台费钱包计划、平台费还原无法映射到原始抵扣,或 Credit 画师调整缺少目标钱包(持久数据不变量)不要重试;联系支持核对冻结快照与钱包事实
60001WorkTaskPriceChangeInvalid改价提交的新价格与当前价格相同提示重新输入价格
60002WorkTaskPriceChangeTermsChanged改价批准时事实已变化重新试算并重新提交改价
60003WorkTaskPriceChangeAlreadyPending该 Commission 已存在 pending / wait_pay 的改价申请,用户端或画师端再次发起展示已有改价申请并等待其结束,不再提交
60004WorkTaskPriceChangeStateInvalidapprove/reject/cancel 或补款完成时改价状态不允许该动作(如重复批准已 paid 的改价)刷新改价状态后再操作
21012WorkTaskPaymentActionConflict存在进行中的改价/退款/取消导致冲突刷新相关资源

10. 开发阶段破坏性变更

本章作用:说明本功能的开发阶段契约不承诺兼容,并列出本次清理移除的字段、端点、命令、主题与错误码;逐条旧→新绑定映射见 §11。

本功能仍处于开发阶段,接口按“正确契约优先”演进,不提供旧字段兼容层。 注意区分两件事:

  • API 不兼容:旧字段、旧路由、旧命令、旧 Topic 不再返回或存在;调用方必须按本文档升级。
  • 数据库可增量部署:已运行过历史 migration 的测试数据库不需要 migrate:fresh,执行普通 php artisan migrate 即可。正向 migration 2026_09_16_000001_finalize_commission_refund_schema 会先校验 commission_refunds.execution_status 无 NULL 且双轴取值都在当前枚举内,通过后删除 status、destination、active_work_task_id,并把 execution_status 收紧为非空;资金金额、双轴状态、cash_destination 与 Financial Resolution 关联都保留。校验发现漂移数据时会中止 deployment 并要求人工处理,不会按旧 status 重新推导或补造状态。

本次破坏性清理(2026-09-16 按代码复核):

  • 退款债权移除单轴状态与旧去向字段:不再返回 status、destination,也不再返回与 cash_destinations 重复的 destinations。前端只能读 obligation_status + execution_status 双轴,以及 cash_destination / cash_destinations.allowed / cash_destinations.unavailable。旧 CommissionRefundStatus 枚举已不存在。
  • destinations.available / destinations.unavailable 只属于取消 Preview、取消对象与改价能力集合;退款债权改用 cash_destinations.allowed / cash_destinations.unavailable。
  • commission_refunds 表移除 status、destination、active_work_task_id 列;新增 commission_financial_resolution_id(可空)、obligation_status(非空,默认 open)、execution_status(非空、无默认值)、cash_destination(可空)、preference_applied、action_required_at、manual_review_reason。两轴状态均为非空,前端不再需要处理历史 null。
  • commission_refunds 表删除 next_retry_at、last_notified_at 两列(2026_09_17_000004):它们没有任何生产读者,重试由执行事实与 Outbox 驱动。这两个字段从未是公开契约字段;未来的通知需求必须记录自己的投递事实,不复用含义不清的通用时间戳。
  • 删除旧直接取消端点 POST /api/work_tasks/cancel 与 POST /api/artist_center/work_tasks/cancel(调用返回 404)。未付款直接取消只能走统一 cancellation/preview + cancellation/request(mode = direct)。
  • 删除命令 refund:retry-commission 与 commission-refund:backfill-allocations。保留的运维命令为 commission-refund:retry、refund:retry-commission-cancellation、commission-refund:reconcile、commission-financial-resolution:dispatch-pending、commission-financial-resolution:resume、wallet:record-stripe-refund-adjustment。
  • 删除 Outbox 主题 commission.financial_resolution.recorded。现存主题为 commission.refund.execute、commission.financial_resolution.resume、commission.cancellation_settlement.execute。
  • 删除已退役的旧退款冲突错误码:该值不再保留定义、未重新编号;22015–22019 取值与含义不变,并新增 22020(内部人工操作请求冲突)与 22021(取消结算计划数据不变量)。取消入口的 active_refund 禁用原因同样不再存在。
  • voided 是不可执行终态:已作废债权不能选择去向、不能被执行;陈旧执行消息对它是幂等 no-op(不调用 Provider、不增加执行次数、不重放步骤)。voidRefund 只允许作废仍为 open、全部 allocation 仍 reserved 且客户履约步骤尚未开始的债权;已开始 Provider 操作、部分履行或已 fulfilled 的债权拒绝作废。
  • voided 与 execution_status = not_required 稳定配对(2026_09_17_000003):作废时两轴一次提交,历史数据中曾被投影为 retryable_failed 的作废债权会被规范化为 not_required。该迁移在写入前校验作废债权没有已开始/成功的客户履约步骤、且 allocation 已全部 released,不满足则中止并输出数量与样例 ID。not_required 既不是失败也不是履约完成。
  • allocation 完整性异常原因码 allocation_integrity_invalid:客户 Step 与其 allocation 的关联属于数据不变量。open 债权出现该异常时进入 execution_status = manual_review;fulfilled 债权保持 obligation_status = fulfilled + execution_status = completed(不把客户已收到的钱展示为退款失败),异常记录在 Refund 的 manual_review_reason / last_error_code / last_error,并把该债权自身的 Financial Resolution 以及通过 dependency 依赖它的 Resolution 一并置为 manual_review。

在本次清理之前已经完成的其它统一化(相对更早原型仍然不兼容):

  • 移除所有扁平镜像金额:refund.business_paid_amount、refund.business_refund_amount、refund.business_retained_amount、refund.standard_maximum_refundable_amount、refund.absolute_maximum_refundable_amount、refund.effective_maximum_refundable_amount、refund.suggested_refund_amount、refund.artist_fee_coverage_amount、refund.payment_fee_amount、refund.payment_fee_bearer、refund.business_currency,以及取消/退款资源上的 business_paid_amount、business_refund_amount、*_maximum_refundable_amount、currency(金额与其币种统一由 financials 提供)。
  • refund 块改名为 refund_entry,只保留 presets(Money)与 fee_policy。
  • calculation_complete 与 can_submit_cancellation_request 不再返回,统一使用 can_submit(并配 disabled_reason)。
  • 改价对象:old_price、new_price、need_pay_amount、work_task_paid_amount 改为 Money;old_price_money、new_price_money、business_refund_amount 移除。
  • role_view:金额改为 Money;移除与 financials 重复的字段(payment_processing_fee、refund_to_user、final_platform_fee、estimated_artist_net、currency、business_currency);gateway_refunds / credit_refunds 由 { 币种码: 金额 } 改为 Money 列表;画师平台费拆解移入 role_view.platform_fee_breakdown。
  • work_task.contract_amount 改为 Money,work_task.business_currency 移除。
  • financials 新增 retained(退款后保留额)。
  • 改价 financials 新增 future_payment_fee_estimate;payment_fee 明确只表示实际已产生手续费。改价的 artist_settlement_basis、platform_fee、estimated_artist_net 改为按变更后总稿酬预测。
  • 移除重复字段:work_task.destinations(改用顶层 destinations,语义属于本次退款)、role_view.balance_after_refund(改用 financials.artist_settlement_basis)。
  • 明确 financials 不按角色裁剪:用户端同样包含 artist_settlement_basis、platform_fee、estimated_artist_net、artist_fee_coverage 与 payment_fee;角色专属信息只在 role_view。
  • WorkTask 详情新增 refund_pool 与 financial_resolution;一张 Commission 可同时存在多笔未完成退款债权。
  • 退款新增 obligation_status + execution_status 双轴状态、cash_destination、cash_destinations、action_required_at、preference_applied。
  • 取消对象新增 business_status 与 financial_status,业务决定不会因后续 Provider 执行失败而回滚。
  • 新增批量去向接口 POST /commission_refunds/select_destinations;共同去向为空是正常读模型结果,非法提交使用 22018。

11. 前端现有绑定迁移对照表

本章作用:改前端时的操作清单——按当前绑定逐条替换;变更原因见 §10。

下表按 pipipen-front 当前 Commission 详情页(用户端 / 画师端)的实际绑定整理,逐条给出新字段。请求参数与错误码不变。

旧绑定新绑定说明
refund_preview.refund.standard_maximum_refundable_amountrefund_preview.financials.refund_limits.standard.amount默认建议退款额
refund_preview.refund.absolute_maximum_refundable_amountrefund_preview.financials.refund_limits.maximum.amount语义修正:absolute 在平台收款场景大于有效上限,旧绑定会让输入越过硬上限并被 22004 拒绝;输入上限一律用 maximum
refund_preview.refund.business_paid_amountrefund_preview.financials.paid.amount已支付额
refund_preview.refund.business_retained_amountrefund_preview.financials.retained.amount退款后保留额
refund_preview.refund.payment_fee_amountrefund_preview.financials.payment_fee.amount支付手续费
refund_preview.refund.artist_fee_coverage_amountrefund_preview.financials.artist_fee_coverage.amount画师承担的手续费
refund_preview.refund.payment_fee_bearerfinancials.artist_fee_coverage.amount > 0 ? 'artist' : 'user'承担方由覆盖额推导,不再单独返回
refund_preview.refund.presets.unfinished_stages / .standard / .fullrefund_preview.refund_entry.presets.unfinished_stages / .standard / .full(各取 .amount)三个快捷额
refund_preview.refund.fee_policyrefund_preview.refund_entry.fee_policy—
refund_preview.role_view.refund_to_userrefund_preview.financials.refund.amount退款给用户
refund_preview.role_view.payment_processing_feerefund_preview.financials.payment_fee.amount—
refund_preview.role_view.balance_after_refundrefund_preview.financials.artist_settlement_basis.amount退款后余额
refund_preview.role_view.final_platform_feerefund_preview.financials.platform_fee.amount—
refund_preview.role_view.estimated_artist_netrefund_preview.financials.estimated_artist_net.amount—
refund_preview.role_view.platform_fee_basis / gross_platform_fee / open_call_fee_waiver / plat_fee_wallet_appliedrefund_preview.role_view.platform_fee_breakdown.basis / .gross / .open_call_fee_waiver / .wallet_applied(均为 Money)画师专属拆解
refund_preview.role_view.gateway_refunds.CNY(映射取值)refund_preview.role_view.gateway_refunds[].amount + .currency改为 Money 列表,不再需要站点币种表
refund_preview.role_view.credit_refunds.CNY(映射取值)refund_preview.role_view.credit_refunds[].amount + .currency同上
refund_preview.role_view.estimated_display_amount(数字)refund_preview.role_view.estimated_display_amount.amount(Money;缺汇率时整体 null)—
refund_preview.role_view.display_currencyrefund_preview.role_view.estimated_display_amount.currency币种内嵌进金额
refund_preview.can_submit_cancellation_request / calculation_completerefund_preview.can_submit提交判定单字段;disabled_reason 说明退款输入为何不可用
active_cancellation.business_refund_amountactive_cancellation.financials.refund.amount申请金额
active_cancellation.business_refund_amountt(用户端详情页拼写多一个 t,实际恒为 0)active_cancellation.financials.refund.amount迁移时顺带修掉拼写;否则该处显示始终为 0
active_cancellation.business_paid_amountactive_cancellation.financials.paid.amount—
active_cancellation.standard_maximum_refundable_amountactive_cancellation.financials.refund_limits.standard.amount—
active_cancellation.absolute_maximum_refundable_amountactive_cancellation.financials.refund_limits.maximum.amount输入上限语义同前
active_cancellation.role_view.*(画师卡片明细)同上规则:金额改指 financials.*,平台费拆解改指 role_view.platform_fee_breakdown.*—
work_task.destinations顶层 destinations去向属于本次退款
work_task.business_currency各金额自带的 currency—
金额硬编码 / 100依据 currency.is_zero_decimal 决定小数位修正零小数位币种(如 JPY)显示
改价:old_price / new_price / need_pay_amount / work_task_paid_amount(数字)同名 Money(读取 .amount)价格改为金额对象
改价:business_refund_amountprice_change.financials.refund.amount—
改价:old_price_money / new_price_money移除(直接用 old_price / new_price)—
改价 UI「后续支付手续费预估」绑定 price_change.financials.payment_feeprice_change.financials.future_payment_fee_estimatepayment_fee 是已产生的实际手续费,不能重复用于未来预估

保持不变:请求参数 business_refund_amount(数字,业务币种最小单位)、refund_destination、price、idempotency_key;错误码与 HTTP 语义;active_cancellation.id / initiator_role / viewer_is_initiator / can_withdraw / can_accept / can_reject;commission_refund_id;取消对象与改价对象的 status(退款债权没有单轴 status)。


12. 常见误解(FAQ)

Q:退款额输入上限为什么不是 financials.refund_limits.absolute? A:absolute 是理论上限(P − R),只有全部有效 Order 都是 Stripe 关联账户收款时才能达到;其他场景提交超过 maximum 的金额会返回 22004。上限只用 maximum(见 §3.1)。

Q:退款完成后为什么 financials.paid 没有减少? A:paid 是「终身累计已付」这一支付事实,退款不会回写它;当前可退额度由 refund_limits 与 previous_refunded 表达(见 §3.1)。

Q:disabled_reason = no_refundable_amount 时还能提交吗? A:能。它只表示退款额输入不可用(未付款或已无可退额度),零退款取消与改价仍然合法;能否提交看 can_submit(见 §4.5)。

Q:用户端为什么能看到画师的平台费与净收入? A:financials 是双方的共同事实层,不做角色裁剪;角色专属信息(画师平台费拆解、用户偏好币种估算与网关/Credit 投影)在 role_view。是否展示由 UI 决定(见 §4.6)。

Q:为什么 destinations.available 只有 original? A:Commission 中存在 Stripe 关联账户收款(destination charge)Order;原因码在 destinations.unavailable.credit(见 §3.2、§4.4)。

Q:退款为什么长时间停在 processing? A:对账阶段需要读取 Provider 账本;Provider 尚未结算或账本暂不可用时后端会保持 processing 并自动重试,属正常状态,不要重复提交或引导用户重试(见 §3.4、§7.4)。

Q:客户已经收到退款,为什么 Financial Resolution 仍是 manual_review? A:客户退款履行与画师/平台资金回收是两组事实。obligation_status = fulfilled 表示客户退款已完成;Transfer 冲正、画师追缴或对账未完成时,Financial Resolution 仍可进入 manual_review。前端不能把它显示成客户退款失败(见 §4.8)。

Q:多笔退款的批量去向为什么是空数组? A:每笔债权在创建时冻结自己的可用去向,多笔债权可能没有共同去向。refund_pool.bulk_allowed_destinations = [] 是合法结果,应禁用批量入口并让用户逐笔选择,不要自行回退到 Credit 或原路退款(见 §6.5)。

Q:worktask/info 的 refund_pool 里为什么没有每笔退款的 id? A:refund_pool 是摘要读模型,不是债权列表。批量选择要用的 id 在 refund_pool.action_required_refund_ids,它与 bulk_allowed_destinations 是同一集合、可直接提交;要渲染每一笔债权的金额、cash_destinations.allowed 与执行状态,读 commission_refunds/list 的 data[](其 summary 与 refund_pool 同构,见 §4、§6.5)。

Q:改价 status = paid 了,但退款没完成? A:paid 只表示新价格已应用;退款是独立资源,请用改价对象的 commission_refund_id 查退款状态(见 §4.8)。

Q:用户降价时必须选择退款去向吗? A:不必须。work_task_price_changes/create 的 refund_destination 是可选偏好。未填写或降价生效时偏好已不可用,改价仍然生效,退款债权进入 awaiting_destination,再由用户通过退款领域接口选择(见 §6.6、§7.3)。

Q:为什么改价对象和退款对象都有 financials.refund? A:所属领域不同。改价对象中的值是“该价格方案生效后预计产生的退款额”;CommissionRefund 中的值是已经创建并预留资金的债权金额。前端用 commission_refund_id 从改价领域跳转到退款领域,退款执行状态只读后者(见 §7.3、§8)。

Q:改价里的 payment_fee 和 future_payment_fee_estimate 有什么区别? A:payment_fee 汇总已付款 Order 实际发生的手续费;future_payment_fee_estimate 按画师 pay_fee_ratio 估算变更后全部未付款稿酬的手续费。前者是事实,后者是预测,不能复用同一字段(见 §3.6、§6.6)。

Q:为什么改价的平台费不是先减支付手续费再乘费率? A:Commission 正常完成结算以总稿酬为平台费基数,支付手续费和平台费分别扣除。因此改价的 platform_fee 按 new_price 与画师数据库费率进入平台费政策计算;费率不是固定 5%(见 §3.6)。

Q:Preview 里为什么看不到 orders、stripe、calculation_snapshot? A:内部字段一律不返回;需要执行进度用 commission_refunds/info 的 execution.steps(见 §4.9、§6.5)。

Q:金额是不是都除以 100? A:不要硬编码。每个金额自带 currency.is_zero_decimal:false 除以 100 显示两位小数,true 直接显示整数(见 §2.3)。


13. 执行可靠性与发布

业务事实与资金执行分离。取消接受、降价生效或 Commission 完成时,接口先以 HTTP 200 返回已经落库的业务结果;退款、追缴、对账和画师结算随后通过 Financial Resolution 异步收尾。

13.1 冻结结算与依赖

  • 正常完成 Commission 时,后端在记录决议的同一事务内冻结 v2 结算计划。计划固定费率、费用政策、支付手续费、目标金额、历史已结算金额和本次差额;最终钱包操作只执行 目标金额 − 历史已结算金额,不会重复发放已经结算的部分。
  • 执行前锁定 WorkTask、已付 Order、相关钱包和抵扣记录并逐项核对。金额、币种、归属、订单手续费或指纹不一致时,本次资金写入整体回滚,Financial Resolution 转为 manual_review。
  • 非 CNY 历史已结算金额只有在能映射到同一业务币种资金事实时才自动计算差额;无法证明一致时进入 manual_review,不会猜测跨币种金额。
  • 退款、追缴或对账依赖尚未满足属于正常等待,不写成业务失败,也不会绕过依赖提前结算画师。

13.2 Outbox、幂等与恢复

本节描述的是基础设施可靠性,不是新增的前端业务领域或接口。Outbox 可以理解为数据库里的“可靠待办”:后端在保存取消、降价或财务决议的同一个事务中,也保存一条后续资金任务。这样即使接口返回后服务立刻重启,任务也不会只存在于内存队列里而丢失。完整术语解释见 §2.5。

后台执行顺序:

  1. 业务事务同时保存业务结果、财务决议和 Outbox 待办。
  2. Outbox 扫描器为待办取得短时租约,再投递 Queue Job;租约避免多个 Worker 同时处理同一条待办。
  3. Job 执行退款、资金回收或结算步骤。重复投递会被幂等键、已落库的执行事实和数据库行锁拦住,不会因此重复动钱。
  4. Provider 已受理、但账本暂未确认时,步骤保持 processing。后端保留已成功的事实并继续对账,不会把“暂时查不到最终结果”当作失败后重新退款。
  5. 对账确认后由 Finalizer 统一收尾:更新客户退款义务,核销或释放资金预留,并继续推进 Financial Resolution。
  6. Outbox 和业务恢复扫描都有有限重试。默认陈旧阈值为 2400 秒(高于连接级 retry_after 与退款步骤的 300 秒租约),业务恢复最多重新投递 5 次;仍无法安全恢复时进入 manual_review,停止自动动钱。
  7. 客户退款已经 fulfilled、但收尾投影(取消 hand-over、依赖 satisfied_at)因进程崩溃或投递耗尽而缺失时,恢复扫描与人工 resume 会按已持久化终态补齐这些投影,不会重新向客户退款;归属不一致时保留客户 fulfilled,把异常写入 Refund 诊断并把关联 Financial Resolution 置为 manual_review。
  8. 已经停放 cancellation_handover_failed 的债权会被排除在自动收尾扫描之外,不再反复占用每轮扫描预算;它们仍可由人工受控的 reconcile / resume 处理,收尾成功后该 blocker 被清除。已经被并发 worker 标记为 completed 的 Financial Resolution 是终态:收尾失败只写 Refund 诊断,绝不把它降级回 manual_review。
  9. fulfilled 取消退款的恢复身份来自不可变的 trigger_type + trigger_id,不依赖待修复的 commission_refund_id 投影:该投影为空、错指或对应取消行缺失时,扫描仍能发现并安全停放。cancellation_handover_failed 只表示已证明的持久领域冲突;数据库死锁、连接/查询错误、类型错误和其它未知系统异常会整体回滚并保留自动重试能力,不会被伪装成人工审核事实。单条被 deferred 的记录不消耗本轮修复预算,也不阻止更高 ID 的候选被处理。

对前端而言,只需要遵守三个边界:HTTP 200 表示业务结果已经保存,不表示异步资金步骤已经全部完成;pending / processing 时按接口状态轮询;进入 manual_review 后停止自动操作,并分别读取退款债权与财务决议状态,不能统一显示成“客户退款失败”。

13.3 发布检查

  • 按版本号顺序执行本功能的全部数据库迁移:

    1. 2026_09_04_000001_create_commission_cancellation_refund_tables
    2. 2026_09_11_000001_create_commission_financial_resolution_tables
    3. 2026_09_11_000002_allow_multiple_open_commission_refunds
    4. 2026_09_14_000001_add_execution_facts_to_commission_financial_resolutions
    5. 2026_09_15_235959_backfill_legacy_commission_refund_allocations
    6. 2026_09_16_000001_finalize_commission_refund_schema
    7. 2026_09_17_000001_add_requested_refund_destination_to_commission_cancellations
    8. 2026_09_17_000002_create_commission_financial_operation_logs
    9. 2026_09_17_000003_normalize_voided_commission_refund_execution_status
    10. 2026_09_17_000004_drop_unused_commission_refund_retry_notification_columns
  • 已运行过历史 migration 的测试数据库只需普通 php artisan migrate(保持上述顺序),由正向迁移先校验数据再改结构,不需要 migrate:fresh。

  • 迁移采用 expand → backfill → validate → contract:先加字段/表并回填,再让代码依赖,最后删旧字段。任何无法证明数据正确的步骤都会失败关闭:迁移中止并输出异常行数量与样例 ID,交人工核查;修好数据后重新执行普通 php artisan migrate 即可继续,不依赖 fresh。

  • 数据迁移的 down() 不回退已经形成的财务历史(例如 2026_09_17_000003 明确 no-op),避免把终态事实猜回一个错误标签。

  • commission_cancellations.requested_refund_destination 仅为服务端幂等条款的不可变事实,不是公开 API 字段,不会出现在取消对象里;对外仍然只读 refund_destination(实际冻结去向)。

  • 队列时序不变量:retry_after 是连接级参数,必须大于同一连接上所有 Job 的 max(timeout)。本应用 default / payments / translate / compress_image worker 共用 QUEUE_CONNECTION,最长 Job timeout 为 1800 秒(TranslateJob、CompressImage),因此仓库默认值取 2100 秒(DB_QUEUE_RETRY_AFTER / BEANSTALKD_QUEUE_RETRY_AFTER / REDIS_QUEUE_RETRY_AFTER);COMMISSION_RECOVERY_STALE_SECONDS 默认 2400 秒,高于 retry_after 与 300 秒步骤租约。发布前必须核对生产/测试环境的真实 override 满足同一关系,单元测试只能证明仓库默认值,不能证明服务器变量已配置。

  • 保持 Outbox 扫描每分钟运行,财务恢复扫描每五分钟运行,并确保队列 Worker 正常消费。

  • 当前没有“3~7 天后自动选择去向或自动退款”。未选择去向的债权继续留在退款池,等待用户明确选择。


附录 A:完整响应示例

每个示例都标注了它演示什么,可直接对照复制。

A.1 取消 Preview(用户端,平台收款场景)

演示:平台收款 + 已付款,退款额等于标准上限,refund_policy 表明可退 Credit(maximum 与 standard 相等)。

{
  "data": {
    "mode": "negotiated",
    "viewer_role": "user",
    "counterparty_role": "artist",
    "requires_counterparty_confirmation": true,
    "work_task": {
      "id": 2210,
      "status": "working",
      "contract_amount": { "amount": 27000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
    },
    "payment_state": "paid",
    "calculation_version": 14,
    "can_submit": true,
    "disabled_reason": null,
    "refund_entry": {
      "presets": {
        "unfinished_stages": { "amount": 8100, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "standard": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "full": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "fee_policy": "payment_fee_standard_cap_then_post_refund_platform_fee"
    },
    "financials": {
      "paid": { "amount": 13500, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "previous_refunded": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "retained": { "amount": 1120, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund_limits": {
        "standard": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "maximum": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "absolute": { "amount": 13500, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "payment_fee": { "amount": 1120, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_fee_coverage": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_settlement_basis": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "platform_fee": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "estimated_artist_net": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
    },
    "refund_policy": {
      "maximum_policy": "standard",
      "can_exceed_standard": false,
      "can_refund_to_credit": true,
      "has_stripe_connected_order": false,
      "all_orders_stripe_connected": false,
      "has_unknown_order": false
    },
    "destinations": { "available": ["original", "credit"], "unavailable": {} },
    "role_view": {
      "estimated_display_amount": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "gateway_refunds": [{ "amount": 1883, "currency": { "id": 2, "code": "USD", "symbol": "US$", "is_zero_decimal": false } }],
      "credit_refunds": [],
      "estimate_notice": "Gateway refunds use the refund-time rate; Credit refunds use the locked order value."
    },
    "warnings": [],
    "issues": []
  }
}

A.2 取消 Preview(纯 Stripe 关联账户场景片段)

演示:纯 Stripe 关联账户收款时 maximum 大于 standard、credit 被禁用、超出部分计入画师承担额。

片段示例,currency: {} 为省略占位,实际返回完整币种对象。

{
  "financials": {
    "refund_limits": {
      "standard": { "amount": 9200, "currency": {} },
      "maximum": { "amount": 10000, "currency": {} },
      "absolute": { "amount": 10000, "currency": {} }
    },
    "artist_fee_coverage": { "amount": 800, "currency": {} }
  },
  "refund_policy": {
    "maximum_policy": "stripe_connected_absolute",
    "can_exceed_standard": true,
    "can_refund_to_credit": false,
    "has_stripe_connected_order": true,
    "all_orders_stripe_connected": true,
    "has_unknown_order": false
  },
  "destinations": { "available": ["original"], "unavailable": { "credit": "stripe_connected_account_present" } }
}

A.3 取消对象(完整形态,用户视角)

演示:取消对象的完整形态——摘要字段 + currency_id + refund(退款对象)+ settlement(结算步骤)+ 时间戳。

{
  "data": {
    "id": 91,
    "work_task_id": 2210,
    "mode": "negotiated",
    "status": "refund_processing",
    "initiator_role": "user",
    "viewer_is_initiator": true,
    "financials": {
      "paid": { "amount": 13500, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "previous_refunded": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "retained": { "amount": 1120, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund_limits": {
        "standard": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "maximum": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "absolute": { "amount": 13500, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "payment_fee": { "amount": 1120, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_fee_coverage": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_settlement_basis": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "platform_fee": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "estimated_artist_net": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
    },
    "refund_policy": {
      "maximum_policy": "standard",
      "can_exceed_standard": false,
      "can_refund_to_credit": true,
      "has_stripe_connected_order": false,
      "all_orders_stripe_connected": false,
      "has_unknown_order": false
    },
    "refund_destination": "original",
    "destinations": { "available": ["original", "credit"], "unavailable": {} },
    "role_view": {
      "estimated_display_amount": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "gateway_refunds": [],
      "credit_refunds": [{ "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }],
      "estimate_notice": "Gateway refunds use the refund-time rate; Credit refunds use the locked order value."
    },
    "can_withdraw": false,
    "can_accept": false,
    "can_reject": false,
    "failure_code": null,
    "failure_stage": null,
    "created_at": "2026-09-09T03:00:00.000000Z",
    "updated_at": "2026-09-09T03:05:00.000000Z",
    "currency_id": 31,
    "refund": {
      "id": 301,
      "work_task_id": 2210,
      "trigger_type": "cancellation",
      "trigger_id": 91,
      "obligation_status": "open",
      "execution_status": "processing",
      "cash_destination": "original",
      "preference_applied": true,
      "currency_id": 31,
      "cash_destinations": { "allowed": ["original", "credit"], "unavailable": {} },
      "financials": {
        "refund": { "amount": 12380, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "refund_policy": {},
      "execution": {
        "attempts": 1,
        "total_steps": 2,
        "succeeded_steps": 1,
        "steps": [
          { "type": "stripe_refund", "status": "succeeded", "financial_effect": "customer_fulfillment" },
          { "type": "user_credit_refund", "status": "processing", "financial_effect": "customer_fulfillment" }
        ],
        "readiness": "ready",
        "blocked_reason": null,
        "failure_code": null,
        "manual_review_reason": null
      },
      "action_required_at": null,
      "created_at": "2026-09-09T03:01:00.000000Z",
      "completed_at": null
    },
    "settlement": { "total_steps": 0, "succeeded_steps": 0, "steps": [] },
    "accepted_at": "2026-09-09T03:01:00.000000Z",
    "completed_at": null
  }
}

说明:内层 refund.financials、refund.refund_policy 的结构与上文一致,此处为节省篇幅省略其余字段。

A.4 改价对象(详情/列表元素)

演示:Commission 已付 CN80.00、实际手续费 CN5.49,由 CN80.00 加价到 CN400.00;画师平台费率为 10%,后续支付手续费预估率为 4%。payment_fee 与 future_payment_fee_estimate 分开返回,平台费按变更后总稿酬计算。

{
  "data": {
    "id": 401,
    "work_task_id": 2210,
    "currency_id": 31,
    "initiator_type": "user",
    "initiator_id": 31322,
    "approver_type": null,
    "approver_id": null,
    "old_price": { "amount": 8000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
    "new_price": { "amount": 40000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
    "need_pay_amount": { "amount": 32000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
    "work_task_paid_amount": { "amount": 8000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
    "status": "wait_pay",
    "commission_refund_id": null,
    "refund_destination": null,
    "financials": {
      "paid": { "amount": 8000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "previous_refunded": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "retained": { "amount": 8000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund_limits": {
        "standard": { "amount": 7451, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "maximum": { "amount": 7451, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "absolute": { "amount": 8000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "payment_fee": { "amount": 549, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "future_payment_fee_estimate": { "amount": 1280, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_fee_coverage": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_settlement_basis": { "amount": 40000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "platform_fee": { "amount": 4000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "estimated_artist_net": { "amount": 34171, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
    },
    "refund_policy": {
      "maximum_policy": "standard",
      "can_exceed_standard": false,
      "can_refund_to_credit": true,
      "has_stripe_connected_order": false,
      "all_orders_stripe_connected": false,
      "has_unknown_order": false
    },
    "destinations": { "available": [], "unavailable": {} },
    "approved_at": "2026-09-11T04:00:00.000000Z",
    "paid_at": null,
    "created_at": "2026-09-11T04:00:00.000000Z",
    "updated_at": "2026-09-11T04:00:00.000000Z"
  }
}

future_payment_fee_estimate = ceil((40000 − 8000) × 4%) = 1280;platform_fee = 40000 × 10% = 4000;estimated_artist_net = 40000 − 549 − 1280 − 4000 = 34171。最终手续费以实际支付 Order 为准。

A.5 未付款取消 Preview(直接取消)

演示:未付款场景——payment_state = unpaid、mode = direct、financials 全零但仍带币种、disabled_reason = no_refundable_amount。

{
  "data": {
    "mode": "direct",
    "viewer_role": "user",
    "counterparty_role": "artist",
    "requires_counterparty_confirmation": false,
    "work_task": {
      "id": 2210,
      "status": "wait_pay",
      "contract_amount": { "amount": 10000, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
    },
    "payment_state": "unpaid",
    "calculation_version": 14,
    "can_submit": true,
    "disabled_reason": "no_refundable_amount",
    "refund_entry": {
      "presets": {
        "unfinished_stages": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "standard": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "full": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "fee_policy": "payment_fee_standard_cap_then_post_refund_platform_fee"
    },
    "financials": {
      "paid": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "previous_refunded": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "retained": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "refund_limits": {
        "standard": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "maximum": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
        "absolute": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
      },
      "payment_fee": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_fee_coverage": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "artist_settlement_basis": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "platform_fee": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "estimated_artist_net": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } }
    },
    "refund_policy": {
      "maximum_policy": "standard",
      "can_exceed_standard": false,
      "can_refund_to_credit": false,
      "has_stripe_connected_order": false,
      "all_orders_stripe_connected": false,
      "has_unknown_order": false
    },
    "destinations": { "available": [], "unavailable": {} },
    "role_view": {
      "estimated_display_amount": { "amount": 0, "currency": { "id": 31, "code": "CNY", "symbol": "CN", "is_zero_decimal": false } },
      "gateway_refunds": [],
      "credit_refunds": [],
      "estimate_notice": "Gateway refunds use the refund-time rate; Credit refunds use the locked order value."
    },
    "warnings": [],
    "issues": []
  }
}

附录 B:请求参数速查

写请求时的查表;字段语义与边界见 §6。

参数类型/范围出现在
idinteger ≥ 1取消 preview/info/request、退款 info、改价 info/approve/reject/cancel
request_idinteger ≥ 1取消 withdraw/reject/accept
work_task_idinteger,必须存在退款 list/select_destinations、改价 preview/create/list
refund_idsinteger[],至少 1 个且不可重复退款 select_destinations
business_refund_amountinteger ≥ 0取消 preview/request(条件必填)
refund_destinationoriginal | credit用户取消 request/accept(可选提前偏好,省略则进入 awaiting_destination)、退款 select_destination/select_destinations(必填)、用户改价 create(降价时可选提前偏好)。画师取消 request/accept 与画师改价 create 均不接受
idempotency_keystring 8–128取消 request
priceinteger/numeric ≥ 0改价 preview/create
page / size≥ 1 / 1–50退款 list、改价 list
ON THIS PAGE