退款政策与金额口径:最低退款门槛、两次退款限制、碎额抹平与改价纯差额 (2026-09-26)

需求背景

  • 来源:2026-09-28 退款规则答疑会议(Speaker 1 的 Q1–Q4)+ 任务 09-23-refund-zero-source-rounding(PRD prd.md)、09-24-price-change-refund-pure-difference(PRD prd.md)与 09-30-refund-write-off-cap-strategy(PRD prd.md);2026-09-26 首次发布小额退款政策,2026-09-27 发布改价纯差额,2026-09-28 评审合并为「退款政策与金额口径」统一文档,2026-09-30 迭代为「按支付 Order 独立碎额上限 + 两次退款与最终关闭」。
  • 动机:此前跨币种按比例向下取整会产生「业务金额为正、原币金额为零」的分配,preview 可能允许提交而预留/执行拒绝;小额剩余也没有统一规则,用户需要在多个不连续区间试探。同时,改价降价退款一度按「扣除不可退支付手续费后的有效上限减新价格」计算,把本应留给客户的差额扣成了手续费。本期用一套可解释的规则收口:每笔 Commission 最多两次有效退款、正额退款不得低于平台最低退款金额、无法执行的极小碎额按上限抹平,改价降价按业务纯差额退款并增加新价下限。
  • 范围:取消(cancellation/preview|request|accept)与改价(work_task_price_changes/preview|create|approve)两条既有退款入口新增政策字段与政策错误码,引入「按支付 Order 独立的碎额上限」「final closure 逐来源关闭」与终局 refund_closed 状态;本次不新增任意金额退款入口、不改退款去向与渠道能力(保留 original / credit 双去向)、不改含手续费全额退款入口的开放范围,也不修改前端源码(前端仓库只读,仅提供对接交接)。
  • 面向读者:前端(用户端 + 画师端)、联调、测试。
  • 术语对照:
    • commission = WorkTask(委托);commissions 是 worktask 的对外展示名;credit = 站内 Credit 钱包。
    • 最低退款金额(符号 T;refund_policy.minimum_refund_amount)= 平台最低退款金额,默认等值 USD 0.10;换算成业务币时向上取整。
    • 剩余可退金额(符号 M;refund_policy.remaining_refundable_amount)= 当前剩余可退金额,等于 financials.refund_limits.maximum。
    • 由改价差额算出的退款额(符号 R)= 改价降价时后端按「未占用已付金额 − 新价格」算出的退款额,写在 financials.refund.amount;它不是用户直接输入。
    • 整笔 = 同一笔 Commission 的全部剩余可退金额(即 M)。
    • 最终关闭(final closure)= 第一次直接全退(R = M)与第二次退款共用的执行模式:逐来源按账本事实退回剩余原币净额 S(该来源仍对应客户可退权益的剩余业务容量记为 B),不再运行普通比例分配、当前汇率换算与碎额准入;R = M 全退不受最低退款金额限制。
    • 碎额 = 无法按资金来源原币最小单位执行的极小尾差,分「本次请求碎额」与「来源残余碎额」两类:
      • 本次请求碎额(scope = 'request',reason = 'rounding_zero'):本次退款分配给某资金来源时,因汇率换算与最小货币单位向下取整导致「业务币为正(如 1 分钱),但底层原币向下取整为 0」的尾差。它包含在本次请求退款额内,但无法发往底层渠道执行,由平台在账面按抹平核销,实际出款给客户时扣除该金额。
      • 来源残余碎额(scope = 'residual',reason = 'unexecutable_fragment'):本次退款执行后,某资金来源在底层原币已无可用余额(原币剩余 ≤ 0),但业务币账面仍残留的微小尾数。该尾数不属于本次请求(不影响本次到账),但在原币下已永久无法单独执行,由平台在本次一并建立抹平计划彻底核销。
    • 抹平(write-off)= 平台按政策不支付给用户、但永久占用来源可退容量的极小尾差;它是平台自定规则,不代表任何支付渠道的固定最低退款额。
    • 执行碎额上限(符号 C;refund_policy.write_off_cap_amount)= 每个支付 Order 各自可抹平的碎额合计上限,默认等值 USD 0.01;换算成业务币时向下取整。同一 Order 内的 Gateway 与所有 Wallet 来源先按碎额类型聚合,不会为每条来源各放一份上限;不同 Order 的额度互不借用、也不跨 Order 合计阻断。
    • 不可退支付手续费(符号 F)= 订单已发生的实际不可退支付手续费合计,financials.payment_fee.amount。
    • 新价格(符号 N)= 改价发起方填写的新稿酬;其下限为 N ≥ F(见 4.3)。

建议阅读顺序:第 1 章(一分钟上手,含会议疑问 FAQ)→ 第 2 章(接口变更)→ 第 3 章(接口示例)→ 第 4 章(字段与规则补充说明)→ 第 5 章(兼容性说明)。遇到「这个金额为什么被拒」先看第 1.3 节 FAQ 与第 4.2 节。

目录

章内容什么时候看
1.0 符号速查R / M / T / C / N / F / B / S 等符号的含义与对应响应字段看到符号不知道什么意思
1. 一分钟上手退款次数状态机、小额与碎额流程、会议疑问 FAQ、对接要点第一次接触本规则
2. 接口变更改了哪些接口定位影响面
3. 接口示例字段与错误示例写绑定 / 联调
4. 字段与规则补充说明每个字段含义、改价纯差额与前端交接对齐口径
5. 兼容性说明上线协同与旧快照行为发布前

1. 一分钟上手

面向读者:前端(用户端 + 画师端)、联调、测试。本文描述的是开发阶段契约,新增字段为向后兼容增量,错误码为新增。建议先看图再看 FAQ,最后翻字段表。

1.0 符号速查

下文流程图、公式与 FAQ 使用少量单字母符号,含义与后端已落地实现一致。符号只用于阅读,前端对接一律以后端同次响应的字段为准。

符号读作含义与响应字段的对应
T最低退款金额平台最低退款金额,默认等值 USD 0.10;换算成业务币时向上取整。只限制第一次正额部分退款,全退(R = M)不受 T 限制refund_policy.minimum_refund_amount
C执行碎额上限每个支付 Order 各自可抹平的碎额合计上限,默认等值 USD 0.01;换算成业务币时向下取整。同一 Order 内的 Gateway 与所有 Wallet 来源先按碎额类型聚合,不同 Order 的额度互不借用refund_policy.write_off_cap_amount
M剩余可退金额当前剩余可退金额 = 已付业务金额 − 不可退支付手续费 − 历史实际退款 − 已应用抹平 − 其他有效占用refund_policy.remaining_refundable_amount(等于 financials.refund_limits.maximum)
R本次请求退款金额本次退款请求额(业务币):改价场景由新价格派生,取消场景由双方协商;R = M 表示全退financials.refund.amount
N新价格改价发起方填写的新稿酬,下限为 N ≥ F请求参数 price;响应 data.new_price
F不可退支付手续费该约稿已发生的实际不可退支付手续费合计financials.payment_fee.amount
B剩余业务容量final closure 时某个资金来源仍对应客户可退权益的剩余业务币容量(B > 0 才可能退款)无独立响应字段,由后端账本事实派生
S剩余原币净额final closure 时某个资金来源的剩余原币净额,已扣不可退原币手续费、历史实际退款与有效预留无独立响应字段,由后端账本事实派生

T / C 是平台自定规则,不代表任何支付渠道的最低金额或限额;B / S 只在 final closure(首次全退 R = M 与第二次退款)内部使用,不额外暴露给前端。第 4.3 节的改价纯差额公式另用局部变量 P(累计实付)、O(已占用容量)、U(未占用已付金额),与上表的 C(执行碎额上限)、B(final closure 剩余业务容量)不是同一个量。

1.1 退款次数状态机

一笔 Commission 生命周期内最多两次有效退款:第一次可以部分退款,第二次只能退回当前剩余全部可退金额。失败重试与幂等重放不额外计数;升价补款与无退款改价不计数,补款也不会重置次数。第一次直接全退(R = M)不关闭委托;第二次退款在全部资金动作与对账完成后把委托置为终局 refund_closed,不再接受新付款与退款。本节与下图用到 M(剩余可退金额)、R(本次请求退款金额)、T(最低退款金额)、C(每个支付 Order 的执行碎额上限),定义见 1.0 符号速查。

1.2 小额余额与碎额处理流程

下图为一次退款从试算到收口的完整分支,用到 M(剩余可退金额)、R(本次请求退款金额)、T(最低退款金额)与 C(每个支付 Order 的执行碎额上限);符号定义见 1.0 符号速查。

