PayPal CNY 支付接入(前端对接说明)

变更概览

本次新增的是买家端 PayPal 支付能力。

前端需要理解的业务边界:

  • PayPal 只作为支付渠道,不是 artist 入驻方式。
  • 只支持原始订单币种为 CNY 的场景。
  • 用户用 PayPal 支付时,后端会把外部支付金额换算成 USD 发起支付。
  • artist 结算和提现逻辑不变,仍然走现有 Alipay 钱包和提现账户。

前端对接时,可以把 PayPal 理解成“和 Alipay 一样需要跳转到外部页面支付”的渠道。

前端需要对接的接口

1. Work Task 金额预计算

POST /api/user/pay/work_task/pre_calc

用途:

  • 用户切换支付方式时重新计算应付金额。
  • pay_channel 现在支持 paypal

请求参数:

字段类型必填说明
work_task_idnumberwork task id
typestringstage_pay / full_pay / price_change
pay_channelstringstripe / alipay / paypal
walletsnumber[]参与抵扣的 credit wallet id 列表

请求示例:

{
  "work_task_id": 123,
  "type": "stage_pay",
  "pay_channel": "paypal",
  "wallets": []
}

响应要点:

  • 前端继续读取 data.before_amountdata.after_amountdata.process
  • 当原始币种为 CNYpay_channel=paypal 时,after_amount.currency.code 会变成 USD
  • 这表示外部支付金额按 USD 发起,不表示 artist 收款币种变更。

响应示例:

{
  "data": {
    "before_amount": {
      "amount": 1000,
      "currency": {
        "code": "CNY"
      }
    },
    "after_amount": {
      "amount": 143,
      "currency": {
        "code": "USD"
      }
    },
    "process": [
      {
        "type": "init_from_work_task_stage"
      },
      {
        "type": "cny_currency_conversion"
      }
    ]
  }
}

2. Work Task 创建支付会话

POST /api/user/pay/work_task/create_checkout_session

用途:

  • 用户确认支付后创建订单和支付会话。

请求参数:

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

前端处理规则:

  • pay_channel=stripe
    • 使用 pay_data.client_secretpay_data.checkout_session_id 打开现有 Stripe 支付弹窗。
  • pay_channel=alipay
    • 使用 pay_data.pay_url 打开新标签页或新窗口。
  • pay_channel=paypal
    • 使用 pay_data.pay_url 打开新标签页或新窗口。
  • amount=0
    • 不打开外部支付,走零金额确认接口。

响应示例:

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

说明:

  • 顶层的 pay_url / paypal_order_id 是兼容字段。
  • 新前端统一使用 pay_channel + pay_data 即可,不要依赖顶层兼容字段。

3. Work Task 零金额确认

POST /api/user/pay/work_task/confirm_zero

用途:

  • create_checkout_session 返回 amount = 0 时,前端调用此接口完成支付确认。

请求参数:

字段类型必填说明
out_trade_nostring订单号

说明:

  • out_trade_no 来自零金额场景的 pay_data.out_trade_no
  • paypal 非零金额支付不调用这个接口。

4. Work Task 继续支付

POST /api/orders/continue

用途:

  • work task 已有 paying 订单时,继续支付未完成订单。

请求参数:

字段类型必填说明
idnumberorder id

前端处理规则:

  • data.pay_channel == stripe
    • 使用 data.pay_data.client_secretdata.pay_data.checkout_session_id
  • data.pay_channel == paypal
    • 使用 data.pay_data.pay_url 打开新标签页或新窗口。
  • 当前接口没有 alipay 继续支付返回分支,前端不需要为这个接口补 alipay 处理。

响应示例:

{
  "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"
    },
    "pay_url": "https://www.paypal.com/checkoutnow?token=PAYPAL-ORDER-123",
    "paypal_order_id": "PAYPAL-ORDER-123"
  }
}

说明:

  • 旧页面可能直接读取顶层 client_secret / checkout_session_id / pay_url
  • 新前端统一读取 pay_channel + pay_data

5. Product 金额预计算

POST /api/user/pay/product/pre_calc

用途:

  • 商品支付弹窗中,用户切换支付方式或钱包抵扣时重新计算金额。

请求参数:

字段类型必填说明
product_option_idnumberproduct option id
pay_channelstringstripe / alipay / paypal
walletsnumber[]参与抵扣的 credit wallet id 列表

响应规则与 work task 相同:

  • CNY + paypal 时,after_amount.currency.code 会是 USD

6. Product 创建支付会话

POST /api/user/pay/product/create_checkout_session

