确认支付
契约 v1.0.0
提交首次支付,或在上一次尝试明确失败后发起新尝试。新尝试须在 expires_at 之前、未超过最多尝试次数(默认 3 次)并通过风控; 换卡时由付款人重新提供卡信息,并使用新的幂等键。 已有在途尝试(processing、requires_action)时返回原状态或 409 payment_in_progress,不触发新扣款。 需要认证时返回 requires_action 与 next_action,由商户把付款人浏览器带到认证页。 原始卡数据只在平台隔离的卡处理环境中瞬时使用,不落库、不写日志。
请求头
| 请求头 | 必填 | 格式 | 说明 |
|---|---|---|---|
| Authorization | 是 | Bearer <API_KEY> | 服务端密钥,只能在商户服务端使用;商户与环境由密钥推导。缺失、无效、撤销或过期返回 401。 |
| Content-Type | 是 | application/json | 请求体为 UTF-8 编码的 JSON。 |
| Idempotency-Key | 是 | 1–255 个可见 ASCII 字符 | 幂等键,1–255 个可见 ASCII 字符。作用域为商户、环境、方法与路径:同键且安全业务参数一致时复用原业务对象,参数冲突返回 409 idempotency_conflict。 一次提交使用一个键,网络重试沿用原键;换卡等新的提交使用新键。 同键并发时只有一个请求取得执行权,其余请求等待其事务提交后返回同一对象(200);等待超时返回 409 operation_in_progress(retryable=true),稍后用原键重试。 被 400、401、403、422 拒绝的请求不占用幂等键,修正后用新键提交。 |
请求体
JSON 对象 ConfirmPaymentRequest
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
payment_method_data | PaymentMethodData | 条件:与 payment_method_id 二选一;创建时已提供 payment_method_id 可都不传 | 直接传卡的支付方式数据,只在 confirm=true 且商户已开通直接传卡能力时接受。 原始卡字段只在平台隔离的卡处理环境中瞬时使用,绝不写入数据库、缓存、队列、日志或错误报告。与 payment_method_id 二选一。 |
type | string | 必填 | 支付方式类型,首期只支持 card。
示例: |
card | CardDetails | 必填 | 原始卡信息,只在受控卡处理链路中瞬时使用。 |
payment_method_id | string | 条件:与 payment_method_data 二选一;创建时已提供可不传 | 适配器支持的限域支付凭据。 约束: 示例: |
return_url | string(uri) | 条件:创建时未提供则必填 | 认证完成后的商户返回地址(创建时未提供则必填),须匹配当前环境已登记的回跳地址规则:按协议、主机(精确或一级通配)、端口与路径前缀(按路径段)匹配,查询串与片段不参与匹配。平台回跳时原样使用该地址,不追加任何参数。 约束: 示例: |
billing_details | BillingDetails | 条件:按当前上游能力要求;缺失时在提交上游前返回 400, error.field 指明字段 | 持卡人最小必要信息,是否必填由当前上游适配器能力决定。 |
name | string | 否 | 持卡人姓名。 约束: 示例: |
email | string(email) | 否 | 持卡人邮箱。 约束: 示例: |
address | Address | 否 | 账单地址。 |
browser_info | BrowserInfo | 条件:按当前上游能力要求;缺失时在提交上游前返回 400, error.field 指明字段 | 付款人真实浏览器上下文,用于 3DS。必须来自付款人浏览器,不能用商户服务器信息替代;是否必填由当前上游适配器能力决定,缺失必要信息时在提交上游前返回明确错误。 |
user_agent | string | 否 | 付款人浏览器的 User-Agent。 约束: 示例: |
accept_header | string | 否 | 付款人浏览器的 Accept 请求头。 约束: 示例: |
language | string | 否 | 浏览器语言(BCP 47)。 约束: 示例: |
screen_width | integer(int32) | 否 | 约束: 示例: |
screen_height | integer(int32) | 否 | 约束: 示例: |
color_depth | integer(int32) | 否 | 约束: 示例: |
timezone_offset_minutes | integer(int32) | 否 | 时区偏移分钟数,采用 JavaScript getTimezoneOffset() 的符号语义,例如 UTC+8 为 -480。 约束: 示例: |
javascript_enabled | boolean | 否 | 示例: |
ip_address | string | 否 | 付款人 IPv4 或 IPv6 地址,须按受信代理链获取,不能用商户服务器 IP 替代。 约束: 示例: |
请求示例
curl -X POST 'https://api.payhub.bbpays.site/v1/payments/<PAYMENT_ID>/confirm' \
-H 'Authorization: Bearer <TEST_SERVER_API_KEY>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: <UNIQUE_OPERATION_KEY>' \
-d '{
"payment_method_data": {
"type": "card",
"card": {
"number": "<UPSTREAM_TEST_CARD_NUMBER>",
"exp_month": 12,
"exp_year": 2030,
"cvc": "<UPSTREAM_TEST_CVC>"
}
},
"browser_info": {
"user_agent": "Mozilla/5.0",
"accept_header": "text/html,application/xhtml+xml",
"language": "zh-CN",
"screen_width": 1366,
"screen_height": 768,
"color_depth": 24,
"timezone_offset_minutes": -480,
"javascript_enabled": true,
"ip_address": "203.0.113.10"
}
}'需要替换的占位符
<PAYMENT_ID>:创建支付返回的 id(pay_ 开头)<TEST_SERVER_API_KEY>:测试环境服务端 API Key:在商户后台「API Key」页面创建<UNIQUE_OPERATION_KEY>:你为这次提交生成的幂等键(如 UUID),网络重试沿用同一个<UPSTREAM_TEST_CARD_NUMBER>:测试卡号:登录商户后台「接口文档与测试指南」查看<UPSTREAM_TEST_CVC>:测试卡 CVC:与测试卡号一起在商户后台查看
说明
服务端 API Key 只能在你的服务端使用,文档站不提供在线调试。获取测试 Key:商户后台 API Key(在新标签页打开商户后台)(测试环境;需要所有者、管理员或开发者)
响应
已得出当前结果(succeeded、failed 或 requires_action),或同键幂等复用。
{
"id": "pay_demo_004",
"merchant_order_no": "ORDER-DEMO-004",
"environment": "test",
"amount": 10000,
"currency": "USD",
"status": "succeeded",
"resolution_status": "known",
"captured_amount": 10000,
"refunded_amount": 0,
"refund_returned_amount": 0,
"refund_reserved_amount": 0,
"refund_status": "none",
"authentication_status": "not_required",
"reconciliation_status": "pending",
"upstream_receipt_status": "not_received",
"latest_attempt_id": "pat_demo_005",
"attempt_count": 2,
"version": 6,
"next_action": null,
"failure": null,
"description": "示例订单",
"customer_reference": "CUSTOMER-DEMO-004",
"metadata": {
"business_order_type": "one_time_purchase"
},
"card": {
"brand": "visa",
"last4": "1234"
},
"created_at": "2026-09-23T09:00:00Z",
"succeeded_at": "2026-09-23T09:12:05Z",
"expires_at": "2026-09-23T09:30:00Z",
"request_id": "req_demo_011"
}响应字段
返回对象 字段说明见 对象小节。
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 必填 | 平台支付号。 示例: |
merchant_order_no | string | 必填 | 商户订单号,按原值区分大小写。 示例: |
environment | string | 必填 | 环境:test 为测试,live 为生产。测试与生产对象相互隔离。
示例: |
amount | integer(int64) | 必填 | 订单金额,最小货币单位。首次提交上游后不可修改。 约束: 示例: |
currency | string | 必填 | 大写三字母币种代码。 约束: 示例: |
status | string | 必填 | Payment 状态。客户端不得把未知状态当作成功。
示例: |
resolution_status | string | 必填 | 结果确认程度。
示例: |
captured_amount | integer(int64) | 必填 | 已确认扣款金额;成功时等于 amount。 约束: 示例: |
refunded_amount | integer(int64) | 必填 | 已确认的有效退款本金,即成功退款累计减去已确认的退款退回;不含处理中的退款,也不含平台异常退款。 约束: 示例: |
refund_returned_amount | integer(int64) | 必填 | 已确认退回的退款本金累计。 约束: 示例: |
refund_reserved_amount | integer(int64) | 必填 | 审核中及处理中退款的本金占用。 约束: 示例: |
refund_status | string | 必填 | 按有效退款本金 refunded_amount 派生的退款汇总状态:none、partial、full。
示例: |
authentication_status | string可为空 | 必填 | 当前尝试的 3DS 认证状态,取认证会话状态:not_required、pending、challenge、authenticated、failed、abandoned、expired,须容忍新增取值。 认证成功(authenticated)不等于扣款成功,不要据此判断支付结果。尚无尝试或未进入认证时为 null。 示例: |
reconciliation_status | ReconciliationStatus | 必填 | 平台资金处理进度,仅供对账参考,不代表支付是否成功,也不代表何时打款。
|
upstream_receipt_status | UpstreamReceiptStatus | 必填 | 平台资金处理进度,仅供对账参考,不代表支付是否成功,也不代表何时打款。
|
latest_attempt_id | string可为空 | 必填 | 最近一次支付尝试号;尚未提交尝试时为 null。 示例: |
attempt_count | integer(int32) | 必填 | 已发起的尝试次数;默认最多 3 次。 约束: 示例: |
version | integer(int64) | 必填 | 对象版本,每次有效业务变更递增。比较版本来丢弃旧事件,不要按状态名排序。 约束: 示例: |
next_action | NextAction可为空 | 必填 | status=requires_action 时需要付款人完成的动作,其他情况为 null。 |
type | string | 必填 | 首期公共 API 只开放 redirect_to_url。客户端须容忍未来新增的类型。
示例: |
authentication_session_id | string | 必填 | 认证会话号,关联具体的支付尝试。 示例: |
url | string(uri) | 必填 | 平台认证承载页地址。 示例: |
expires_at | string(date-time) | 必填 | 认证访问的失效时间(最长 10 分钟)。 示例: |
failure | PaymentFailure可为空 | 必填 | latest_attempt_id 所指尝试明确失败时的原因,否则为 null。 |
code | string | 必填 | 失败原因码。 示例: |
message | string | 必填 | 脱敏说明。 示例: |
attempt_id | string | 必填 | 失败的支付尝试号。 示例: |
description | string | 否 | 订单摘要。 约束: 示例: |
customer_reference | string | 否 | 商户侧客户标识。 约束: 示例: |
metadata | object | 否 | 商户自定义键值,最多 20 个键;键最长 40 字符,值为最长 200 字符的字符串。禁止放入卡号等敏感数据,平台会做检查并拒绝。 约束: 示例: |
card | CardSummary可为空 | 必填 | 最近一次尝试所用卡的脱敏信息;尚无卡信息时为 null。 |
brand | string | 必填 | 卡品牌。 示例: |
last4 | string | 必填 | 卡号后四位。 约束: 示例: |
created_at | string(date-time) | 必填 | 创建时间。 示例: |
succeeded_at | string(date-time)可为空 | 必填 | 确认支付成功的时间;未成功时为 null。 示例: |
expires_at | string(date-time) | 必填 | 可发起新尝试的截止时间,创建时确定(默认创建后 30 分钟,平台可在 5–120 分钟之间调整,以本字段为准)。到期只关闭新的付款入口,不否定已提交的支付。 示例: |
request_id | string | 必填 | 本次请求的唯一标识,用于排查与支持。 示例: |
可能的错误码
| HTTP | 错误码 | 何时出现 | 如何处理 |
|---|---|---|---|
| 400 | invalid_request | 缺 Idempotency-Key;订单号含非法字符;payment_method_data 与 payment_method_id 同时提交;confirm=false 携带卡信息;confirm=true 缺 return_url;收银会话两组身份参数同时提交或都缺;缺少上游要求的 browser_info / billing_details 字段;游标、时间格式、limit 不合法 | 按 error.field 修正后重新提交,不要自动重扣 |
| 403 | capability_disabled | 未开通直接传卡;支付或退款申请能力被关闭;该币种未开放 | 改用托管收银台或联系平台运营 |
| 403 | merchant_not_active | 商户被暂停或关闭;已受理业务的回调、查询、通知不受影响 | 联系平台运营 |
| 404 | not_found | ID 错误;用测试 Key 查询生产对象;查询平台异常退款(不通过开放 API 返回) | 核对 ID 与 Key 的环境;平台不会透露对象是否属于其他商户 |
| 409 | idempotency_conflict | 复用了键但金额、币种、订单号等不同 | 属于集成缺陷:检查幂等键生成逻辑;不要改参数后重放原键;新操作使用新键 |
| 409 | operation_in_progress | 并发发送了同键请求 | 稍后用同一个键重试,或查询 related_resource_id;不要换键 |
| 409 | payment_in_progress | 在途(processing / requires_action)时再次 confirm,或用新键对同一订单重复提交 | 查询 related_resource_id 指向的 Payment,按其状态继续(引导付款人完成认证或等待结果);不要新建订单 |
| 409 | invalid_state | 取消 processing / succeeded 的支付;对已过期、已取消、已成功的订单 confirm;为不可支付的订单创建收银会话 | 查询对象最新状态后决定 |
| 409 | attempt_limit_reached | 默认最多 3 次,可被平台收紧 | 不能再对该订单扣款;确认原支付关闭后用新订单号 |
| 422 | card_brand_not_supported | 商户配置与上游能力的交集不含该品牌 | 提示付款人换卡 |
| 422 | payment_method_unusable | payment_method_id 不属于当前环境、上游账户或本商户 | 重新获取支付凭据 |
| 422 | url_not_allowed | return_url / cancel_url 不匹配已登记的地址规则 | 在 商户后台回跳地址页面 登记后重新提交 |
| 422 | risk_rejected | 命中风险名单等基础规则;不说明具体规则与阈值 | 不要尝试绕过;有疑问联系平台 |
| 503 | temporarily_unavailable | 平台暂停了该通道的新交易;响应不说明内部原因 | 稍后用原幂等键重试;本次没有产生 Payment 或扣款;持续出现时联系平台运营 |
| 401 | unauthenticated通用 | 未带 Authorization;Key 复制不完整;轮换后旧 Key 已过期;Key 被撤销 | 检查凭据,在 商户后台 API Key 页面 核对 Key 状态与环境 |
| 403 | forbidden通用 | 只读 Key 创建退款;getEvent 缺少事件所属业务的读权限;请求来源 IP 不在 Key 的白名单时返回更具体的 ip_not_allowed | 使用具备所需 scope 的 Key;不要绕过限制 |
| 429 | rate_limited通用 | 商户、Key 维度的频率限制;换 Key 不能绕过商户总限额 | 等待 Retry-After 秒后用原幂等键重试 |
| 500 | internal_error通用 | — | 用原幂等键重试或按商户订单号 / 退款号查询;不能换键重扣 |
| 502 / 503 | upstream_unavailable通用 | 上游或平台依赖故障,平台无法确认本次请求是否已生效 | 同上 |
注意事项
- 用于
confirm=false创建后的首次提交,或上一次尝试明确失败后的新尝试。 - 新尝试须在
expires_at之前、未超过最多尝试次数(默认 3 次,可能被平台收紧)并通过风控。 - 换卡重试使用新的幂等键;同一个键重放只会返回原操作的结果,不会用新卡扣款。
- 已有在途尝试(
processing、requires_action)时返回原状态或 409payment_in_progress:引导付款人完成认证或等待结果,不要新建订单。