新增与礼品卡同级的 open_call_commission_credit。该 Credit 属于委托方/付款用户的 User Wallet,统一以 USD 最小货币单位发放,只能抵扣 Project/Open Call 创建的 Commission,支持 stage_pay、full_pay 和 price_change;不能用于 Service Commission 或商品订单,也不是 Artist 的收款或结算余额。
同时新增 Artist 侧 Open Call 平台手续费减免:后台可配置比例、固定 CNY 金额和总上限。规则在支付与最终结算时实时读取,Commission 创建时间仅用于判断是否落在当前活动窗口;支付渠道手续费不属于减免范围。
POST /api/user/pay/work_task/available_credits
POST /api/user/pay/work_task/pre_calc
wallets 支持同时选择两类 Credit,并严格按数组顺序依次抵扣POST /api/user/pay/work_task/create_checkout_session
POST /api/internal/open_call_commission_credits/grant
POST /api/internal/system_settings/open_call_fee_waiver/detail
POST /api/internal/system_settings/open_call_fee_waiver/update
POST /api/user/pay/work_task/available_creditsdata 是第一级分组数组。前端应把 gift_card 和 open_call_commission_credit 展示为同级类型,不要把 Open Call Commission 专用 Credit 放进礼品卡分组。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
work_task_id | number | 是 | Commission 对应的 WorkTask ID |
type | string | 是 | stage_pay、full_pay 或 price_change |
| Commission / 支付类型 | gift_card | open_call_commission_credit |
|---|---|---|
Open Call Commission + stage_pay | 返回 | 返回 |
Open Call Commission + full_pay | 返回 | 返回 |
Open Call Commission + price_change | 返回 | 返回 |
| Service Commission | 返回 | 不返回 |
| 商品支付 | 沿用商品礼品卡逻辑 | 不适用 |
没有余额时分组仍可能返回,但 wallets 为空。专用 Credit 钱包只会以 USD 返回。
data[] 每一项都是独立的第一级 Credit 类型。title 和 notice。422:参数缺失、WorkTask 不存在或 type 非法。POST /api/user/pay/work_task/pre_calcwallets 为用户选择的 Wallet ID 数组,后端按数组顺序逐张抵扣。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
work_task_id | number | 是 | Commission 对应的 WorkTask ID |
type | string | 是 | stage_pay、full_pay 或 price_change |
pay_channel | string | 是 | stripe、alipay 或 paypal |
wallets | number[] | 否 | Credit Wallet ID;不得重复,数组顺序即抵扣顺序 |
该示例先抵扣 Wallet 82,仍有剩余时再抵扣 Wallet 36。响应 data.process 中的 wallet_balance_deduction 阶段新增 wallet_usage,用于识别本次扣减来自 credit 还是 open_call_commission_credit。
400:任一 Wallet 不属于当前用户、类型不适用、专用 Credit 不是 USD,或前端提交了当前支付不可使用的 Credit。
422:Wallet ID 重复、不存在或其他请求字段校验失败。
POST /api/user/pay/work_task/create_checkout_sessionwallets 的字段、校验、适用范围和顺序与 pre_calc 完全一致;后端会在事务中锁定钱包并再次校验,不能通过跳过预计算绕过限制。Credit 全额覆盖本次款项时,不会创建外部支付会话:
与 pre_calc 一致;业务不适用返回 HTTP 400 和错误码 21014,字段校验失败返回 422。
POST /api/internal/open_call_commission_credits/grant| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
user_id | number | 是 | 获赠的付款用户 ID;Credit 记入该 User 的 Wallet,不是 artist_id,也不会记入 Artist 的收款 Wallet |
amount | number | 是 | 正整数,USD 最小货币单位;2500 表示 USD 25.00 |
idempotency_key | string | 是 | 最长 255 字符;一次业务发放的全局唯一键 |
note | string|null | 否 | 发放说明,最长 2048 字符 |
相同 idempotency_key、user_id 和 amount 重试时返回原发放记录,不重复增加余额。
user_id表示 Credit 的实际所有者。即使该 User 同时拥有 Artist Profile,这笔 Credit 仍是用户侧抵扣余额,只有在其作为委托方/付款方支付符合条件的 Open Call Commission 时才能使用。不要因为某个 Artist 被 Open Call 选中,就把 Credit 发放给该 Artist 对应的artist.user_id;除非业务目的确实是让这个 User 以后作为委托方使用 Credit。
401:Internal API Token 缺失或错误。422:用户不存在、金额不是正整数或字段长度超限。400 / 21015:同一个 idempotency_key 已用于不同用户或不同金额。配置保存在新建的 system_settings 表,由 SystemSetting Model 管理。历史 settings 表可能是遗留结构,本功能不会读取、写入或迁移其中的数据。配置变更审计独立保存在 system_setting_change_logs。
system_settings 保持最小键值结构,不承载管理后台展示元数据:
| 字段 | 说明 |
|---|---|
id | 主键 |
key | 唯一配置键 |
value | JSON 配置值 |
created_at | 创建时间 |
updated_at | 更新时间 |
本功能使用的配置键为 promotion.open_call_fee_waiver。system_setting_change_logs.request_id 仅用于关联请求和审计查询,使用普通索引,不是唯一约束,也不承担接口幂等控制。
POST /api/internal/system_settings/open_call_fee_waiver/detail读取当前配置,响应同时包含 UTC 时间、配置时区下的本地时间、版本和状态:
status 可能为 disabled、not_started、active 或 ended。
POST /api/internal/system_settings/open_call_fee_waiver/updatestart_at 和 end_at 按请求中的 IANA timezone 解释,API 转换为 UTC 原子保存。[start_at, end_at)。percentage_bps 使用整数基点,范围 0..10000;例如 5000 表示 50%,不使用浮点数存储比例。fixed_amount_cny 和 cap_amount_cny 均使用 CNY 最小货币单位(分)。结算币种不是 CNY 时,计算时换算到结算币种。cap_type 必须为 unlimited 或 amount。unlimited 明确表示不设上限,此时 cap_amount_cny 必须为 null;amount 时上限必须大于 0。X-Admin-Id、X-Admin-Name;X-Request-Id 用于审计请求追踪。expected_version 不等于当前版本时返回 HTTP 400、错误码 30022,后台应刷新配置后让管理员重新确认。400、错误码 30023。POST /api/internal/system_settings/open_call_fee_waiver/simulate_manual管理后台的无状态手动试算接口。它使用请求中传入的草稿规则和资金参数,不要求先保存系统配置,也不会创建 Order、修改 Wallet 或写入结算记录。接口固定按“符合 Open Call 活动条件”计算,因此不校验配置开关、时间窗口或真实 Commission 来源。
所有金额仍使用对应币种的最小货币单位,比例统一使用整数基点:
credit_share_bps 表示 Commission 金额中由 Open Call Commission Credit 覆盖的比例,其余部分按外部支付估算。artist_plat_fee_credit_amount 使用 Artist 结算币种的最小单位;CNY Commission 的结算币种为 CNY,其他 Commission 当前统一为 USD。commission_to_usd_rate、usd_to_cny_rate 传 null 时读取当前实时汇率;传正数时用于运营的假设场景。真实支付和结算不会使用这里的手动汇率。stripe_fee_bps 和 stripe_fixed_fee_usd 只用于估算外部支付部分的渠道手续费,固定金额使用 USD 分。响应示例:
warnings 当前可能包含 cap_applied、full_waiver 和 manual_rate。该接口只模拟单笔汇总资金组成,不代替真实 WorkTask 的多 Order / 多阶段结算预览;需要核对历史 Order、支付时汇率和 Checkout 元数据时,继续使用 POST /api/internal/system_settings/open_call_fee_waiver/simulate。
支付和最终结算时,系统实时读取当前配置,再按 work_tasks.created_at 判断 Commission 是否位于当前活动窗口。创建 Commission 时不写入、不读取政策快照参与计算。
最终结算完成后,系统将本次实际读取的配置、资格判断和计算结果写入审计快照:
数据库不再保存独立的 work_tasks.plat_fee_policy_code 列。plat_fee_policy_snapshot 只用于最终审计,不作为后续计算输入。
尚未最终结算的 Commission 会受后台实时配置影响。Stripe 支付阶段已经实施的减免不会被追回;最终结算会读取 Checkout Session 元数据,在当前规则需要更高减免时只补足剩余部分。结算预览与 Pending Payout 的 calculation 新增:
减免公式为:先计算比例减免,再追加固定金额,最后应用总上限,并保证减免不超过标准平台手续费。之后才使用 Artist PlatFee Wallet 抵扣剩余平台费。全额减免时 Artist PlatFee Wallet 不扣减,Stripe application_fee_amount 仅保留支付渠道手续费;系统不会创建真实的 Stripe Application Fee Refund。
stage_pay、full_pay 和 price_change 都先生成 Order,Commission 完成时统一汇总全部已支付 Order。三种支付动作以及多阶段支付不会产生不同的手续费公式。
交易明细是“单个 Artist Wallet 的资金流水”,Commission 结算字段则是“整个 Commission 的汇总结果”,两者不能混用:
StripeDeduction 和 StripePayout 两条资金流。StripeDeduction 记录最终结算阶段尚未实施的 waiver:通常对应 Credit 覆盖部分;配置在支付后提高时,也可能包含对 Stripe 已收平台费的最终补差。StripeDeduction WalletTransaction。init - minus + plus = result。| Commission 币种 | 外部通道 | 资金组成 | Artist 结算 Wallet | Wallet 明细中的 waiver |
|---|---|---|---|---|
| CNY | Alipay / PayPal / Stripe | 纯外部支付 | Alipay Deposit | Commission 全部标准平台手续费 |
| CNY | internal | Credit 全额覆盖 | Alipay Deposit | Commission 全部标准平台手续费 |
| CNY | Alipay / PayPal / Stripe | Credit + 外部支付 | Alipay Deposit | Commission 全部标准平台手续费 |
| 非 CNY | internal | Credit 全额覆盖 | StripeDeduction | 只记录 Credit 覆盖部分的平台手续费 |
| 非 CNY | Stripe | 纯 Stripe | StripePayout;StripeDeduction 为零金额资金流 | StripeDeduction 不记录 Stripe 部分 waiver |
| 非 CNY | Stripe | Credit + Stripe | StripeDeduction + StripePayout | 记录最终剩余 waiver,不重复记录 Checkout 已实施部分 |
以下旧链路示例继续采用“比例 10000、固定金额 0、不设上限”的全额减免配置。金额均使用最小货币单位。假设 Commission 总额为 1000,Artist 平台费率为 10%,因此:
假设支付渠道手续费为 30:
Alipay、PayPal、Stripe 支付 CNY Commission 最终都进入这条 CNY 结算链路。
假设 Credit 覆盖 400、外部支付 600、支付渠道手续费为 30:
Credit 金额只作为结算来源信息,不会把 CNY Artist 收入拆成两个 Deposit Wallet。
假设 Stripe 支付渠道手续费为 30,Artist 的 Stripe 实际收入为 970:
StripeDeduction 没有承接这笔 Stripe 本金,因此不能在其明细中写入 plat_fee_waiver +100。
假设 Credit 覆盖 400、Stripe 支付 600,Stripe 支付渠道手续费为 30:
不能把 Commission 总 waiver 100 全部写入 StripeDeduction 明细,否则该明细会变成 400 - 40 + 100 = 460,与实际入账 400 不一致。
假设最终存在以下已支付订单:
汇总后:
StripeDeduction 明细只写入 plat_fee -30 和 plat_fee_waiver +30;Commission 结算汇总仍返回总 waiver 100。支付动作和 Order 数量不改变该边界。
| 使用场景 | 当前实现 | 新实现 | 是否必须切换 |
|---|---|---|---|
| Commission 支付页获取可用 Credit | POST /api/wallets/list 后由前端筛选 usage=credit && balance>0 | POST /api/user/pay/work_task/available_credits,提交 work_task_id 和本次支付 type | 是 |
| Commission 支付预计算 | POST /api/user/pay/work_task/pre_calc | 接口不变;wallets 改为跨 Credit 类型、按用户选择顺序组成的 Wallet ID 数组 | 保留接口,调整数据来源 |
| Commission 创建支付会话 | POST /api/user/pay/work_task/create_checkout_session | 接口不变;提交与最后一次预计算完全相同的 wallets 顺序 | 保留接口,调整数据来源 |
| Product 支付获取礼品卡 | POST /api/wallets/list | 继续使用原接口并只筛选 usage=credit | 否 |
| 钱包账户页、余额页、使用记录页 | POST /api/wallets/list | 继续使用原接口 | 否 |
不要修改 /api/wallets/list 的通用含义。该接口仍用于返回用户全部 Wallet,不知道当前 WorkTask、Commission 来源和支付类型,无法判断专用 Credit 是否适用。
当前 Commission 支付流程在打开支付弹窗时调用 /api/wallets/list,前端再执行以下筛选:
这段逻辑必须替换为调用 /api/user/pay/work_task/available_credits。所有仍可进入 Commission 支付流程的页面都应使用相同的新逻辑,包括新版 Commission 详情页和仍在使用的旧版 WorkTask / Project / Application 详情页。
共享支付组件目前接收扁平的 wallets。前端需要将组件输入调整为 creditGroups,直接使用接口返回的第一级分组:
页面展示层级应为:
open_call_commission_credit 必须是与 gift_card 同级的第一级类型,不能作为礼品卡内部的一张特殊卡展示。
stage_pay 或 full_pay。available_credits 获取当前 WorkTask、当前支付类型的可选分组。pre_calc 获取未抵扣时的初始金额。pre_calc。wallets 原样传给 create_checkout_session。支付类型发生变化时必须重新获取可用 Credit,不能复用旧列表。Project/Open Call Commission 的 stage_pay、full_pay 和 price_change 都可以返回 Open Call Commission 专用 Credit。
取消一个支付中的订单后,重新打开支付方式选择时也应重新请求 available_credits,因为 Wallet 余额或可用状态可能已经变化。
后端严格按照 wallets 数组顺序扣减,不会按 Wallet ID、币种或分组重新排序。前端需要保存用户的实际选择顺序:
跨分组选择同样适用。例如用户依次选择专用 Credit 82、礼品卡 36,请求必须保持:
不要使用以下处理:
pre_calc 与 create_checkout_session 之间重新生成顺序。data[].title,按当前语言读取。data[].notice,不要在前端重复硬编码专用 Credit 的适用范围。data.process[].wallet_usage 可区分本次抵扣来自普通礼品卡还是专用 Credit。wallets=[],可以隐藏该分组的选择器;是否保留标题和说明由产品展示决定。available_credits 没有返回专用分组时,不应通过 /api/wallets/list 自行补入专用 Wallet。| HTTP / code | 前端处理建议 |
|---|---|
422 | 请求字段或 Wallet ID 不合法;阻止支付并展示字段校验信息 |
400 / 21014 | 所选 Credit 已失效或不适用于当前支付;清空无效选择,重新请求 available_credits 和 pre_calc |
| 其他 Commission 支付错误 | 沿用现有支付错误处理和 paying order 互斥流程 |
前端不能只依赖列表控制安全性。pre_calc 和 create_checkout_session 都会重新校验 Wallet 所有人、用途、币种和 Commission 来源。
stage_pay、full_pay 和 price_change 同时显示礼品卡及 Open Call Commission 专用 Credit 两个一级类型。price_change 显示 Open Call Commission 专用 Credit,并沿用原 Wallet 退款规则。pre_calc 的抵扣顺序与点击顺序一致。create_checkout_session 的 Wallet ID 和顺序与最后一次成功 pre_calc 完全一致。21014 后,页面会刷新可用 Credit,而不是继续提交旧 Wallet ID。open_call_commission_credit。open_call_commission_credit 的所有者是 users.id 对应的付款用户;支付接口只查询并扣减当前付款用户自己的 Wallet,不从 Artist Profile 或 Artist 的收款账户读取该 Credit。deposit Wallet,和用户侧 open_call_commission_credit 是两套独立余额。open_call_commission_credit 追加在现有 plat_fee 之后,保留历史 ENUM 值的序号,降低上线 ALTER TABLE 触发表重建的风险。open_call_commission_credit_grants 发放审计表,关联 Wallet 和 WalletTransaction。USD;支付目标是其他币种时沿用现有汇率换算和取整规则。credit、open_call_commission_credit、plat_fee,Wallet 按“用户 + 币种 + Usage”归并;deposit 还需要使用二级分类 deposit_type,因此其 Wallet 身份为“用户 + 币种 + Usage + DepositType”,不能仅按 Usage 合并。stage_pay、full_pay 和 price_change 都允许使用 Open Call Commission 专用 Credit。credit_type。refunded;退款 WalletTransaction 同样记录 credit_type。usage=credit,不会误用专用 Credit。专用 Credit 不允许在回滚时自动转换成普通 credit,否则受限余额会变成可用于 Service Commission 或 Product 的普通礼品卡。
迁移回滚遵循以下规则:
open_call_commission_credit Wallet 和 Grant 时,可以正常删除审计表并移除 ENUM 值。0,自动回滚也会中止并给出明确错误。credit Wallet;恢复专用能力时必须能够按原用户和原审计记录恢复。这项限制是资金安全保护,不应通过手工修改 migration、直接更新 usage=credit 或删除审计记录绕过。