开发者文档
立即开通

接入支付宝收款

BBpay 替你处理支付宝的应用申请、签名验签和异步通知。你只需要一个 API Key:后端调一次接口拿到收银台链接, 买家付完款,BBpay 用 Webhook 通知你的网站。

测试和正式两种 Key。bbp_test_ 开头的 Key 走支付宝沙箱,不扣真钱,不用绑定就能跑通流程;bbp_live_ 开头的 Key 真实收款,需要先在后台「收款账户」绑定你自己的支付宝。两种 Key 的接口完全一样。

快速开始

  1. 开通并拿到 API Key。在 bbpay.uk/signup 付款开通,开通后会直接显示一个bbp_test_ 开头的 Key(只显示一次)。之后可以在后台「API 密钥」页随时新建或作废。
  2. 后端创建订单。用 Key 调 POST /api/vibe/orders,拿到 pay_url,把买家带过去付款。
  3. 确认付款。在后台「Webhook」页填你的接收地址,付款成功时 BBpay 会 POST 通知你;也可以随时调查询接口确认。
curl -X POST https://bbpay.uk/api/vibe/orders \
  -H "Authorization: Bearer $BBPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "out_trade_no": "ORDER-1001",
    "amount": "9.90",
    "subject": "AI 简历润色 · 单次",
    "return_url": "https://your.site/thanks",
    "channel": "page"
  }'

让 AI 帮你接

我们把这份文档整理成了一份给 AI 读的纯文本:https://bbpay.uk/llms.txt。在 Cursor、Claude Code、 Windsurf 里把下面这段话发给 AI,它会自己读文档并写好代码。

读一下 https://bbpay.uk/llms.txt ,用 BBpay 给我的网站接入支付宝付款:
1. 后端加一个下单接口,调 BBpay 创建订单,返回 pay_url,前端跳过去付款;
2. 加一个 Webhook 接口,验签后把订单标记为已付款;
3. API Key 和 Webhook 密钥从环境变量 BBPAY_KEY、BBPAY_WEBHOOK_SECRET 读。

鉴权

所有接口都用请求头带 API Key:

Authorization: Bearer bbp_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • Key 只能放在你的服务器端(环境变量),不要写进前端代码或提交到 Git。
  • 泄露了就到后台「API 密钥」页作废,再建一个新的,旧 Key 立即失效。
  • 套餐到期后有 3 天宽限期,之后接口返回 402;在后台「账号与续费」续费后立刻恢复,Key 不用换。

创建订单

POST/api/vibe/orders

创建一笔支付宝订单,返回收银台链接。买家打开链接后用支付宝扫码或登录付款,订单 15 分钟内未付自动关闭。

请求参数(JSON)

参数必填说明
out_trade_no是你的订单号,在你自己的订单里唯一即可,和其他商户重复也没关系。只能用字母、数字、_、-,最长 64 位,不能以 SUB 开头(BBpay 内部保留)。建议带上你的前缀,例如 LF-20261101-0042。
amount是金额,单位元,最多两位小数,例如 "9.90"。范围 0.01 ~ 100000000。
subject是商品名称,会显示在支付宝收银台和买家账单里,最长 256 字。
return_url否买家付完款后跳回的地址。跳回只代表买家离开了收银台,是否付款以 Webhook 或查询结果为准。
channel否page(电脑网页,默认,显示二维码)或 wap(手机网页,唤起支付宝 App)。
notify_url否这一单的回调地址。带了它,这一单的结果改为以表单格式推到这里,不再推你在后台填的 Webhook 地址。适合只认表单回调的商城系统,见 按单回调。

返回

{
  "out_trade_no": "ORDER-1001",
  "alipay_out_trade_no": "ORDER-1001",
  "livemode": false,
  "pay_url": "https://openapi-sandbox.dl.alipaydev.com/gateway.do?..."
}
  • 同一个 out_trade_no 在未付款时重复调用(金额相同),会返回新的收银台链接,方便买家重新付款。
  • 订单号已付款、已关闭,或金额不同,返回 409,请换一个新订单号。
  • 返回里的 alipay_out_trade_no 是发给支付宝的单号,一般和你的 out_trade_no 一样;万一和其他商户撞号,BBpay 会另生成一个。只有去支付宝账单里对账时才用得到它,查单和 Webhook 都用你自己的订单号。

查询订单

GET/api/vibe/orders/:out_trade_no

查一笔订单的最新状态。还没付款的订单,BBpay 会实时向支付宝补查一次,所以即使 Webhook 还没到,也能马上知道买家是否付了款。 适合在买家跳回 return_url 时调用。

curl https://bbpay.uk/api/vibe/orders/ORDER-1001 \
  -H "Authorization: Bearer $BBPAY_KEY"

trade_status

