Work Task 支付中订单查询与支付互斥(2026-06-10)

变更背景

线上 PayPal 支付出现过 PaypalCheckoutSessionStatus: conflicted:用户已经在 PayPal 页面完成支付,但本地订单被自动取消,后续 capture / webhook 回来时发现远端已支付、本地已取消,进入冲突状态。

本次调整的核心原则:

  • 后端不再自动取消 work task 已存在的 paying 订单。
  • PayPal 已打开的支付页面不能像 Stripe Checkout Session 一样被平台主动关闭,因此本地不能仅因为过期时间或用户再次点击支付就取消旧订单。
  • PayPal 本地 expires_at 只作为巡检/展示信号,不代表远端支付页面已经不可用;本地 expired session 在远端未明确终态前仍可能继续支付。
  • 同一个 work task 同一时刻只能存在以下三类动作之一:stage_payfull_payprice_change
  • 前端点击支付按钮前,必须调用单独的 paying_order 查询接口获取最新状态;work_tasks/info 中的 payingOrder 只作为首次进入页面时的展示快照。
  • POST /api/orders/continue 返回支付链接前会重新锁定订单确认仍为 paying,避免极端并发下返回刚被取消或已支付订单的支付链接。

接口变更

user

  • POST /api/work_tasks/paying_order

    • 功能:查询 work task 当前是否存在支付中的订单。
    • 变更:新增接口;存在 paying 订单时返回订单、支付会话和从订单快照派生的 pre_calc 形态数据;不存在时返回空结果,不再按支付类型重新计算金额。
    • 调用方:work task 的用户侧买家和 artist 侧接单画师都可以调用;artist 发起改价前也应调用该接口检查是否存在支付中的订单。
    • 异常:如果发现同一 work task 存在多个 paying 订单,返回 error code 20003;如果同一 paying 订单存在多个 active payment session,返回 error code 21003
  • POST /api/user/pay/work_task/create_checkout_session

    • 功能:创建 work task 支付订单和支付会话。
    • 变更:不再自动取消已有订单;如果 work task 已存在 paying 订单,返回 error code 20001
  • POST /api/orders/cancel_by_worktask

    • 功能:用户主动取消 work task 的支付中订单。
    • 变更:取消动作仍保留,但必须由前端明确触发;后端在事务中锁定 work task 和订单后取消。
  • POST /api/orders/continue

    • 功能:继续支付已有订单。
    • 变更:PayPal pending / processing session 会直接复用已有 pay_url;本地 expired session 会先同步 PayPal 远端状态,远端仍可继续时复用原 pay_url,远端明确不可继续时才允许新建。返回前会重新锁定订单确认仍为 paying;如果新 session 创建后订单已不再是 paying,不会把新支付链接返回给前端。
    • 幂等:PayPal capture 请求会使用稳定的 PayPal-Request-Id,避免 capture retry 造成重复处理。
  • GET /api/work_tasks/info

    • 功能:获取 work task 详情。
    • 变更:payingOrder 增加金额和计算过程字段,便于首次进入页面展示支付中订单。
  • POST /api/work_task_price_changes/create

    • 功能:用户发起改价。
    • 变更:如果 work task 已有 paying 订单,返回 error code 20001。该检查与 artist 侧共用同一套后端互斥规则。
  • POST /api/work_task_price_changes/approve

    • 功能:用户同意 artist 发起的改价。
    • 变更:如果 work task 已有 paying 订单,返回 error code 20001。该检查与 artist 侧共用同一套后端互斥规则。

artist_center

  • POST /api/artist_center/work_task_price_changes/create

    • 功能:artist 发起改价。
    • 变更:如果 work task 已有 paying 订单,返回 error code 20001。该检查与 user 侧共用同一套后端互斥规则。
  • POST /api/artist_center/work_task_price_changes/approve

    • 功能:artist 同意用户发起的改价。
    • 变更:如果 work task 已有 paying 订单,返回 error code 20001。该检查与 user 侧共用同一套后端互斥规则。

流程图

前端支付入口流程

后端互斥流程

paying_order 查询流程

继续支付并发保护流程

状态图

Work task 支付与改价互斥状态

PayPal 本地订单/session 状态

支付中订单查询状态

接口示例

user

