企划支付渠道选择与画师收款匹配 (2026-07-28)

功能说明

用户创建或编辑企划(Project / Open Call)时,必须声明可使用的支付渠道。画师应征时,系统根据企划支付渠道、画师已绑定且生效的收款账户及应征币种进行匹配。

支持的支付渠道:

  • paypal
  • stripe
  • alipay

本次只修改后端接口和数据兼容逻辑。公开企划列表不会过滤支付方式不匹配的企划,而是返回匹配结果供前端展示或置灰。

核心规则

收款账户匹配

画师收款账户账户状态可匹配企划渠道可用应征币种
StripesuccessedstripeStripe 收款账户的 currency_id
AliPaysuccessedpaypalstripealipayAliPay 收款账户的 currency_id,当前为 CNY
任意账户pending / failed / unbound不参与匹配

画师只要存在一个同时满足以下条件的账户,即可使用该账户币种应征:

  1. 收款账户状态为 successed
  2. 收款账户支持的支付渠道与企划 payment_channels 至少有一个交集。
  3. 应征 currency_id 等于该收款账户的 currency_id

payment_channelspayment_match 的区别

字段含义数据来源是否持久化及快照是否因访问者变化
payment_channels企划创建者声明该企划允许使用的支付渠道创建、更新企划时提交
payment_match当前访问画师的有效收款账户能否匹配该企划后端根据访问画师的收款账户实时计算

payment_match 的结构如下:

{
  "matched": true,
  "currency_ids": [1]
}
  • matched:当前画师是否至少存在一个兼容的收款币种。
  • currency_ids:当前画师可用于应征该企划的收款币种 id,不是支付渠道名称。
  • 未登录或当前用户不是有效画师时,payment_match 返回 null
  • payment_match 只用于前端提示或置灰,不影响企划列表的返回、总数和排序。

例如,企划允许 PayPal 和 AliPay,而当前画师只有 Stripe 收款账户时:

{
  "payment_channels": ["paypal", "alipay"],
  "payment_match": {
    "matched": false,
    "currency_ids": []
  }
}

因此可以将两个字段理解为:

payment_channels = 企划要求
payment_match    = 当前画师是否满足该要求

payment_matchPOST /api/content/projects/listGET /api/content/projects/info 返回。

Service 支付渠道

Service 不新增数据库字段,接口根据 Service 币种动态返回 payment_channels

Service 币种payment_channels
CNY["paypal", "stripe", "alipay"]
非 CNY["stripe"]

Service 前端接入约定

前端可以使用 Service 返回的 payment_channels 完全替代原有的币种到支付渠道计算逻辑:

  • payment_channels 是后端根据当前业务规则生成的权威展示结果。Service 列表、详情及创建成功后的支付方式图标或标签,都应直接遍历该字段。
  • 前端不应再根据 currency.code 自行推导支付渠道,避免后端规则调整后出现展示不一致。
  • Service 的 payment_channels 表示该服务对外支持的支付方式,不针对当前访问者计算,因此 Service 不提供 Project 场景中的 payment_match

以下接口直接返回 Service 的 payment_channels

POST /api/artist_center/services/update 当前仅返回 {"ok": true}。如果更新了 Service 币种,前端应在更新成功后重新请求 /api/artist_center/services/info 获取最新的 payment_channels,不要在本地临时计算。

Project WorkTask 支付

  • Project WorkTask 创建时,将企划的 payment_channels 保存到 busable_snap
  • 后续预计算和创建支付会话时,以 WorkTask 快照中的渠道为准,不读取企划当前值。
  • 请求的 pay_channel 不在快照中时,返回 400 和错误码 21008

接口变更

content

user

artist_center

接口示例

content

POST /api/content/projects/list

  • 功能说明:获取公开企划列表。
  • 变更说明:每个企划返回 payment_channelspayment_match。不匹配的企划仍保留在列表中,列表总数和排序不受支付匹配影响。

新增响应字段

字段类型说明
data[].payment_channelsstring[]企划允许的支付渠道
data[].payment_matchobject|null当前登录用户为有效画师时返回匹配结果,否则为 null
data[].payment_match.matchedboolean是否至少存在一个可用应征币种
data[].payment_match.currency_idsnumber[]当前画师可用于该企划的收款币种 id

请求示例

{
  "page": 1,
  "size": 15
}

响应示例(部分)

{
  "data": [
    {
      "id": 101,
      "payment_channels": ["stripe"],
      "payment_match": {
        "matched": true,
        "currency_ids": [1]
      }
    },
    {
      "id": 102,
      "payment_channels": ["alipay"],
      "payment_match": {
        "matched": false,
        "currency_ids": []
      }
    }
  ],
  "total": 2
}