整笔余额满足 0 < M < T 时不再自动抹平:M 保持开放,R = M 全退走 final closure 分支(豁免最低退款金额)。未被申请的余额由取消 / 正常完成时的终局结算收口;取消是终局决定,第二次退款后的 refund_closed 关闭只作用于改价等继续场景,取消路径仍为 user_canceled。

政策金额的取值来源(T = 最低退款金额、C = 执行碎额上限,两者来自平台后台配置并换算成业务币,定义见 1.0 符号速查):

1.3 会议疑问 FAQ(Q1–Q4)

以下四条疑问来自 2026-09-28 会议 Speaker 1 的原始提问,逐条给出结论、依据字段/错误码与对应示例。(本节继续使用 1.0 符号速查中的 T / C / M / R / N / F,读到不熟悉的符号可直接对照该表。)

Q1:校验的是本次请求退款额,还是退款发生后的剩余可退金额?已付 100 改为 99.99 会报什么?

结论:门槛校验的是本次请求退款额,不是「退款后的余额」。以已付 100.00、不可退支付手续费 3.00 为例(当前剩余可退金额上限为 100.00 − 3.00 = 97.00):当新价改为 99.99 时,由改价差额算出的退款额为 0.01(100.00 − 99.99 = 0.01),低于最低退款金额 0.10,因此被拒:预览 can_submit = false、disabled_reason = below_minimum_amount,正式提交返回 22022。但若派生退款额恰好等于当前剩余全部可退金额(R = M,全退),则豁免最低退款金额,即使 M < 0.10 也可提交。

依据字段 / 错误码:financials.refund.amount(本次请求退款额)、refund_policy.minimum_refund_amount、disabled_reason = below_minimum_amount、22022(CommissionRefundBelowMinimumAmount)。disabled_reason = below_minimum_amount 现在只出现在改价入口(派生退款额既低于门槛、又不等于剩余全部);取消入口的试算不再因整笔剩余低于门槛而禁用输入——用户始终可以选择 R = M 全退。

对应示例:3.0 节场景 1、场景 3、场景 4、场景 11。

请注意:不是「退款后的余额小于门槛就拒绝本次退款」。退款后余额低于门槛不会拒绝本次退款,也不会被自动抹平:余额保持开放,用户可以随时以 R = M 全退;未被申请的余额由终局结算收口(见 Q2)。

Q2:新价格的下限是多少?退 9.90 与退 9.91 有什么区别?

结论:两件事要分开看。

  • 改价新价格(N)的下限是 N ≥ F(F = 订单已发生的实际不可退支付手续费合计)。N < F 时预览 disabled_reason = below_payment_fee、can_submit = false,正式提交返回 60001(WorkTaskPriceChangeInvalid)。
  • 本次请求退款额的门槛是另一条独立规则:本次请求退款额 ≥ 最低退款金额。
  • 剩余 10.00 退 9.90 → 退款后余额 0.10,恰等于最低退款金额:本次退款满足门槛,放行;余额 0.10 之后仍可退。
  • 剩余 10.00 退 9.91 → 退款后余额 0.09,低于最低退款金额:本次退款不因此被拒(9.91 ≥ 0.10)。余额 0.09 保持开放且不再自动抹平;用户仍可发起 R = M 的全退(0.09 低于门槛也被豁免),或由取消 / 正常完成时的终局结算收口。前端无需引导用户再次提交,可按需展示「剩余 0.09 可全额退完」的引导。

依据字段 / 错误码:refund_policy.minimum_refund_amount、remaining_refundable_amount、financials.payment_fee.amount、disabled_reason = below_minimum_amount / below_payment_fee、22022、60001。

对应示例:3.0 节场景 2、场景 3、场景 4、场景 5、场景 6。

不存在「离散禁退区间」:平台不会要求每个来源余款都达到最低退款金额,也不会为保护未来的退款而拒绝本次合法退款。

Q3:「本次请求碎额」与「来源残余碎额」是什么?前端该如何理解与提示?

结论:碎额是指因货币最小精度或向下取整导致无法在支付渠道原币正常执行的极小尾差(通常等值 1 分钱)。系统将其分为两类不同性质的碎额;每个支付 Order 各有一份执行碎额上限(默认等值 USD 0.01),同一 Order 内的 Gateway 与所有 Wallet 来源先按类型聚合后再判断:

  1. 本次请求碎额(Request Write-Off Fragment):

    • 是什么:本次退款拆分到某个资金来源时,因汇率换算与向下取整,出现业务币金额为正(如 1 分钱),但底层原币金额向下取整后变成了 0。底层支付网关(Stripe、PayPal 等)严格禁止发起 0 元退款;
    • 对到账影响:它属于本次请求额,但无法发往底层渠道,由平台在账面抹平。客户实际收到的退款 = 本次请求退款额 − 本次请求碎额;
    • 阻断规则:属于本次请求自身携带的缺陷。如果同一个支付 Order 内的本次请求碎额合计超过该 Order 的上限(默认 USD 0.01),或者业务币缺少 USD 汇率无法确定上限,必须阻断本次提交(can_submit = false,disabled_reason = write_off_cap_exceeded 或 exchange_rate_unavailable)。一个 Order 超限不会被另一个 Order 的剩余额度抵消,也不会把多个 Order 的碎额合并成一次阻断。
  2. 来源残余碎额(Residual Write-Off Fragment):

    • 是什么:本次退款扣减后,某个资金来源在底层原币已全部退尽归零(source_amount 剩余 ≤ 0),但受多期换算精度影响,该来源在业务币账面上仍残留着 1 分钱的死余数。由于底层原币已无款可退,未来任何退款都无法再从该来源执行;
    • 对到账影响:它不属于本次请求退款额(属于本次退款后的未来剩余)。完全不影响本次实际到账金额;系统只是在本次退款时顺手将其建立抹平计划、永久核销该来源容量,清除账面死钱;
    • 阻断规则:属于退款后的残留物,并非本次请求的错误。即使某 Order 的来源残余碎额超过该 Order 上限,也绝不阻断本次退款(只要本次请求自身无碎额,依然 can_submit = true),避免「为了保护未来的残差,反而拒绝用户当下完全合法的退款」;该 Order 超限的残余本次不建抹平计划、延期到终局收口,不会被静默吞掉。

前端提示建议:

  • 前端无需向普通用户暴露复杂的算法术语,按 disabled_reason 提示即可:
    • 当 disabled_reason === 'write_off_cap_exceeded' 时,提示:「本次退款的小额尾差超过平台可抹平上限,请调整金额或联系客服」。
    • 当 disabled_reason === 'exchange_rate_unavailable' 时,提示:「当前币种汇率暂不可用,暂不支持自动处理小额尾差,请稍后重试」。
  • 如果试算成功且存在抹平(write_off_explained === true),可在明细说明中展示:「含极小系统抹平尾差 $0.01(实际到账以各渠道出款为准)」。

依据字段 / 错误码:refund_policy.write_off_cap_amount、estimated_write_off_amount、write_off_cap_exceeded、write_off_explained、exchange_rate_unavailable。

对应示例:3.0 节场景 7–场景 11。

核心记忆口诀:

  • 本次请求碎额:本次退款里的钱,渠道退不出 0 元,平台账面抹平它,客户实收扣减它,同一 Order 内超限才阻断本次。
  • 来源残余碎额:本次退完后的残余(底层原币已退尽),平台顺手核销它,客户实收不扣它,超限不阻断本次,延期到终局收口。

Q4:是否只能降价一次?第二次降价是不是等于取消?

结论:计的是有效退款次数(非作废退款单最多 2 笔),不是改价次数。

  • 第一次可以部分退款;第二次必须退回当前剩余全部可退金额,提交部分金额返回 22024(CommissionRefundFinalRefundMustBeRemaining),预览对应 disabled_reason = final_refund_must_be_remaining;第二次退款全部来源成功对账后 WorkTask 置 refund_closed,不再接受付款与退款。第三次返回 22023(CommissionRefundAttemptsExhausted),但它现在只是异常数据防御——正常流程下 WorkTask 已在第二次退款时关闭。
  • 升价补款与无退款改价(退款额 0)不占次数;补款不重置次数,第二次仍只能退剩余全部。
  • 第二次降价不等于取消:它仍是一次改价,平台不会自动把新价格改到不可退支付手续费,也不会自动转为取消;只是新价格必须满足 N ≥ F。当 N = F 时刚好把剩余可退金额全部退完,但新价格仍是发起方填写的新稿酬。

依据字段 / 错误码:refund_policy.attempts_used、attempts_remaining、final_refund_only、disabled_reason、22023、22024、60001、work_tasks.status = refund_closed。