值含义
WAIT_BUYER_PAY等待付款
TRADE_SUCCESS付款成功,可以发货
TRADE_FINISHED交易完结(付款成功且不可退款),同样可以发货
TRADE_CLOSED超时未付或已关闭

只能查到用自己 Key 创建的订单,别人的订单一律返回 404。返回里的 refunded_amount 是已退成功的总额,refunds 是这笔订单的退款记录。

退款

POST/api/vibe/refunds

把一笔已付款订单的钱原路退给买家。钱从你自己的支付宝账户退出,BBpay 不经手资金;用 bbp_test_ Key 下的单在沙箱里退。

参数必填说明
out_trade_no是要退款的订单号(下单时你传的那个)
out_refund_no是你的退款单号,同一笔订单里唯一。字母、数字、_、-,最长 64 位。
refund_amount是这次退多少,单位元,最多两位小数
reason否退款原因,买家在支付宝账单里能看到
curl -X POST https://bbpay.uk/api/vibe/refunds \
  -H "Authorization: Bearer $BBPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"out_trade_no":"ORDER-1001","out_refund_no":"ORDER-1001-R1","refund_amount":"9.90","reason":"买家申请退款"}'
  • status:succeeded 退款成功;pending 支付宝还在处理或结果未知;failed 没退成(HTTP 422,原因在 error,例如支付宝账户余额不足)。
  • 遇到 pending、超时或网络错误,用同一个 out_refund_no 和金额再调一次即可,不会重复退款; 也可以过十几秒调查询订单接口,看 refunds 里的状态。
  • 可以分多次部分退款,累计不能超过订单金额。全额退完后支付宝会关闭这笔交易,你还会收到 order.closed。
  • 退款成功会推 refund.succeeded 事件(下单带了 notify_url 的推表单)。
  • 交易完结(TRADE_FINISHED,超过退款期限)后支付宝不允许再退。沙箱里只能全额退一次。

Webhook 通知

在后台「Webhook」页填一个 https 地址,订单状态变化时 BBpay 会向它发 POST。页面上还能发测试事件、看每次推送的结果、手动重发。

事件什么时候
payment.succeeded买家付款成功
order.closed订单超时未付或被关闭(全额退款后也会关闭)
refund.succeeded一笔退款成功。data.refund 是这次的退款,data.refunded_amount 是累计已退
webhook.test你在后台点「发送测试事件」

请求内容

POST https://your.site/api/bbpay-webhook
BBpay-Signature: t=1793608212,v1=5f3c…e81a
BBpay-Event: payment.succeeded
BBpay-Delivery: evt_9Qm3xK2pLw8r

{
  "id": "evt_9Qm3xK2pLw8r",
  "type": "payment.succeeded",
  "created": "2026-11-02T08:30:12.000Z",
  "livemode": false,
  "data": {
    "out_trade_no": "ORDER-1001",
    "trade_no": "2026110222001498290509998118",
    "trade_status": "TRADE_SUCCESS",
    "amount": "9.90",
    "subject": "AI 简历润色 · 单次",
    "channel": "page",
    "buyer_logon_id": "kve***@sandbox.com",
    "paid_at": "2026-11-02T08:30:10.000Z",
    "created_at": "2026-11-02T08:29:40.000Z"
  }
}

验证签名

每个请求都带 BBpay-Signature: t=时间戳,v1=签名。签名是用你的 Webhook 密钥(whsec_ 开头,在后台查看) 对 ${t}.${原始请求体} 做 HMAC-SHA256 得到的十六进制串。请务必:

  • 用原始请求体验签,不要先解析成 JSON 再序列化;
  • 拒绝 t 与当前时间相差超过 5 分钟的请求,防止重放;
  • 按 data.out_trade_no 去重,同一事件可能送达多次。
// Next.js:app/api/bbpay-webhook/route.ts
import crypto from "node:crypto";