POST /api/work_tasks/paying_order

  • 功能说明:查询某个 work task 是否存在支付中的订单。
  • 变更说明:前端在用户点击支付按钮时必须调用此接口获取最新状态;不要只依赖 work_tasks/infopayingOrder 快照。
  • 该接口是纯查询接口,只查询已有 paying 订单;不会根据 typepay_channelwallets 重新计算金额。
  • 授权说明:work task 的买家,或该 work task 所属 artist 的用户账号,都可以调用该接口;无关用户返回 404

请求参数

字段类型必填说明
work_task_idnumberwork task id

其他校验规则

  • 仅校验 work_task_id
  • 旧客户端如果继续传 typepay_channelwallets,后端不会使用这些字段做金额计算。

请求示例

{
  "work_task_id": 123
}

响应示例:存在支付中 PayPal 订单

{
  "data": {
    "has_paying_order": true,
    "paying_order": {
      "id": 456,
      "status": "paying",
      "pay_type": "stage_pay",
      "busable_info": {
        "work_task_id": 123,
        "pay_type": "stage_pay"
      },
      "amount": 143,
      "currency_id": 2,
      "currency": {
        "id": 2,
        "code": "USD"
      },
      "init_amount": 1000,
      "init_currency_id": 1,
      "init_currency": {
        "id": 1,
        "code": "CNY"
      },
      "amount_calc_process": {
        "init_amount": {
          "amount": 1000,
          "currency": { "code": "CNY" }
        },
        "ret_amount": {
          "amount": 143,
          "currency": { "code": "USD" }
        },
        "process": []
      },
      "created_at": "2026-06-10T08:00:00.000000Z"
    },
    "payment": {
      "pay_channel": "paypal",
      "paypal_order_id": "PAYPAL-ORDER-123",
      "pay_url": "https://www.paypal.com/checkoutnow?token=PAYPAL-ORDER-123",
      "session_status": "pending"
    },
    "pre_calc": {
      "before_amount": {
        "amount": 1000,
        "currency": { "code": "CNY" }
      },
      "after_amount": {
        "amount": 143,
        "currency": { "code": "USD" }
      },
      "process": []
    }
  }
}

active payment session 规则

  • PayPal active 状态:pendingprocessingconflicted
  • Stripe active 状态:pendingprocessing
  • Alipay active 状态:pendingprocessing
  • 正常系统路径下,同一 paying order 同一时刻只应存在一个 active payment session。
  • 如果查到多个 active session,接口返回 error code 21003,不会按渠道优先级静默选择。

响应示例:不存在支付中订单

{
  "data": {
    "has_paying_order": false,
    "paying_order": null,
    "payment": null,
    "pre_calc": null
  }
}

错误响应

400:同一 work task 存在多个支付中订单,返回 code=20003。正常业务路径不会产生该状态,通常表示历史数据或极端并发异常。

{
  "code": 20003,
  "message": "Work task has multiple paying orders"
}

400:同一支付中订单存在多个 active payment session,返回 code=21003

{
  "code": 21003,
  "message": "Work task paying order has multiple active payment sessions"
}

422:参数校验失败。

{
  "message": "The selected work task id is invalid."
}

POST /api/user/pay/work_task/create_checkout_session

  • 功能说明:创建 work task 支付订单和支付会话。
  • 变更说明:后端不再自动取消已有 paying 订单;存在支付中订单时直接拒绝创建新支付。

请求参数

沿用原有参数:

字段类型必填说明
work_task_idnumberwork task id
typestringstage_pay / full_pay / price_change
pay_channelstringstripe / alipay / paypal
walletsnumber[]参与抵扣的 credit wallet id 列表
callbackstring外部支付完成后回跳前端页面地址

错误响应

400:work task 已存在支付中订单,返回 code=20001

{
  "code": 20001,
  "message": "Work task already has a paying order"
}

400:存在未完成改价,不能发起 stage_pay / full_pay

{
  "message": "Has pending price change"
}

POST /api/orders/continue

  • 功能说明:继续支付已有订单。
  • 变更说明:PayPal pending / processing session 会复用已有 pay_url;PayPal 本地 expired session 会先同步远端状态,远端仍可继续时复用旧 pay_url,远端明确不可继续时才创建新 PayPal session。返回支付数据前,后端会重新锁定订单确认仍为 paying。如果新建 session 后订单已经被取消或支付完成,后端不会返回新支付链接。
  • PayPal capture 请求使用稳定 PayPal-Request-Id,同一个 PayPal order 的 capture retry 保持幂等。

请求参数

字段类型必填说明
idnumberorder id

请求示例