对应示例:3.0 节场景 12–场景 16。

1.4 对接要点

(本节与第 3 章的 R / M / T / C 均见 1.0 符号速查。)

  • 政策字段与禁用原因以后端同次响应为准:POST …/cancellation/preview 与 POST …/work_task_price_changes/preview 的 refund_policy 直接给出 minimum_refund_amount、remaining_refundable_amount、attempts_used、attempts_remaining、final_refund_only、write_off_cap_amount、estimated_write_off_amount、write_off_cap_exceeded、write_off_explained、exchange_rate_unavailable;同一个 Commission 的改价对象、退款债权与取消对象也返回同一组政策字段,前端不要自行重算门槛、次数或手续费。
  • disabled_reason 只描述退款额输入 / 本次固定退款是否可用,与 can_submit 相互独立:
    • below_minimum_amount:仅改价入口返回,表示由改价差额算出的退款额低于最低退款金额且不等于剩余全部(提交禁用);取消入口不再因整笔剩余低于门槛而禁用输入,用户始终可选 R = M 全退。
    • refund_attempts_exhausted:两次机会已用完(正常流程下 WorkTask 已 refund_closed,此原因仅异常数据防御)。
    • final_refund_must_be_remaining:第二次由改价差额算出的退款额不等于剩余全部可退金额。
    • write_off_cap_exceeded:本次退款中某个支付 Order 的请求碎额合计超过该 Order 的执行碎额上限,预览即阻断;仅另一个 Order 的残余碎额超限不产生本原因、不阻断本次合法退款。
    • exchange_rate_unavailable:该币种缺少 USD 汇率,换算不可用且禁止自动抹平。
    • below_payment_fee:改价新价格低于不可退支付手续费合计 F。
  • 全退(R = M)与第二次退款进入同一个 final closure:不判断最低退款金额、不做碎额准入、不依赖当前退款汇率,业务币缺少 USD 汇率也不影响全退;执行期间暂停创建新付款,全部资金来源成功对账后本次退款完成。
  • final closure 创建前必须没有进行中支付:存在 paying Order、待处理 checkout session 或未完成的改价补款时拒绝创建(22005),需等待其完成或失败收口后再提交;创建后到账的旧支付走人工复核(Stripe / Alipay 返回 21013,PayPal 返回 21001),不并入已确认计划。
  • final closure 的展示:全部来源都无可执行原币净额时仍走同一 final closure 完成退款(不返回 22026);final closure 的 final_closure 抹平不计入 estimated_write_off_amount(该字段只统计普通部分退款的碎额计划),也不使 write_off_explained 为 true。
  • 改价降价的退款额由新价格算出(不是用户直接输入):当由改价差额算出的退款额低于 minimum_refund_amount(且不等于剩余全部)、两次机会已用完、或第二次不等于剩余全部时,disabled_reason 给出对应原因且 can_submit = false(同一个新价格没有「零退款」的等价提交);升价补款与无退款改价(退款额 0)不受影响,仍为 can_submit = true。
  • attempts_remaining = 1(即 final_refund_only = true)时,第二次退款金额必须精确等于 remaining_refundable_amount;提交部分金额会收到 22024。
  • 金额字段统一为 { amount, currency } 结构,amount 是业务币最小单位整数;minimum_refund_amount 与 remaining_refundable_amount 都是平台规则值,不表示渠道最低金额。示例中的 USD 数值不得直接套用到人民币等其它币种。
  • 抹平金额与实退金额分开表达:estimated_write_off_amount 是预计不支付的极小尾差,只统计在生效上限内的抹平计划(本次请求碎额 + 本次可应用的来源残余碎额);某个 Order 超过该 Order 上限的残余不计入,它延期到终局收口,既不阻断本次也不被静默吞掉。整笔剩余低于最低退款金额时不再自动抹平:余额保持开放,R = M 全退始终可提交(全退豁免最低退款金额),未被申请的余额由取消 / 正常完成时的终局结算收口。
  • 终局与收款门禁:第二次退款全部资金动作(含客户退款、画师侧追回与渠道对账)完成后,work_tasks.status 变为 refund_closed,该状态不再接受付款、改价、取消与退款;首次全退处理期间暂停创建新付款,成功对账且委托仍可继续后恢复。普通部分退款不冻结收款(等待选择去向或执行中的拦截由既有付款校验规则承担)。付款入口在 refund_closed 时返回 22027(CommissionRefundClosedPaymentBlocked),在 final closure 资金动作未结清时返回 22013(CommissionRefundExecutionIncomplete)。
  • 错误码分支:22022 正额部分退款低于最低退款金额(不含全退 R = M);22023 两次机会已用完;22024 第二次必须退剩余全部;22025 抹平数据完整性校验失败(fail-closed,按系统异常处理);22026 正额部分退款的所有来源原币向下取整为 0(不创建、不占次数;final closure 纯抹平全退不走此拒绝);22027 委托已因最终退款关闭,不接受新付款;21013 冻结资金集合前发起的旧支付晚到账(Stripe / Alipay;PayPal 为 21001),需人工处理;改价新价格低于不可退支付手续费 60001;改价旧条款变化 60002。
  • 改价新价格下限:N ≥ F。N < F 时 preview 返回 disabled_reason = below_payment_fee,financials.payment_fee.amount 显示手续费金额;直接调用 create 返回 60001。
  • 幂等与重试:cancellation/request 使用 idempotency_key,重放或失败重试不消耗新次数、不重复抹平;已抹平的剩余不会再次成为可退余额。

1.5 相关文档导航(什么时候看哪一篇)

文档内容什么时候看
2026-09-09_commission_cancellation_and_refunds.md取消/改价/退款领域模型、字段字典、端点详解(financials、退款上限、去向能力)需要整体领域模型或字段全表
2026-09-21_commission_refund_notifications.md退款通知与页面事件(站内信场景、worktask_page_event_list.type)对接通知与待办入口
2026-09-22_commission_refund_auto_destination.md退款去向七天未选自动执行(destination_deadline_at)对接退款去向倒计时与自动选定
2026-09-22_net_paid_amount.md约稿详情/列表净已支付金额(展示口径,net_paid_amount)展示合同覆盖净额与支付进度
本文退款政策与金额口径(最低退款金额、两次退款、按支付 Order 的碎额抹平、最终关闭、改价纯差额)判断某次退款/改价为什么被拒或为什么被抹平

原 2026-09-27_price_change_pure_difference_refund.md(改价纯差额)与 2026-09-22_price_change_preview_credit_allocation_fix.md(credit 分配修复)已并入本文并删除;后台财务与其它管理后台接口文档不再保留在对外 api-changes 目录。

2. 接口变更

user

  • POST /api/work_tasks/cancellation/preview
    • 功能:取消 / 退款试算
    • 变更:✨ refund_policy 新增小额退款政策字段;⚠️ 顶层 disabled_reason 新增 refund_attempts_exhausted 取值;below_minimum_amount 不再由取消入口返回(整笔剩余低于门槛时仍可选择 R = M 全退)。
  • POST /api/work_tasks/cancellation/request
    • 功能:发起取消申请(可含退款额)
    • 变更:⚠️ 正额退款新增政策拒绝 22022 / 22023 / 22024;⚠️ 全退(R = M)豁免最低退款金额,0 < M < T 的小额余额可选择全退,不再返回 22022 或自动抹平;✨ 全部来源原币向下取整为 0 时拒绝并返回 22026,不占退款次数。
  • POST /api/work_tasks/cancellation/accept
    • 功能:接受取消申请
    • 变更:⚠️ 接受时同样适用 22022 / 22023 / 22024 / 22026;全退(R = M)豁免最低退款金额。
  • POST /api/work_task_price_changes/preview
    • 功能:改价 / 退款试算
    • 变更:⚠️ 降价退款额改为按业务纯差额计算;✨ refund_policy 新增政策字段;⚠️ 顶层 disabled_reason 新增 below_minimum_amount、refund_attempts_exhausted、final_refund_must_be_remaining、write_off_cap_exceeded、exchange_rate_unavailable、below_payment_fee 取值;被政策拒绝时 can_submit = false。⚠️ 全退(派生 R = M,含小额余额)豁免最低退款金额与碎额准入,below_minimum_amount 只用于非全退的部分降价。
  • POST /api/work_task_price_changes/create
    • 功能:发起改价
    • 变更:⚠️ 降价退款额快照改为纯差额;⚠️ 新价格低于不可退支付手续费 F 时拒绝并返回 60001;用户端本接口只落待批改价,政策拒绝(22022 / 22023 / 22024 / 22026)发生在 approve 阶段,画师端直连退款单、在本接口返回;无退款改价与升价补款不受门槛与次数影响。
  • POST /api/work_task_price_changes/approve
    • 功能:同意改价
    • 变更:⚠️ 同意降价时在锁内重算退款额并重新校验政策,拒绝码 22022 / 22023 / 22024 / 22026;条款与快照不符返回 60002。