错误响应

无新增,沿用原有错误语义。

GET /api/content/projects/info

  • 功能说明:获取公开企划详情。
  • 变更说明:响应新增 payment_channelspayment_match,字段语义与公开列表一致。

请求参数

字段类型必填说明
idnumber企划 id

响应示例(部分)

{
  "data": {
    "id": 101,
    "payment_channels": ["paypal", "alipay"],
    "payment_match": {
      "matched": true,
      "currency_ids": [2]
    }
  }
}

未登录或当前用户不是有效画师时:

{
  "data": {
    "id": 101,
    "payment_channels": ["paypal", "alipay"],
    "payment_match": null
  }
}

错误响应

无新增,沿用原有 404 Project not found 等错误语义。

POST /api/content/service/list

  • 功能说明:获取公开 Service 列表。
  • 变更说明:每个 Service 新增根据币种派生的 payment_channels

请求参数

沿用原接口,本次无新增请求参数。

响应示例(部分)

{
  "data": [
    {
      "id": 201,
      "currency": {
        "code": "CNY"
      },
      "payment_channels": ["paypal", "stripe", "alipay"]
    },
    {
      "id": 202,
      "currency": {
        "code": "USD"
      },
      "payment_channels": ["stripe"]
    }
  ],
  "current_page": 1,
  "total": 2
}

错误响应

无新增,沿用原有错误语义。

GET /api/content/service/info

  • 功能说明:获取公开 Service 详情。
  • 变更说明:新增根据 Service 币种派生的 payment_channels

请求参数

字段类型必填说明
service_idnumberService id

响应示例(部分)

{
  "data": {
    "id": 201,
    "payment_channels": ["paypal", "stripe", "alipay"]
  }
}

错误响应

无新增,沿用原有 404 Service Not Found 等错误语义。

user

POST /api/projects/list

  • 功能说明:获取当前用户的企划列表。
  • 变更说明:每个 Project 新增 payment_channels: string[]

请求参数

沿用原接口,本次无新增请求参数。

响应示例(部分)

{
  "data": [
    {
      "id": 101,
      "payment_channels": ["paypal", "stripe"]
    }
  ],
  "total": 1
}

错误响应

无新增,沿用原有错误语义。

POST /api/projects/create

  • 功能说明:创建企划。
  • 变更说明:新增必填字段 payment_channels

新增请求参数

字段类型必填说明
payment_channelsstring[]企划允许的支付渠道,至少一项

其他校验规则

  • 数组元素只能是 paypalstripealipay
  • 数组元素不能重复。
  • 空数组校验失败。

请求示例(部分)

{
  "name": {
    "en": "Character illustration",
    "_lang": "en"
  },
  "content": {
    "en": "Project description",
    "_lang": "en"
  },
  "currency_id": 1,
  "price_start": 10000,
  "price_end": 30000,
  "deadline": "2026-09-01 23:59:59",
  "payment_channels": ["stripe", "paypal"]
}

响应示例(部分)

{
  "data": {
    "id": 101,
    "payment_channels": ["stripe", "paypal"]
  }
}

错误响应

422:未传、传入空数组、包含重复值或非法渠道。

{
  "message": "The payment channels field is required.",
  "errors": {
    "payment_channels": ["The payment channels field is required."]
  }
}

POST /api/projects/update

  • 功能说明:更新企划。
  • 变更说明:支持更新 payment_channels

新增请求参数

字段类型必填说明
payment_channelsstring[]传入时覆盖原支付渠道;至少一项

更新接口仍支持局部更新。未传 payment_channels 时保留原值;传入时使用与创建接口相同的枚举、非空和去重校验。

请求示例

{
  "id": 101,
  "payment_channels": ["alipay"]
}

响应示例

{
  "ok": true
}

错误响应

422:传入空数组、重复值或非法渠道。

GET /api/projects/info

  • 功能说明:获取当前用户的企划详情。
  • 变更说明:响应新增 payment_channels: string[]

请求参数

字段类型必填说明
idnumber企划 id

响应示例(部分)

{
  "data": {
    "id": 101,
    "payment_channels": ["stripe"]
  }
}

错误响应

无新增,沿用原有错误语义。

GET /api/projects/meta

  • 功能说明:获取企划编辑元数据。
  • 变更说明:响应新增 payment_channels 枚举。

请求参数

无。

响应示例(部分)

{
  "data": {
    "payment_channels": ["paypal", "stripe", "alipay"]
  }
}

错误响应

无新增。

POST /api/project_requests/requested_list

  • 功能说明:获取当前用户某个企划收到的应征。
  • 变更说明:每个应征新增 payment_compatible。不兼容应征仍返回。

请求参数