{
  "id": 456
}

响应示例:继续 PayPal 支付

{
  "data": {
    "pay_channel": "paypal",
    "pay_data": {
      "pay_url": "https://www.paypal.com/checkoutnow?token=PAYPAL-ORDER-123",
      "paypal_order_id": "PAYPAL-ORDER-123"
    },
    "amount": 143,
    "currency": {
      "code": "USD"
    }
  }
}

错误响应

400:订单不存在、订单状态不是 paying,或新 session 创建后订单已不再是 paying

{
  "message": "Order status is not paying"
}

400:PayPal session 已进入冲突状态,返回 code=21001

{
  "code": 21001,
  "message": "PayPal payment conflict"
}

400:同步本地 expired PayPal session 时发现远端已经完成支付,本地订单已变为非 paying,不会返回任何新支付链接。

{
  "message": "Order status is not paying"
}

400:同一个 PayPal session 正在被其他请求同步或 capture,返回 code=21002

{
  "code": 21002,
  "message": "Payment is being processed"
}

POST /api/orders/cancel_by_worktask

  • 功能说明:取消某个 work task 当前支付中的订单。
  • 变更说明:该接口是显式取消入口;创建新支付不会再自动调用取消逻辑。

请求参数

字段类型必填说明
work_task_idnumberwork task id

请求示例

{
  "work_task_id": 123
}

响应示例

{
  "ok": true
}

错误响应

400:work task 不存在或不属于当前用户。

{
  "message": "Work task not found"
}

GET /api/work_tasks/info

  • 功能说明:获取 work task 详情。
  • 变更说明:payingOrder 增加支付金额和计算过程字段,供首次进入页面展示。

请求参数

字段类型必填说明
idnumberwork task id

响应字段变更

data.payingOrder 现在包含以下字段:

字段说明
idorder id
statusorder 状态,当前只会返回 paying 的关联订单
busable_id / busable_type业务关联信息
busable_info支付业务信息,包含 pay_type
amount / currency_id实际外部支付金额和币种
init_amount / init_currency_id原始金额和币种
amount_calc_process金额计算过程
created_at订单创建时间

注意事项

  • payingOrder 是页面详情加载时的快照,只用于首次展示。
  • 用户点击继续支付、取消订单、创建支付前,仍应调用 POST /api/work_tasks/paying_order 获取最新状态。

POST /api/work_task_price_changes/create

  • 功能说明:用户发起改价。
  • 变更说明:如果 work task 已存在 paying 订单,拒绝创建改价。

错误响应

400:work task 已存在支付中订单,返回 code=20001

{
  "code": 20001,
  "message": "Work task already has a paying order"
}

POST /api/work_task_price_changes/approve

  • 功能说明:用户同意 artist 发起的改价。
  • 变更说明:如果 work task 已存在 paying 订单,拒绝审批改价。

错误响应

400:work task 已存在支付中订单,返回 code=20001

{
  "code": 20001,
  "message": "Work task already has a paying order"
}

artist_center

POST /api/artist_center/work_task_price_changes/create

  • 功能说明:artist 发起改价。
  • 变更说明:如果 work task 已存在 paying 订单,拒绝创建改价。

错误响应

400:work task 已存在支付中订单,返回 code=20001

{
  "code": 20001,
  "message": "Work task already has a paying order"
}

POST /api/artist_center/work_task_price_changes/approve

  • 功能说明:artist 同意用户发起的改价。
  • 变更说明:如果 work task 已存在 paying 订单,拒绝审批改价。

错误响应

400:work task 已存在支付中订单,返回 code=20001

{
  "code": 20001,
  "message": "Work task already has a paying order"
}

兼容性说明

  • 前端原来“创建新支付前自动取消旧订单”的假设不再成立。
  • 如果存在 paying_order,前端应展示“继续支付”和“取消订单”两个动作。
  • PayPal 场景下,continue 返回旧 pay_url 是预期行为,不表示后端没有刷新状态。
  • PayPal 场景下,本地 expires_at 到期不再代表支付失效;后台 payment:expire-sessions 只同步 PayPal 远端状态,不会自动取消本地 PayPal 订单。
  • 如果 continue 返回 400 Order status is not paying,前端应刷新 work task/order 状态,不要继续使用之前缓存的支付链接。
  • stage_payfull_payprice_change 在同一个 work task 上互斥;如果用户已经发起改价,在该改价结束前不能再发起阶段支付或全款支付。