artist_center

3. 接口示例

示例场景与金额速查

以下场景使用同一种业务币,其最低退款金额 = 0.10、执行碎额上限 = 0.01(平台默认的 USD 0.10 / USD 0.01 等值)。表中「本次请求退款额」即 R、「剩余可退金额」即 M、「最低退款金额」即 T,符号定义见 1.0 符号速查。金额均为业务币最小单位整数,示例数值与测试实测一致,不得把 USD 数字直接套用到人民币等其它币种。

字段说明:表中「剩余可退金额」为发起本次操作前该委托当前的最大可退容量上限(即 financials.refund_limits.maximum 与 refund_policy.remaining_refundable_amount,等于 累计实付 − 不可退支付手续费 − 历史占用),它不是改价后的新合同金额(新价),也不是退款执行后的未来剩余。例如场景 1 中累计实付 100.00、不可退支付手续费 3.00,因此操作前剩余可退金额为 100.00 − 3.00 = 97.00。

场景 15 前提:22023 仅位于“异常数据仍可操作”的分支。正常流程下第二次退款对账完成后委托已 refund_closed:改价返回 22007(状态不允许),取消入口直接不可发起,不会再走到退款政策层。

场景渠道本次请求退款额剩余可退金额最低退款金额已用次数预览 can_submit预览 disabled_reason正式提交结果
1 改价 100.00 → 99.99(已付 100.00、手续费 3.00)改价0.0197.000.100falsebelow_minimum_amount22022(非全退的部分降价)
2 改价 100.00 → 99.90(已付 100.00、手续费 3.00,差额 = 0.10 = 门槛)改价0.1097.000.100truenull成功
3 剩余 10.00 退 9.90(退款后余额 0.10)取消9.9010.000.100truenull成功;余 0.10 后续仍可退
4 剩余 10.00 退 9.91(退款后余额 0.09)取消9.9110.000.100truenull成功;余 0.09 保持开放,可随时以 R = M 全额退完
5 新价 ≥ 手续费(已付 600.00、手续费 27.71、新价 550.00)改价50.00572.290.100truenull成功;新价下限 N ≥ F 通过
6 新价 < 手续费(已付 600.00、手续费 27.71、新价 20.00)改价—572.290.100falsebelow_payment_fee60001
7 同一 Order 本次请求碎额 0.01(在该 Order 上限内)取消1.00≥ 1.000.100truenull成功;estimated_write_off_amount = 0.01
8 同一 Order 本次请求碎额 0.02(Gateway + Wallet 各 0.01,超该 Order 上限)取消—≥ 0.020.100falsewrite_off_cap_exceeded被拒(预览即阻断)
8b 两个不同 Order 各 0.01 本次请求碎额取消2.00≥ 2.000.100truenull成功;每个 Order 各用一份上限,合计可抹平 0.02
9 同一 Order 仅来源残余碎额 0.02(本次请求干净)取消5.00≥ 7.000.100truenull成功;残余超限不阻断本次,延期到终局收口
10 业务币缺 USD 汇率且本次请求带碎额取消—≥ 0.01不可换算0falseexchange_rate_unavailable被拒(禁止自动抹平)
11 整笔剩余 0.08(低于门槛)选择全退取消 / 改价0.08(R = M)0.080.100truenull成功;全退豁免门槛,不做抹平
11b 整笔剩余 0.08 却只申请部分(如 0.05)取消 / 改价0.050.080.100取消 true / 改价 false取消 null / 改价 below_minimum_amount22022(余额状态不变,全退仍可用)
12 第一次部分退款取消 / 改价2.0010.000.100truenull成功;attempts_used 变 1
13 第二次退部分(如 2.00)取消 / 改价2.008.000.101false(改价)final_refund_must_be_remaining22024
14 第二次退剩余全部(8.00)取消 / 改价8.008.000.101truenull成功;全部来源对账后 WorkTask 置 refund_closed
15 第三次请求取消 / 改价任意00.102falserefund_attempts_exhausted22023(仅异常数据防御,正常流程委托已关闭)
16 升价补款 / 无退款改价改价0不变0.10不变truenull成功;不占次数、不重置次数
17 全部来源原币向下取整为 0 的部分退款取消 / 改价1.00≥ 1.000.100——22026;不创建、不占次数、不建抹平
18 final closure 中某来源 B > 0 且 S = 0取消 / 改价B(全退)M不判断0 或 1truenull该来源差额记 final closure 抹平,其余来源逐来源退回原币净额

user

POST /api/work_tasks/cancellation/preview

  • 功能说明:已付款 Commission 的取消 / 退款试算,返回金额、去向与退款额输入可用性。
  • 变更说明:✨ refund_policy 新增政策字段;⚠️ disabled_reason 新增 below_minimum_amount、refund_attempts_exhausted。

请求参数

字段类型必填说明
idnumber是WorkTask id
business_refund_amountnumber否期望退款额(业务币最小单位);不传时后端给出默认建议值

响应示例

{
  "data": {
    "mode": "negotiated",
    "payment_state": "paid",
    "can_submit": true,
    "disabled_reason": null,
    "financials": {
      "paid": { "amount": 10000, "currency": { "code": "USD" } },
      "previous_refunded": { "amount": 0, "currency": { "code": "USD" } },
      "refund": { "amount": 9700, "currency": { "code": "USD" } },
      "retained": { "amount": 300, "currency": { "code": "USD" } },
      "refund_limits": {
        "standard": { "amount": 9700, "currency": { "code": "USD" } },
        "maximum": { "amount": 9700, "currency": { "code": "USD" } },
        "absolute": { "amount": 10000, "currency": { "code": "USD" } }
      },
      "payment_fee": { "amount": 300, "currency": { "code": "USD" } }
      // ...其余 financials 字段省略
    },
    "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,
      "minimum_refund_amount": { "amount": 10, "currency": { "code": "USD" } },
      "remaining_refundable_amount": { "amount": 9700, "currency": { "code": "USD" } },
      "attempts_used": 0,
      "attempts_remaining": 2,
      "final_refund_only": false,
      "write_off_cap_amount": { "amount": 1, "currency": { "code": "USD" } },
      "estimated_write_off_amount": { "amount": 0, "currency": { "code": "USD" } },
      "write_off_cap_exceeded": false,
      "write_off_explained": false,
      "exchange_rate_unavailable": false
    },
    "destinations": {
      "available": ["original", "credit"],
      "unavailable": {}
    }
    // ...其余字段省略
  }
}

金额对象的 currency 实际还包含 id、symbol、is_zero_decimal,此处按节选约定省略;下同。

错误响应

无新增错误码;沿用试算原有的计算事实与状态校验。

POST /api/work_tasks/cancellation/request

  • 功能说明:发起取消申请,正额部分形成退款债权。
  • 变更说明:⚠️ 正额退款新增政策拒绝 22022 / 22023 / 22024 / 22026;全退(R = M)豁免最低退款金额。

请求参数

字段类型必填说明
idnumber是WorkTask id
business_refund_amountnumber否本次请求退款额(业务币最小单位);0 表示零退款取消
idempotency_keystring是幂等键,长度 8–128
refund_destinationstring否退款去向偏好:original(原路退回,默认)、credit(站内 Credit 钱包)

错误响应

400:正额部分退款低于最低退款金额(全退 R = M 豁免,即使整笔剩余低于门槛也可提交)

{
  "code": 22022,
  "message": "A refund below the platform minimum refund amount cannot be created"
}

400:全部来源原币向下取整为 0,本次退款无法实际出款

{
  "code": 22026,
  "message": "A positive refund must execute at least one funding source in its original currency"
}

400:两次退款机会均已用完

{
  "code": 22023,
  "message": "This Commission has already used both refund attempts"
}

400:第二次退款必须退回剩余全部可退金额

{
  "code": 22024,
  "message": "The second refund must return the whole remaining refundable amount"
}

POST /api/work_tasks/cancellation/accept

  • 功能说明:接受对方发起的取消申请。
  • 变更说明:⚠️ 接受时重新校验政策,拒绝码同 cancellation/request;全退(R = M)豁免最低退款金额。

请求参数

字段类型必填说明
request_idnumber是取消申请 id
refund_destinationstring否退款去向偏好:original 或 credit

错误响应

同 POST /api/work_tasks/cancellation/request 的 22022 / 22023 / 22024 / 22026。