用途:

  • 商品购买时创建订单和支付会话。

请求参数:

字段类型必填说明
product_option_idnumberproduct option id
pay_channelstringstripe / alipay / paypal
walletsnumber[]参与抵扣的 credit wallet id 列表

前端处理规则:

  • pay_channel=stripe
    • 使用 pay_data.client_secretpay_data.checkout_session_id 打开 Stripe 弹窗。
  • pay_channel=alipay
    • 使用 pay_data.pay_url 打开新标签页或新窗口。
  • pay_channel=paypal
    • 使用 pay_data.pay_url 打开新标签页或新窗口。
  • amount=0
    • 不打开外部支付,走商品零金额确认接口。

响应示例:

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

7. Product 零金额确认

POST /api/user/pay/product/confirm_zero

用途:

  • 商品支付金额为 0 时,前端调用此接口完成支付确认。

请求参数:

字段类型必填说明
license_idnumberproduct license id

说明:

  • license_id 来自零金额场景的 pay_data.license_id

8. Product 继续支付

POST /api/product_licenses/continue_payment

用途:

  • 商品订单处于 paying 状态时继续支付。

请求参数:

字段类型必填说明
license_idnumberproduct license id

前端处理规则:

  • data.pay_channel == stripe
    • 使用 data.pay_data.client_secretdata.pay_data.checkout_session_id
  • data.pay_channel == alipay
    • 使用 data.pay_data.pay_url 打开新标签页或新窗口。
  • data.pay_channel == paypal
    • 使用 data.pay_data.pay_url 打开新标签页或新窗口。
  • amount = 0
    • 打开零金额确认流程。

响应示例:

{
  "data": {
    "pay_channel": "paypal",
    "pay_data": {
      "pay_url": "https://www.paypal.com/checkoutnow?token=PAYPAL-ORDER-456",
      "paypal_order_id": "PAYPAL-ORDER-456"
    },
    "status": "paying"
  }
}

前端统一处理建议

建议前端统一按下面的方式分支,不再为不同页面写多套判断:

if (data.amount === 0) {
  // 走 confirm_zero
} else if (data.pay_channel === 'stripe') {
  // 用 pay_data.client_secret + pay_data.checkout_session_id
} else if (data.pay_channel === 'alipay' || data.pay_channel === 'paypal') {
  // window.open(data.pay_data.pay_url, '_blank')
}

建议:

  • 新代码统一使用 pay_channel + pay_data
  • 不要依赖顶层兼容字段。
  • PayPal 交互按 Alipay 的外跳模式处理,不要走 Stripe 弹窗。

注意事项

1. PayPal 只支持原始 CNY 订单

前端展示 PayPal 支付入口时,应限制在原始币种为 CNY 的场景。

如果前端仍把 paypal 提交给非 CNY 订单,后端会返回:

{
  "message": "PayPal only supports CNY"
}

或:

{
  "message": "PayPal only supports CNY currency"
}

2. 前端看到 USD 是正常现象

对于 CNY + paypal

  • 订单原价仍然是 CNY
  • 外部支付金额会换算成 USD
  • 所以预计算和创建支付会话返回里的 amount/currency 可能是 USD

前端不要把这理解成 artist 改为美元结算。

3. Work Task 外部支付建议传 callback

work task 的 alipay/paypal 外部支付,建议在 create_checkout_session 时传当前页面 URL 作为 callback

这样用户在 PayPal 页面支付完成或取消后,后端可以回跳到原页面。

4. Product 继续支付要补 PayPal 分支

当前前端商品续付页如果只判断 stripe/alipay,会漏掉 paypal

需要按 data.pay_channel === 'paypal' 增加外跳处理。

5. 不要直接调用 PayPal 回调接口

下面这些接口是后端内部支付回调使用,前端不需要主动调用:

  • GET /api/user/paypal/return
  • GET /api/user/paypal/cancel
  • POST /api/user/paypal/notify

前端只需要:

  • 创建支付会话
  • 打开 pay_url
  • 用户回到页面后按现有页面刷新/继续支付逻辑处理即可

本次前端最小改动范围

如果前端只做最小改动,至少需要补这几处:

  • 在支付方式枚举中增加 paypal
  • 在 work task 支付弹窗中增加 paypal 分支,行为与 alipay 类似:外跳 pay_url
  • 在 product 支付弹窗中增加 paypal 分支,行为与 alipay 类似:外跳 pay_url
  • 在 product 继续支付中增加 paypal 分支
  • 统一使用 pay_channel + pay_data 处理支付结果