字段类型必填说明
project_idnumber企划 id
pagenumber页码
sizenumber每页数量

响应示例(部分)

{
  "data": [
    {
      "id": 301,
      "currency_id": 2,
      "payment_compatible": true
    },
    {
      "id": 302,
      "currency_id": 1,
      "payment_compatible": false
    }
  ],
  "total": 2
}

payment_compatible 根据当前企划渠道、画师当前有效收款账户和该应征的 currency_id 实时计算。

错误响应

无新增,沿用原有错误语义。

POST /api/project_requests/choosed_list

  • 功能说明:获取当前用户某个企划已选择的应征。
  • 变更说明:每个应征新增 payment_compatible。字段语义与 requested_list 相同。

请求示例

{
  "project_id": 101,
  "page": 1,
  "size": 15
}

响应示例(部分)

{
  "data": [
    {
      "id": 301,
      "payment_compatible": true
    }
  ],
  "total": 1
}

错误响应

无新增,沿用原有错误语义。

POST /api/project_requests/choose

  • 功能说明:选择企划应征并创建 WorkTask。
  • 变更说明:创建 WorkTask 前,重新校验应征币种对应的有效收款账户能否匹配企划渠道。校验和解绑账户使用事务锁协调。

请求参数

沿用原接口,本次无新增请求字段。

请求示例

{
  "project_request_id": 301,
  "deadline": "2026-09-15 23:59:59",
  "name": {
    "en": "Selected project",
    "_lang": "en"
  }
}

错误响应

400:画师当前有效收款账户、应征币种和企划渠道不兼容。

{
  "code": 21008,
  "message": "Artist payout account is not compatible with project payment channels."
}

校验通过后,新建 WorkTask 的 busable_snap.payment_channels 保存选择当时的企划渠道。

POST /api/user/pay/work_task/pre_calc

  • 功能说明:预计算 WorkTask 支付金额。
  • 变更说明:当 work_task_id 对应 Project WorkTask 时,校验 pay_channel 是否在 busable_snap.payment_channels 中。

相关请求参数

字段类型必填说明
work_task_idnumberWorkTask id
typestringstage_payfull_payprice_change
pay_channelstringstripealipaypaypal

请求示例

{
  "work_task_id": 401,
  "type": "stage_pay",
  "pay_channel": "stripe",
  "wallets": []
}

错误响应

400:所选渠道不在 Project WorkTask 快照中。

{
  "code": 21008,
  "message": "Payment channel is not enabled for this project."
}

POST /api/user/pay/work_task/create_checkout_session

  • 功能说明:创建 WorkTask 支付会话。
  • 变更说明:使用与预计算接口相同的 Project 快照渠道校验,不能通过直接调用接口绕过。

请求示例

{
  "work_task_id": 401,
  "type": "stage_pay",
  "pay_channel": "stripe",
  "wallets": []
}

错误响应

400:所选渠道不在 Project WorkTask 快照中,返回错误码 21008

POST /api/stripe/pre_calc

  • 功能说明:WorkTask 支付预计算的兼容路径。
  • 变更说明:请求参数、响应和错误语义与 POST /api/user/pay/work_task/pre_calc 一致。

请求参数

POST /api/user/pay/work_task/pre_calc

错误响应

Project WorkTask 渠道不匹配时返回 400 和错误码 21008

POST /api/stripe/create_work_task_checkout_session

  • 功能说明:创建 WorkTask 支付会话的兼容路径。
  • 变更说明:请求参数、响应和错误语义与 POST /api/user/pay/work_task/create_checkout_session 一致。

请求参数

POST /api/user/pay/work_task/create_checkout_session

错误响应

Project WorkTask 渠道不匹配时返回 400 和错误码 21008

POST /api/work_tasks/list

  • 功能说明:获取用户 WorkTask 列表。
  • 变更说明:Project WorkTask 的 busable_snap 新增 payment_channels

响应示例(部分)

{
  "data": [
    {
      "id": 401,
      "busable_type": "project",
      "busable_snap": {
        "id": 101,
        "payment_channels": ["stripe"]
      }
    }
  ]
}

错误响应

无新增,沿用原有错误语义。

GET /api/work_tasks/info

  • 功能说明:获取用户 WorkTask 详情。
  • 变更说明:Project WorkTask 的 busable_snap.payment_channels 与列表接口一致。

请求参数

字段类型必填说明
idnumberWorkTask id

错误响应

无新增,沿用原有错误语义。

POST /api/withdraw_accounts/unbind

  • 功能说明:解绑收款账户。
  • 变更说明:事务内锁定收款账户;如果存在相同币种且状态为 pendingwait_payworking 的 Project WorkTask,则禁止解绑。

请求参数

字段类型必填说明
idnumber收款账户 id