POST /api/work_task_price_changes/preview

  • 功能说明:改价 / 退款试算(用户端与画师端同一契约)。由改价差额算出的退款额为 max(0, 未占用已付金额 − 新价格);新价格必须满足 N ≥ F。
  • 变更说明:⚠️ 降价退款额改为业务纯差额;✨ refund_policy 新增政策字段;⚠️ disabled_reason 新增 below_minimum_amount、refund_attempts_exhausted、final_refund_must_be_remaining、write_off_cap_exceeded、exchange_rate_unavailable、below_payment_fee;被政策拒绝时 can_submit = false。
  • can_submit 判定与实际响应一致:below_minimum_amount、refund_attempts_exhausted、final_refund_must_be_remaining、write_off_cap_exceeded、exchange_rate_unavailable、below_payment_fee 都表示本次由改价差额算出的退款/改价本身不可执行,降价又没有「零退款」的同价提交,因此一律 can_submit = false;仅来源残余碎额超限、合法升价与无退款改价不受影响,仍为 can_submit = true。在已付款且派生退款恰好等于剩余全部可退金额(R = M)时,上述 below_minimum_amount、write_off_cap_exceeded、exchange_rate_unavailable 与碎额准入都不适用,仍为 can_submit = true。

请求参数

字段类型必填说明
work_task_idnumber是WorkTask id
pricenumber是新价格(业务币最小单位),最小 0

响应示例(正常降价,纯差额)

已付 600.00、不可退支付手续费 27.71、新价 550.00:由改价差额算出的退款额为 50.00。

{
  "data": {
    "work_task_id": 191,
    "old_price": { "amount": 60000, "currency": { "code": "CNY" } },
    "new_price": { "amount": 55000, "currency": { "code": "CNY" } },
    "financials": {
      "paid": { "amount": 60000, "currency": { "code": "CNY" } },
      "previous_refunded": { "amount": 0, "currency": { "code": "CNY" } },
      "refund": { "amount": 5000, "currency": { "code": "CNY" } },
      "retained": { "amount": 55000, "currency": { "code": "CNY" } },
      "refund_limits": {
        "standard": { "amount": 57229, "currency": { "code": "CNY" } },
        "maximum": { "amount": 57229, "currency": { "code": "CNY" } },
        "absolute": { "amount": 60000, "currency": { "code": "CNY" } }
      },
      "payment_fee": { "amount": 2771, "currency": { "code": "CNY" } },
      "artist_fee_coverage": { "amount": 0, "currency": { "code": "CNY" } },
      "artist_settlement_basis": { "amount": 55000, "currency": { "code": "CNY" } },
      "platform_fee": { "amount": 2750, "currency": { "code": "CNY" } },
      "estimated_artist_net": { "amount": 49479, "currency": { "code": "CNY" } }
      // ...其余 financials 字段省略
    },
    "refund_policy": {
      "maximum_policy": "standard",
      "can_exceed_standard": false,
      "minimum_refund_amount": { "amount": 10, "currency": { "code": "CNY" } },
      "remaining_refundable_amount": { "amount": 57229, "currency": { "code": "CNY" } },
      "attempts_used": 0,
      "attempts_remaining": 2,
      "final_refund_only": false,
      "write_off_cap_amount": { "amount": 1, "currency": { "code": "CNY" } },
      "estimated_write_off_amount": { "amount": 0, "currency": { "code": "CNY" } },
      "write_off_cap_exceeded": false,
      "write_off_explained": false,
      "exchange_rate_unavailable": false
    },
    "can_submit": true,
    "disabled_reason": null
    // ...其余字段省略
  }
}

响应示例(由改价差额算出的退款额低于最低退款金额)

已付 100.00、不可退支付手续费 3.00(当前剩余可退金额上限 97.00)、新价 99.99:退款额 0.01 低于最低退款金额 0.10。

{
  "data": {
    "financials": {
      "refund": { "amount": 1, "currency": { "code": "USD" } },
      "refund_limits": {
        "maximum": { "amount": 9700, "currency": { "code": "USD" } }
      }
    },
    "refund_policy": {
      "minimum_refund_amount": { "amount": 10, "currency": { "code": "USD" } },
      "remaining_refundable_amount": { "amount": 9700, "currency": { "code": "USD" } },
      "attempts_used": 0,
      "attempts_remaining": 2,
      "final_refund_only": false,
      "estimated_write_off_amount": { "amount": 0, "currency": { "code": "USD" } },
      "write_off_explained": false,
      "exchange_rate_unavailable": false
    },
    "can_submit": false,
    "disabled_reason": "below_minimum_amount"
    // ...其余字段省略
  }
}

响应示例(新价格低于不可退支付手续费下限)

已付 600.00、不可退支付手续费 27.71、新价 20.00。

{
  "data": {
    "financials": {
      "payment_fee": { "amount": 2771, "currency": { "code": "CNY" } },
      "refund": { "amount": 0, "currency": { "code": "CNY" } }
    },
    "can_submit": false,
    "disabled_reason": "below_payment_fee"
    // ...其余字段省略
  }
}

错误响应

无新增错误码;试算只返回 can_submit 与 disabled_reason,正式提交在 create / approve 阶段返回 22022 / 22023 / 22024 / 22026 / 60001 / 60002。

POST /api/work_task_price_changes/create

  • 功能说明:发起改价;降价产生的差额即由改价差额算出的退款额。
  • 变更说明:⚠️ 退款额快照按纯差额派生;⚠️ 新价格低于不可退支付手续费 F 时拒绝并返回 60001;降价产生的正额退款适用政策门槛与次数限制;升价补款、无退款改价不受影响。用户端 create 只落待批改价、不建退款单,政策拒绝(22022 / 22023 / 22024 / 22026)发生在 approve(建单)阶段;画师端 create 直接建单,政策拒绝发生在该接口。试算响应的 can_submit = false(见 …/price_changes/preview)是前端的提前阻断依据。

请求参数

字段类型必填说明
work_task_idnumber是WorkTask id
pricenumber是新价格(业务币最小单位),最小 0
refund_destinationstring否退款去向偏好(降价时可选:original 原路、credit 站内余额)

错误响应

400:新价格低于不可退支付手续费合计

{
  "code": 60001,
  "message": "New price cannot be lower than the non-refundable payment fees"
}

用户端:本接口不返回 22022 / 22023 / 22024 / 22026 政策拒绝,这些在 approve 阶段返回,语义同上。画师端:同 22022 / 22023 / 22024 / 22026。

POST /api/work_task_price_changes/approve

  • 功能说明:同意改价;同意降价时按冻结试算执行退款。
  • 变更说明:⚠️ 在锁内重算由改价差额算出的退款额并校验条款一致性,同意降价时重新校验政策;条款与快照不符返回 60002。

请求参数

字段类型必填说明
idnumber是改价申请 id

错误响应

同 22022 / 22023 / 22024 / 22026;条款与快照不符时:

{
  "code": 60002,
  "message": "Payment or refund facts changed; create a new price change request"
}

artist_center

POST /api/artist_center/work_tasks/cancellation/preview

  • 功能说明:画师端取消 / 退款试算。
  • 变更说明:与用户端 POST /api/work_tasks/cancellation/preview 相同;role_view 为画师视角。

请求参数

字段类型必填说明
idnumber是WorkTask id
business_refund_amountnumber否期望退款额(业务币最小单位)

响应示例

同用户端示例,refund_policy 新增字段一致。

错误响应

无新增错误码。

POST /api/artist_center/work_tasks/cancellation/request

  • 功能说明:画师端发起取消申请。
  • 变更说明:⚠️ 新增政策拒绝 22022 / 22023 / 22024 / 22026。

请求参数

字段类型必填说明
idnumber是WorkTask id
business_refund_amountnumber否本次请求退款额(业务币最小单位)
idempotency_keystring是幂等键,长度 8–128

画师端发起取消时不传 refund_destination 参数;款项退还给买家,退款去向由买家在接受时选择或由系统后续规则处理。

错误响应

同用户端 22022 / 22023 / 22024 / 22026。

POST /api/artist_center/work_tasks/cancellation/accept

  • 功能说明:画师端接受取消申请。
  • 变更说明:⚠️ 接受时重新校验政策。

请求参数

字段类型必填说明
request_idnumber是取消申请 id

错误响应

同 22022 / 22023 / 22024 / 22026。

POST /api/artist_center/work_task_price_changes/preview

  • 功能说明:画师端改价 / 退款试算。
  • 变更说明:与用户端 POST /api/work_task_price_changes/preview 相同(同一计算链路),退款额按纯差额计算,refund_policy 新增政策字段,disabled_reason 新增取值(含 below_payment_fee),被拒时 can_submit = false。