export async function POST(req: Request) {
  const body = await req.text();                       // 一定要用原始请求体验签
  const header = req.headers.get("bbpay-signature") ?? "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const expected = crypto.createHmac("sha256", process.env.BBPAY_WEBHOOK_SECRET!)
    .update(`${t}.${body}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;   // 拒绝 5 分钟前的请求,防重放
  const ok = fresh && v1?.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex"));
  if (!ok) return new Response("bad signature", { status: 400 });

  const event = JSON.parse(body);
  if (event.type === "payment.succeeded") {
    // 按 out_trade_no 去重:同一事件可能送达多次
    await markOrderPaid(event.data.out_trade_no, event.data.amount);
  }
  return new Response("ok");                           // 8 秒内返回 2xx,否则会重试
}

送达与重试

8 秒内返回 2xx 算送达(不跟随重定向)。失败后按 1 分钟、5 分钟、30 分钟、2 小时、6 小时、12 小时、24 小时重试,共 8 次, 重试时事件 id 不变。测试事件不重试。

按单回调(notify_url)

下单时带 notify_url,这一单付款成功或关闭时,BBpay 向它 POST application/x-www-form-urlencoded 表单, 重试规则同上。要先在后台「Webhook」页保存过一次地址,签名用的是那里的 whsec_ 密钥。

  • 字段:event、out_trade_no、trade_no、trade_status、amount、livemode(true / false)、paid_at、notify_id、timestamp(秒)、sign。 退款成功(event=refund.succeeded)时另有 refund_no、refund_amount、refunded_amount。
  • 验签:去掉 sign 和值为空的字段,按字段名升序拼成 k=v&k=v(不做 URL 编码), 用 whsec_ 密钥做 HMAC-SHA256,得到的小写十六进制和 sign 比对;并拒绝 timestamp 超过 5 分钟的请求。
  • 返回任意 2xx(例如 success)算送达。按 out_trade_no 去重,核对 amount 后再发货。

错误码

出错时返回对应的 HTTP 状态码,响应体是 { "error": "说明" },支付宝报错时还会带 sub_code。

状态码原因怎么办
400参数缺失或格式不对按 error 的说明修正参数
401API Key 无效或已作废检查请求头,或在后台新建 Key
402套餐已到期且过了 3 天宽限期在后台「账号与续费」续费
403用正式 Key 下单,但还没绑定支付宝收款账户在后台「收款账户」扫码绑定
404订单不存在确认订单号,以及是不是用同一个账号的 Key 创建的
409订单号已存在(金额不同或已付款 / 已关闭)换一个新的订单号
409退款:订单未付款 / 已关闭 / 已完结,或累计退款超过订单金额查一下订单状态和已退金额
422退款没退成(原因在 error,例如支付宝账户余额不足)处理后用同一个退款单号重试
502支付宝返回错误看 sub_code;稍后重试
503服务暂时不可用稍后重试

打开 pay_url 后,支付宝收银台如果显示 insufficient-user-permissions(用户权限不足), 说明你绑定的支付宝还没签约对应的产品:channel=page 要签「电脑网站支付」,channel=wap 要签「手机网站支付」。 在支付宝商家平台 b.alipay.com →「产品中心」签约后,重新下单即可,不用重新绑定。

测试和正式

  • 用 bbp_test_ Key 下的单走支付宝沙箱,pay_url 打开的是沙箱收银台,不会扣真钱,也收不到真钱。
  • 沙箱收银台只能用支付宝沙箱买家账号付款,普通支付宝 App 扫码会提示无效。
  • 用 bbp_live_ Key 下的单是真实收款,钱直接进你绑定的支付宝。没绑定会返回 403,先去后台「收款账户」扫码绑定。
  • 接口地址和参数两种 Key 完全一样。订单和 Webhook 里的 livemode 字段告诉你这笔是正式(true)还是测试(false), 你的网站只在 livemode 为 true 时给买家真实发货。
  • 在后台「API 密钥」页可以分别新建测试 Key 和正式 Key,开发时用测试 Key,上线时换成正式 Key 即可。

开通正式收款

正式收款时,钱直接从买家进到你自己的支付宝,BBpay 只代你调用支付宝接口,不经手资金。 所以你的支付宝需要自己具备收款产品。按你的情况选一条路:

企业

准备企业支付宝账号。在支付宝商家平台 b.alipay.com →「产品中心」签约「手机网站支付」和「电脑网站支付」, 然后在后台「收款账户」扫码绑定,新建正式 Key。

个体户

准备营业执照,支付宝账号名称要和执照一致。之后同企业:先签约手机网站支付 / 电脑网站支付,再扫码绑定。

个人(没有营业执照)

支付宝目前一般不给个人开通网站支付。我们正在接入支付宝「即刻收」:开通后,个人用实名认证的支付宝、设置好价格就能收款, 钱同样直接进你的支付宝。在此之前,可以先用测试 Key 把接入做完,开通后换成正式 Key 即可。

  • 手机上付款用 channel=wap(需要「手机网站支付」),电脑上用 channel=page(需要「电脑网站支付」)。只签了其中一个,就只用对应的 channel。
  • 电脑网站支付通常要求网站能正常访问,并有和支付宝账号主体一致的 ICP 备案;没有备案时,支付宝可能只给试用期或单笔、单日限额,以签约页面为准。
  • 没签约也能绑定,但买家打开收银台会看到「用户权限不足」(insufficient-user-permissions)。签约后重新下单即可,不用重新绑定。
  • 小程序里的 JSAPI 支付只能在支付宝小程序内拉起,网站和手机网页用不了,BBpay 暂不支持。
  • BBpay 不会用自己的账户替你代收再转账给你。资金始终由支付宝直接结算到你的账户。