请求示例

{
  "id": 501
}

错误响应

400:存在使用该币种的活跃 Project WorkTask。

{
  "code": 50003,
  "message": "Has project work task waiting processing. worktask id: 401"
}

artist_center

POST /api/artist_center/project_request/create

  • 功能说明:画师应征企划。
  • 变更说明:根据 currency_id、画师有效收款账户和企划 payment_channels 强制校验兼容性。

请求参数

沿用原接口;本次重点校验已有字段 project_idcurrency_id

错误响应

400:没有兼容的有效收款账户。

{
  "code": 21008,
  "message": "Artist payout account is not compatible with project payment channels."
}

POST /api/artist_center/project_request/update

  • 功能说明:更新待处理的企划应征。
  • 变更说明:重新校验更新后的 currency_id;未传 currency_id 时使用应征原币种校验。

请求参数

沿用原接口,本次无新增字段。

错误响应

400:当前收款账户不再兼容企划渠道,返回错误码 21008

POST /api/artist_center/project_request/list

  • 功能说明:获取画师的企划应征列表。
  • 变更说明:data[].project.payment_channels 返回关联企划允许的支付渠道。

响应示例(部分)

{
  "data": [
    {
      "id": 301,
      "project": {
        "id": 101,
        "payment_channels": ["stripe"]
      }
    }
  ]
}

错误响应

无新增。

POST /api/artist_center/project_request/list_by_chosen

  • 功能说明:获取画师已被选择的企划应征列表。
  • 变更说明:data[].project.payment_channels 与普通应征列表一致。

请求参数

沿用原接口,本次无新增参数。

错误响应

无新增。

GET /api/artist_center/services/list

  • 功能说明:获取画师自己的 Service 列表。
  • 变更说明:每个 Service 新增根据币种派生的 payment_channels

响应示例(部分)

{
  "data": [
    {
      "id": 201,
      "payment_channels": ["stripe"]
    }
  ]
}

错误响应

无新增。

GET /api/artist_center/services/info

  • 功能说明:获取画师自己的 Service 详情。
  • 变更说明:响应新增 payment_channels

请求参数

字段类型必填说明
idnumberService id

错误响应

无新增。

POST /api/artist_center/services/create

  • 功能说明:创建 Service。
  • 变更说明:创建成功响应新增根据 Service 币种派生的 payment_channels

请求参数

沿用原接口,本次无新增请求字段。

响应示例(部分)

{
  "data": {
    "id": 201,
    "currency_id": 1,
    "payment_channels": ["stripe"]
  }
}

错误响应

无新增,沿用原有错误语义。

POST /api/artist_center/work_tasks/list

  • 功能说明:获取画师 WorkTask 列表。
  • 变更说明:Project WorkTask 的 busable_snap 新增 payment_channels

响应示例(部分)

{
  "data": [
    {
      "id": 401,
      "busable_type": "project",
      "busable_snap": {
        "payment_channels": ["stripe"]
      }
    }
  ]
}

错误响应

无新增。

GET /api/artist_center/work_tasks/info

  • 功能说明:获取画师 WorkTask 详情。
  • 变更说明:Project WorkTask 的 busable_snap.payment_channels 与列表接口一致。

请求参数

字段类型必填说明
idnumberWorkTask id

错误响应

无新增。

兼容性说明

历史 Project

  • 数据库迁移为所有历史 Project 写入:
["paypal", "stripe", "alipay"]
  • projects.payment_channels 最终为非空 JSON 字段。
  • API 运行时仍保留缺失值兼容:字段缺失或为 null 时按三种渠道全部支持解释。
  • 明确保存的空数组 [] 不属于历史缺失,不会回退为全渠道;应征和支付校验均不通过。

历史快照

迁移同时处理:

  • project_snaps.data.payment_channels
  • work_tasks.busable_snap.payment_channels,仅限 busable_type = project

历史快照缺失该字段时写入三种渠道。运行时读取旧快照时也使用相同回退逻辑。

新建 Project WorkTask 后,即使用户随后修改企划支付渠道,该 WorkTask 仍使用创建时快照,保证历史可追溯。

并发一致性

  • 选择应征时,后端在事务内锁定应征币种对应的有效收款账户。
  • 解绑账户时,后端锁定账户并重新检查相同币种的活跃 Project WorkTask。
  • 两个操作并发时,只允许其中符合最终数据状态的一方成功,避免创建 WorkTask 后收款账户已经失效。

列表行为

  • 公开 Project 列表不按支付匹配结果过滤。
  • 用户收到的 Project Request 列表不按支付兼容性过滤。
  • 前端应使用 payment_matchpayment_compatible 决定提示或置灰方式。
ON THIS PAGE