请求参数

字段类型必填说明
work_task_idnumber是WorkTask id,须属于当前画师
pricenumber是新价格(业务币最小单位)

错误响应

无新增错误码;create / approve 阶段返回 22022 / 22023 / 22024 / 22026 / 60001 / 60002。

POST /api/artist_center/work_task_price_changes/create

  • 功能说明:画师端发起改价;直接降价即时生效并生成退款单。
  • 变更说明:⚠️ 降价执行直接退款时金额为纯差额;新价格低于不可退支付手续费时拒绝并返回 60001;降价退款适用政策拒绝 22022 / 22023 / 22024 / 22026。

请求参数

字段类型必填说明
work_task_idnumber是WorkTask id
pricenumber是新价格(业务币最小单位)

响应示例(直接降价生效)

已付 600.00、手续费 27.71、新价 550.00,差额 50.00 即时生成退款单。

{
  "data": {
    "id": 88,
    "work_task_id": 191,
    "status": "paid",
    "old_price": { "amount": 60000, "currency": { "code": "CNY" } },
    "new_price": { "amount": 55000, "currency": { "code": "CNY" } },
    "commission_refund_id": 105,
    "financials": {
      "refund": { "amount": 5000, "currency": { "code": "CNY" } },
      "estimated_artist_net": { "amount": 49479, "currency": { "code": "CNY" } }
    }
  }
}

错误响应

同 22022 / 22023 / 22024 / 22026 / 60001 / 60002。

POST /api/artist_center/work_task_price_changes/approve

  • 功能说明:画师端同意改价。
  • 变更说明:⚠️ 在锁内重算由改价差额算出的退款额并校验条款一致性;同意降价时重新校验政策。

请求参数

字段类型必填说明
idnumber是改价申请 id

错误响应

同 22022 / 22023 / 22024 / 22026 / 60002。

4. 字段与规则补充说明

4.1refund_policy 新增政策字段

(T / C / M 等符号的含义与字段对应见 1.0 符号速查。)

以下字段由已付款的取消试算(cancellation/preview)与改价试算(work_task_price_changes/preview)返回;同一个 Commission 的改价对象、退款债权与取消对象也返回同一组政策字段。未付款试算(无可退额度)不返回这些政策字段。

字段类型说明
minimum_refund_amountMoney平台最低退款金额,默认等值 USD 0.10;业务币按汇率向上取整。只限制第一次正额部分退款,全退(R = M)豁免、不受门槛约束;汇率不可换算时为 0,以 exchange_rate_unavailable 为准
remaining_refundable_amountMoney当前剩余可退金额(等于 financials.refund_limits.maximum)
attempts_usednumber已使用的退款机会数(按非作废退款单计,最大 2)
attempts_remainingnumber剩余退款机会数(2 − attempts_used)
final_refund_onlybooleantrue 表示下一次退款只能退剩余全部;等价于 attempts_remaining = 1
write_off_cap_amountMoney每个支付 Order 各自可抹平的执行碎额合计上限,默认等值 USD 0.01;业务币向下取整。同一 Order 内的 Gateway 与 Wallet 来源先按类型聚合
estimated_write_off_amountMoney本次试算预计不支付的碎额合计,只统计在生效上限内的抹平计划(本次请求碎额 + 本次可应用的来源残余碎额);某 Order 超限、延期到终局收口的残余不计入,也不阻断本次;final closure 的 final_closure 抹平同样不计入;整笔低余额不再有兜底抹平
write_off_cap_exceededbooleantrue 表示某个支付 Order 的本次请求碎额合计超过该 Order 的上限、本次请求无法完整履约,预览即阻断;另一 Order 的残余碎额超限不产生本值
write_off_explainedbooleantrue 表示本次存在可说明的抹平金额(生效上限内 estimated_write_off_amount > 0)
exchange_rate_unavailablebooleantrue 表示业务币种缺少 USD 汇率,USD 基准的门槛与碎额上限换算不可用:上表两个换算金额字段退化为 0,不代表 USD 等值,且禁止任何自动抹平(含带碎额的本次请求会被阻断);全退不受影响

refund_policy 同时携带既有的去向能力字段 maximum_policy、can_exceed_standard、can_refund_to_credit、has_stripe_connected_order、all_orders_stripe_connected、has_unknown_order(语义见《2026-09-09_commission_cancellation_and_refunds.md》),本次未改动。

4.2disabled_reason 取值

(本节出现的 F、R = M 等符号见 1.0 符号速查。)

取值含义前端处理
calculation_incomplete计算事实未就绪禁止提交,展示 issues
no_refundable_amount无可退额度(未付款或已退完)退款额输入禁用,零退款业务仍可提交
below_minimum_amount仅改价入口:由改价差额算出的退款额低于最低退款金额,且不等于剩余全部可退金额提交禁用(can_submit = false),提示「本次由改价差额算出的退款额低于最低退款金额」;取消入口不再返回本原因,整笔剩余低于门槛时仍可选择 R = M 全退
refund_attempts_exhausted两次退款机会已用完退款额输入禁用,提示已无退款机会(提交 22023);正常流程下委托已 refund_closed,本原因仅异常数据防御
final_refund_must_be_remaining第二次由改价差额算出的退款额不等于剩余可退金额(仅改价入口)提交禁用(can_submit = false),说明第二次只能退回剩余全部可退金额(提交 22024)
write_off_cap_exceeded某个支付 Order 的本次请求碎额合计超过该 Order 的执行碎额上限提交禁用,说明本次退款的小额尾差超过平台可抹平上限;另一 Order 的残余碎额超限不产生本原因、不阻断本次合法退款
exchange_rate_unavailable业务币种缺少 USD 汇率且本次请求需要抹平碎额退款额输入/提交禁用,提示稍后重试或联系客服;换算不可用期间禁止自动抹平
below_payment_fee改价新价格低于不可退支付手续费合计 F(仅改价入口)提交禁用(can_submit = false),提示「新稿酬不能低于已发生的支付手续费」;提交会被 60001 拒绝

below_minimum_amount 现在只由改价入口返回(派生退款额不足且不等于剩余全部);取消入口不再返回本原因,整笔剩余低于门槛时仍可选择 R = M 全退。原文档曾把「整笔剩余不足」也写成取消入口的 below_minimum_amount,本次修正。

4.3 由改价差额算出的退款额与新价下限

改价降价退款额按业务纯差额计算,不再预先扣减支付手续费:

N = 新价格
F = 订单已发生的实际不可退支付手续费合计
P = 累计实付
O = 已占用容量 = 已占用退款义务(已完成 + 在途 open 单)+ 已抹平占用
U = max(0, P − O)                              # 未占用已付金额(后端 unencumbered_paid_amount)
R = max(0, U − N)                              # 由改价差额算出的退款额
新价下限:N ≥ F

这里的 P / O / U 是本节公式的局部变量,不是 1.0 符号速查里的 C(执行碎额上限)或 B(final closure 剩余业务容量);R / N / F 则与速查表同义。

  • 纯差额示例:已付 600.00、不可退支付手续费 27.71、新价 550.00 → 由改价差额算出的退款额为 50.00(旧算法会先扣手续费、只退 22.29)。
  • 不可退支付手续费由画师结算承担:实际支付手续费常驻记录,在画师最终完成结算时扣减一次(estimated_artist_net 反映 新稿酬 − 实际支付手续费 − 平台费),不会在改价时重复扣费,也不会把手续费当成残余抹平。
  • 新价下限独立判定:N ≥ F;N < F 时预览 disabled_reason = below_payment_fee、can_submit = false,create 返回 60001。下限与最低退款金额是两个独立门槛,必须分别满足。
  • 连续改价与历史占用:连续改价时,退款基数准确扣除历史已退款义务与抹平占用(U = P − O),不会因累计 paid_amount 未扣减而重复退款。
  • 升价与同价处理:newPrice > oldPrice(升价)时退款额严格为 0,不派生退款、不触发退款政策限制,按现有 need_pay_amount 补款链路处理(用户发起升价直接进入待付款 WAIT_PAY,画师发起升价先进入待审批 PENDING);newPrice == oldPrice(同价)时禁止发起改价,preview 允许试算并返回退款额 0,但 create 会校验拦截并返回 60001(WorkTaskPriceChangeInvalid,提示同价不可改价)。
  • 状态与支付互斥:改价前校验委托状态(必须处于 Pending / WaitPay / Working),非法状态返回 22007(RefundWorkTaskStatusInvalid);存在进行中支付订单(paying)时,preview 返回 22005(RefundPaymentDataIncomplete),create 经互斥服务返回 20001(WorkTaskHasPayingOrder)。
  • 渠道退款识别与 fail-closed:计算器从渠道账本识别到的历史渠道退款增量扣减可用基数;出现渠道退款金额不一致或缺失资金分配等数据完整性异常时,严格 fail-closed 抛出 22005(RefundPaymentDataIncomplete)。

4.4 前端交接(取消 / 改价退款输入)

(本节 R / M / T / C / N / F 均见 1.0 符号速查。)

本节目标是让前端在输入层就把非法值拦住,不等接口报错再提示。所有约束的取值都来自同一次预览响应(取消用 cancellation/preview,改价用 work_task_price_changes/preview),前端不要自行换算币种、重算次数、手续费或汇率。改价对象 / 退款债权 / 取消对象也返回同一组 refund_policy 字段,可在二次进入时直接复用。

分层约定:4.4.1 / 4.4.2 是前端可主动拦截的部分(优先级最高,输入层拦掉)→ 4.4.3 是前端无法预防、必须写专门文案的部分 → 4.4.4 是输入层已拦截、服务端仍可能返回的兜底文案(优先级低于 4.4.1 / 4.4.2)→ 4.4.5 无需单独处理。

4.4.1 输入框前端限制(主动校验)

改价场景(用户输入的是新价格 N,不是 R)

输入约束规则依据字段拦掉的错误
N 下限降价分支要求 N ≥ F(F ≤ old_price 时输入框 min 直接设为 F)financials.payment_fee.amount(F)below_payment_fee / 60001
N 上限不设上限—升价合法,派生 R = 0 走补款链路
派生退款额 R只读展示 financials.refund.amount,前端不自算financials.refund.amount与后端纯差额口径漂移
同价N == old_price 不可改价(属改价条款校验,不属退款政策);可在输入层直接禁用同价提交old_price60001
第二次改价(final_refund_only = true)只要选择降价,派生 R 必须等于 M;把 N 锁定到使 R = M 的唯一值并提示「本次降价将退回全部剩余金额」refund_policy.final_refund_only、financials.refund.amount、refund_policy.remaining_refundable_amount22024 / final_refund_must_be_remaining
升价 / R = 0不触发退款政策,正常提交(不占次数、不重置次数)——

第二次降价为什么只能取唯一新价格:由改价差额算出的退款额为 R = max(0, U − N)(U 为未占用已付金额,见 4.3),而当前剩余可退金额 M = refund_policy.remaining_refundable_amount。两式相减可得 R = M ⟺ N = U − M:

  • 用响应字段表达即 N = financials.refund_limits.absolute.amount − refund_policy.remaining_refundable_amount.amount(absolute 已扣历史退款与抹平,即 U)。
  • 当 maximum_policy = standard(can_exceed_standard = false,绝大多数场景)时该值等于不可退支付手续费 F(financials.payment_fee.amount),即「N = F 时刚好退完剩余全部」;
  • 当 maximum_policy = stripe_connected_absolute(can_exceed_standard = true,全部为纯 Stripe Connect destination charge)时该值为 0。

因此前端不要硬编码 F,直接按 financials.refund.amount === refund_policy.remaining_refundable_amount 判断是否满足 final_refund_only 即可;N < F 已被 below_payment_fee 拦掉,N > U − M 派生出的 R < M 会被 final_refund_must_be_remaining / 22024 拒绝。

边界:maximum_policy = stripe_connected_absolute(全部为纯 Stripe Connect destination charge,且手续费由画师连接账户承担)时 M = U,公式给出的 N = 0 会先被新价下限 N ≥ F(below_payment_fee / 60001)拦下,改价入口无法派生 R = M;这种形态下前端以预览返回的 can_submit / disabled_reason 为准,是否需要改用取消协商入口(取消可直接协商 R = M)由业务决定。

取消场景(用户输入的是协商退款额 R)

⚠️ 取消预览在无固定请求金额时不返回 below_minimum_amount(小额时 disabled_reason = null):服务端不替前端禁用输入,必须由前端自己限制。

输入约束规则依据字段
合法值集合{0} ∪ [T, M]:部分退款 [T, M)、全退 R = M(豁免 T,必须始终可选)、零退款 R = 0refund_policy.minimum_refund_amount(T)、refund_policy.remaining_refundable_amount(M)
上限R ≤ M;输入框 max 设为 Mrefund_policy.remaining_refundable_amount
小额 0 < M < T合法值只剩 {0, M}([T, M) 为空);输入组件降级为「不退 / 全退」二选一同上
第二次退款(final_refund_only = true)正额条款只有 M 一个值:隐藏自由输入,只提供「退全部剩余(M)」;零退款取消仍可选refund_policy.final_refund_only、refund_policy.remaining_refundable_amount
M = 0只有 {0}:隐藏退款输入,仅展示「无剩余可退金额」refund_policy.remaining_refundable_amount(或 financials.refund_limits.maximum)

不能把合法区间写成 T ≤ R < M:R = M 是合法的全退,写成半开区间会误伤全退。正确写法是 R = 0、或 T ≤ R < M、或 R = M(等价于 {0} ∪ [T, M])。

minimum_refund_amount 在 exchange_rate_unavailable = true 时退化为 0,此时不要把它当真实门槛;disabled_reason 非空也不一定代表不能提交:no_refundable_amount 时 can_submit = true,零退款取消仍可提交(见 4.2 与 1.4)。

4.4.2 前端可主动拦截的场景(按钮隐藏 / 入口禁用,不等报错)

场景拦截方式依据字段
WorkTask 已 refund_closed隐藏付款 / 改价 / 取消全部入口 + 展示终局关闭文案work_tasks.status(终局枚举值 refund_closed,本次新增,前端必须适配)
M = 0(无可退余额)隐藏退款输入,仅展示「无剩余可退金额」refund_policy.remaining_refundable_amount(或 financials.refund_limits.maximum)
attempts_remaining = 0同 refund_closed 处理(异常数据防御,正常流程不可达)refund_policy.attempts_remaining
退款单处理中(final closure 执行期间)付款按钮禁用 + 「退款处理中」提示(服务端 22013 兜底)commission_refunds/list 每笔的 obligation_status / execution_status;建议以服务端 22013 为准

「退款单处理中」的本地判断依据是 commission_refunds/list 的每笔退款单:obligation_status = open,或 execution_status 不在 {completed, not_required},都表示资金动作(含画师侧追回与渠道对账)尚未结清。但普通部分退款等待选择去向时不应冻结收款(本次设计明确普通部分退款不冻结收款),前端只做「正在执行」的近似禁用即可,最终以服务端 22013 为准,避免误拦合法付款;final_closure 标记不在列表响应里,本判断无需精确复刻服务端口径。

4.4.3 必须接入(前端无法预防,必须写专门文案)

错误码场景文案方向
22026请求金额 ≥ T,但全部分配到各来源后取整为 0(纯分配结果,输入层不可预测)「该金额在各支付渠道均无法实际出款,请调整金额或联系客服」
22005填表期间另一端发起了付款 / 资金事实待核实(竞态窗口)「存在未完成的支付或资金数据待核实,请稍后重试」

4.4.4 兜底接入(输入层已拦截,服务端拒绝时的最后防线;优先级低于 4.4.1 / 4.4.2)

错误码被什么拦截兜底文案方向
600014.4.1 改价 N ≥ F 下限「新价格不能低于已发生的支付手续费」
220224.4.1 取消 R ∈ {0} ∪ [T, M]「本次退款金额低于平台最低退款额」
220244.4.1 二次锁定 M「第二次退款只能退回全部剩余金额」
220274.4.2 refund_closed 隐藏入口「该委托已因退款完成关闭,无法付款」
220134.4.2 处理中禁用按钮「退款处理中,暂不可付款」
21013(Stripe / Alipay)/ 21001(PayPal)支付侧竞态(冻结资金集合前发起的旧支付晚到账转人工),前端无法预防「该笔支付需要人工核实,请留意通知」——两码渠道不同,都要映射
22023 / 22007异常数据防御通用「操作不可用」即可
60002改价条款竞态(条款已被对方更新)「条款已被对方更新,请刷新后重试」

4.4.5 无需单独处理

  • 22025 / 22006 等 fail-closed 系统类错误按既有「系统异常」统一处理即可。
  • 被 4.4.1 输入层拦截的 below_minimum_amount 等预览 disabled_reason 提示逻辑保留现状(4.2 的提示文案仍适用);本节只补充「输入层前置拦截」,不替换 disabled_reason 提示。

4.4.6 其余保留约定

  • 禁用文案(平台口吻):below_minimum_amount 只出现在改价入口,提示「本次由改价差额算出的退款额低于最低退款金额」;取消入口的小额余额可选择 R = M 全退,不再出现本原因。refund_attempts_exhausted → 「已无可用的退款机会」;final_refund_must_be_remaining → 「第二次退款仅支持退回剩余全部可退金额」;below_payment_fee → 「新稿酬不能低于已发生的支付手续费」;write_off_cap_exceeded → 「本次退款的小额尾差超过平台可抹平上限」。不要写「某渠道最低退款额为 X」。
  • 已使用一次时提示:「已使用一次退款机会,下一次退款仅支持退回剩余全部可退金额。」(final_refund_only = true,或 attempts_remaining = 1)。
  • 第二次仅退剩余全部 ≠ 含手续费全额退款:第二次退款只允许把当前剩余可退金额一次退完,不改变原稿酬、保留额或手续费承担约定;平台不因此开放「含手续费全额退款」入口。
  • 退款额 0 业务仍可提交:disabled_reason 非空且 can_submit = true 时只表示退款额输入不可用,零退款取消、升价补款、无退款改价仍可提交;改价降价的固定退款被政策拒绝时 can_submit = false,前端应直接禁用提交而不是允许重试。
  • 改价弹窗下限:前端改价下限只受不可退支付手续费 F(financials.payment_fee.amount)约束(N ≥ F),不再用 paid_amount 作为下限;提交前调用 preview,按 can_submit 与 disabled_reason 控制按钮与提示。改价退款金额必须取自后端响应的 financials.refund.amount,禁止前端用 paid_amount 减新价计算。
  • 不暴露内部术语:前端文案与埋点不要出现平账、对账状态机、A/B 分类等内部实现术语。
  • pipipen-front 由前端负责人实现,本仓库(pipipen-api / pipipen-docs)只读参考。

4.4.7 历史绑定交接(5 处paid_amount)

以下为对 pipipen-front 仓库检索核对的 5 处历史绑定,前端负责人维护时请按建议处理(行号以当时检索为准):

  1. pages/user_center/work_tasks.vue 列表页支付进度百分比
    • 现状:(item.paid_amount / item.price) * 100。
    • 风险:降价退款后累计 paid_amount 保持不变,若合同降价可能计算出大于 100% 的异常进度;降价后又升价时简单截断又会掩盖待补款。
    • 交接建议:统一改用后端返回的合同覆盖净额 net_paid_amount。百分比计算注意防御零价:item.price <= 0 ? 100 : (item.net_paid_amount / item.price) * 100。严禁在前端使用 min(paid_amount, price) 截断。
  2. pages/user_center/work_task_details.vue 约稿详情金额组件展示
    • 现状:金额卡片直接展示 paid_amount。
    • 交接建议:区分「累计支付流水」与「当前合同留存」。展示当前生效合同时使用 net_paid_amount;展示付款历史流水时保留 paid_amount 并标注为「累计实付」。
  3. pages/user_center/work_task_details.vue 详情页支付进度条
    • 现状:依赖 paid_amount / price。
    • 交接建议:统一替换为 price <= 0 ? 100 : (net_paid_amount / price) * 100,确保退款与调价后的进度条落在当前合同真实进度区间。严禁用前端 min(paid_amount, price) 简单截断掩盖口径。
  4. pages/user_center/work_task_details.vue 与 pages/artist_center/worktask_info.vue 改价弹窗下限校验与提示
    • 现状:改价弹窗组件将输入框 :min 硬编码绑定为 paid_amount,并在改价提交逻辑中存在前端拦截 modify_price < paid_amount,触发提示 change_price_tip2(“修改后的稿酬不能小于已支付的稿酬”)。
    • 交接建议:
      1. 移除 modify_price < paid_amount 的前端强制下限拦截;纯差额业务允许降价至 paid_amount 以下(产生退款)。
      2. 改价下限改为只受不可退支付手续费 F(preview 返回的 financials.payment_fee.amount)约束(N ≥ F)。
      3. 弹窗提交前调用 preview,依据返回的 can_submit 与 disabled_reason 动态控制提交按钮与提示文案(如 disabled_reason === 'below_payment_fee' 时提示「新稿酬不能低于支付手续费」)。
      4. 改价退款金额必须完全取自后端改价接口响应中的 financials.refund.amount,禁止前端自行用 paid_amount 减新价计算。
  5. pages/user_center/application_details.vue 企划详情支付百分比
    • 现状:绑定 paid_amount。
    • 交接建议:统一使用后端返回的合同覆盖净额计算进度:price <= 0 ? 100 : (net_paid_amount / price) * 100,严禁前端截断。

为什么严禁前端使用 min(paid_amount, price) 简单截断? 截断方案与真实合同覆盖额 net_paid_amount 在存在退款的场景下完全不等价。以「付 100 → 退 50 → 升 80」为例:paid_amount 始终是 100(单调不可逆流水),当前合同留存 net_paid_amount 是 50;若前端用 min(paid_amount, new_price) / new_price 计算新价覆盖率,会得出 min(100, 80) / 80 = 100%,把已经退还给用户的 50 当成仍在合同内的资金,掩盖 30 元待补款缺口。因此合同进度一律用 net_paid_amount,百分比公式统一为 price <= 0 ? 100 : Math.min(100, Math.max(0, (net_paid_amount / price) * 100));待生效改价的 need_pay_amount 只在改价模块独立展示;paid_amount 仅用于展示「累计支付流水 / 实付总额」。

5. 兼容性说明

(本章 M / R / T 等符号见 1.0 符号速查。)

  • 新增字段为增量:旧客户端忽略新增的 refund_policy 字段即可继续工作;但无法据此禁用退款输入,建议与后端同版本上线。改价试算新增的政策字段与 disabled_reason 也是增量。
  • 改价试算的 can_submit:work_task_price_changes/preview 原本只可能因计算不完整而为 false;现在由改价差额算出的退款受政策限制、或新价低于手续费时会额外返回 false。旧客户端若忽略 can_submit,仍会在 create / approve 收到 22022–22024 / 22026 / 60001,不会产生错误退款,只是体验较差。
  • 新增错误码与枚举值:22022–22027 为新增值,未修改任何已发布错误码的值或含义;60001 / 60002 为改价域既有错误码。work_tasks.status 新增终局枚举值 refund_closed;commission_refund_write_offs.reason 新增 final_closure。前端需按 code 分支展示政策文案,而非依赖 message;对 refund_closed 必须新增状态文案并禁用付款 / 改价 / 取消 / 退款入口。
  • 小额余额行为变更与移除项:0 < M < T 不再自动抹平、不再禁用入口,可选择 R = M 全退;整笔低余额入口收尾(below_minimum_balance)已移除。首次全退与第二次退款统一为 final closure 逐来源关闭;第二次退款对账完成后 work_tasks.status = refund_closed,不再接受新付款与新退款。旧客户端若按「小额不可退」的旧文案引导,会与后端实际行为不一致,需同步提示。
  • 旧快照与旧退款单:缺少抹平字段的历史计算快照按「零抹平」读取;历史退款单纳入 attempts_used 计数,但不改写其既有执行结果;历史已 applied 的 below_minimum_balance 抹平仅作为账本事实被新规则读取(开发阶段无存量数据迁移)。已冻结并完成的改价与退款单按原快照执行,不追溯重算或补发历史差额;发版前已创建但未审批的降价申请,在发版后审批时若条款与快照不符会返回 60002(WorkTaskPriceChangeTermsChanged),前端提示重新发起即可。
  • 重试稳定:新的抹平计划随退款单冻结,失败重试沿用同一计划与同一碎额额度,不随门槛或汇率变化重算;已作废退款单的未落账计划会释放,已抹平金额不会释放。
  • credit 分配守恒(并入事实):混合资金(credit + 现金)委托在「历史 credit 退款已完成、之后又用 credit 补差」的形态下,退款分配计算按未列入历史的新来源按 0 处理并受本次退款额封顶,credit / cash 分配守恒;修复前该形态下 preview 误报的 22006 已恢复为正常返回。22006 语义与 refund_limits 口径不变。
  • 存量旧快照不重写:上述 credit 分配修复不做存量数据回填。修复前创建的升价改价单(退款额为 0)其冻结快照内部明细可能仍含幽灵 credit 分配,改价单展示继续按冻结快照原样呈现,本次未逐项验证该呈现结果;顶层金额(如退款额 = 0)取自快照总额、预期正常。若后续发现展示或流程异常,应单独评估数据修正方案。
  • 发布协同:后端先启用政策字段与错误码,前端再接入禁用与提示;发布异常时先停止新增退款写入并保留可识别抹平事实的版本处理在途单,不能回退到忽略抹平、重新开放旧余额的版本。
ON THIS PAGE