本章接口路径前缀 /payment/openapi/alipay;请求头与签名算法(3.2 / 3.3)逐字适用,错误处理总则(3.6)同样适用。
关于寻址:本章三个接口都按你这条 API Key 所属的租户账号寻址,取该租户名下由平台运营报备好的商户配置。你不需要在请求里指定归属。
关于 merchant_code(新增,2026-09-24):平台允许一个租户报备多个商户号(例如你名下有多家门店主体,各自独立结算)。这时「这笔钱收进哪一个商户」必须由你在请求里说清楚,用 merchant_code 指定。
⚠️ 你租户下配了多个商户号、而你又不传
merchant_code时,接口会直接拒绝(code=400,msg为当前租户配置了多个商户编号,请在请求中指定 merchant_code),不会替你挑一个。这是有意的** —— 挑错了钱会进另一个商户的账户,而且全程没有任何报错,要等到对账才发现。只配了一个商户号的调用方不受影响,什么都不用改。怎么知道自己配了几个:登录平台,在「商户配置」页可以看到你名下的全部商户号。三个接口都填同一个
merchant_code即可。
门店与下单的关系:10.1 里的 shop_code / third_shop_code 都是选填。门店不是下单的前置条件 —— 只有需要把交易归属到某个具体门店时才填。
POST /payment/openapi/alipay/order/create
请求
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchant_code |
string | 否 | 你租户下配了多个商户号时必填:这笔钱收进哪一个商户,见本章开头 |
price |
number | 是 | 金额,单位元,必须 > 0,见下 |
title |
string | 是 | 商品名称,≤64 字符 |
out_biz_no |
string | 否 | 你自己的订单号,≤64 字符,原样存下供你查询 |
shop_code |
string | 否 | 平台门店编号 |
third_shop_code |
string | 否 | 门店编码 |
ip |
string | 是 | 顾客用户端ip |
pay_timeout |
int | 否 | 支付过期时间,取值 2–60(单位由上游定义,平台原样透传) |
响应(B 型,data 内)
| 字段 | 说明 |
|---|---|
order_no |
平台订单号,后续查询(10.2)用它 |
out_biz_no |
原样返回 |
upstream_order_no |
上游订单号。下单成功才有值,下单超时时为空 |
platform_trade_no |
平台交易号,支付成功后才有值 |
pay_url |
收款链接,渲染成二维码即可 |
trade_status |
订单状态,见 10.5 |
price |
订单金额(元) |
title |
原样返回 |
shop_code / third_shop_code |
你传的门店编码原样返回 |
trade_time |
支付成功时间 |
rcv_amount |
实收金额(元),支付成功后有值 |
fee_amount / channel_amount |
手续费 / 渠道金额 |
refund_time |
退款时间。退款到账(trade_status 为 "2")后才有值 |
notify_status |
平台向你推送回调的结果,见 10.4 |
created_time / updated_time |
记录时间 |
⚠️ 本接口不幂等 —— 重复调用会产生两笔订单、两次扣款。 每次调用都会生成一个新的
order_no并向上游发起一笔新的交易,平台不做out_biz_no去重(也不做唯一约束)。超时后不要直接重发:先用 10.2 订单查询 按out_biz_no查一次,查到就用那一笔的结果。⚠️
price的单位是「元」,是小数,不是「分」。1.5表示 1 元 5 角;把「元」当「分」写会差 100 倍。⚠️ 金额的写法会直接影响签名。 签名是对你实际发出的那个值做字符串化后算的(3.3),而平台把 JSON 数字读成双精度数:你写
0.010,平台看到的是0.01—— 签名因此对不上,只会返回「签名验证失败」,不会告诉你原因。请写成不带多余零的最简小数(0.01/1.5/100),不要写0.010、1.50。写成字符串("price": "0.01")也可以,字符串原样参与签名,最不容易出错。⚠️ 下单失败时本地记录仍在,不要当作「什么都没发生」。 上游调用失败会以
code=500返回(msg形如下单失败: <原因>),但平台已经落了本地记录并排入了状态轮询 —— 这笔交易仍可能成功。收到失败后请用 10.2 确认,不要凭这一次返回就向用户宣称交易未发生。
POST /payment/openapi/alipay/order/query
请求(order_no 与 out_biz_no 至少提供一个;都提供时以 order_no 为准)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchant_code |
string | 否 | 你租户下配了多个商户号时必填,与其它接口同一个值。传了就只查这个商户的订单 |
order_no |
string | 二选一 | 平台订单号(10.1 响应里的 order_no) |
out_biz_no |
string | 二选一 | 你自己的订单号 |
响应(B 型,data 内)—— 字段与 10.1 的响应一致,另加:
| 字段 | 说明 |
|---|---|
upstream_create_time |
上游建单时间 |
本接口查的是平台已记录的状态,不会当场向上游发起查询。 订单状态由平台的任务自动收敛(下单约 30 秒后开始查,之后按退避节奏重试),所以你刚下单就查、拿到的可能仍是
"0";过一会儿再查即可。
order_no与out_biz_no都没传,返回code=500,msg为order_no 与 out_biz_no 至少填一个。⚠️ 查不到订单时返回的是
code=500,不是404。msg为订单不存在。不属于你的订单也返回同一个msg—— 平台有意不区分「不存在」与「不属于你」,避免被用来探测他人订单是否存在。
对一笔已支付成功的订单发起退款。
POST /payment/openapi/alipay/refund
请求(order_no 与 out_biz_no 至少提供一个,规则同 10.2)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
merchant_code |
string | 否 | 你租户下配了多个商户号时必填,与其它接口同一个值 |
order_no |
string | 二选一 | 平台订单号(10.1 响应里的 order_no) |
out_biz_no |
string | 二选一 | 你自己的订单号 |
reason |
string | 否 | 退款原因。留空时平台补一个默认值(商户申请退款);填了就以你填的为准 |
⚠️ 本接口只有全额退款,请求里没有金额字段。 这条通道的上游不支持部分退款 —— 发起即退整笔。需要退一部分的场景请先联系平台。
ℹ️
reason为什么是选填:通道上游把它列为必填,但没公布长度上限,所以平台不强制你填 —— 留空就替你补一个默认值,不会因此失败。想在自己的账上区分退款来源就填上。
响应(B 型,data 内):结构与 10.2 完全相同(同一份订单)。
⚠️ 返回时
trade_status是"4"(退款中),不是"2"。 同步返回只代表平台已把退款指令发给上游,不代表钱已经退回用户账上。
结果怎么收敛:"4" 由平台的查询任务持续向上游确认,直到拿到终态(上游若推了通知,平台也会转推给你,见 10.4):
最终 trade_status |
含义 |
|---|---|
"2" |
退款成功,钱已退回 |
"1" |
退款没有发生,订单回到已支付 |
⚠️ 不要把
"4"当成已退款,也不要凭它给用户承诺到账。 用户看到的是钱有没有回来,不是接口返回了什么。同 10.4 的提醒:通知可能丢,请对停在"4"的订单做主动查询兜底。⚠️ 只有
trade_status = "1"的订单能退款。 其它状态("0"/"4"/"2"/"8")一律拒绝,code=500,msg形如订单当前状态不可退款: <状态值>。查不到订单、或不是你的订单,仍然是 10.2 那句code=500的订单不存在。
重复调用会怎样 —— 这是最容易误解的一处:
"1",会被上面那句错误挡下。失败分两条路,处理方式相反:
| 返回 | 订单状态 | 你该做什么 |
|---|---|---|
code=500,msg 形如 退款失败: <原因> |
退回 "1" |
上游明确拒绝了,什么都没发生。可用 10.2 确认状态是 "1" 后重新发起 |
code=500,msg 为 退款结果未知(上游未响应),订单已置为退款中,请稍后查询订单状态确认 |
保持 "4" |
上游没应答,这笔退款可能已经发生。不要重发 —— 用 10.2 查到终态为止 |
⚠️ 两条路都是
code=500,只能靠msg区分。 拿不准时一律按「结果未知」处理(不重发、去查询):重发一笔可能已经退掉的款是真实资金损失,多查一次只是麻烦。⚠️ 「退款失败就再退一次」的前提是订单已回到
"1",不是你上次调用的返回。 并发重复发起时只有一次会成功,另一次会被状态挡下 —— 但两次的返回里只有一次是「已受理」,别把两者搞混。
订单状态变化时,平台会向你推送一条回调。
回调地址取自你这把 API Key 上配置的 return_url(未配置时退回该租户的默认回调地址)。没配时平台不会丢下这笔 —— 记一次推送失败并继续重试(见下),你补配地址后未达上限的订单仍会收到;但尽快补配,超过上限就不会再推了,届时只能靠主动查询(10.2)拿结果。
POST <你的 return_url>
Content-Type: application/x-www-form-urlencoded
请求体(表单编码,三个字段):
| 字段 | 说明 |
|---|---|
notify_id |
本次通知的唯一 ID,可用于去重 |
timestamp |
平台发出通知的时间,Unix 毫秒 |
content |
JSON 字符串,业务内容见下(注意:是字符串,需要再解析一次) |
content 示例:
{
"third_order_no": "AP1234567890",
"out_biz_no": "T20260924001",
"order_no": "2026092499001",
"platform_trade_no": "2026092499002",
"merchant_code": "M0001",
"third_shop_code": "S001",
"price": 1.50,
"title": "测试商品",
"status": "1",
"trade_time": "2026-09-24T10:30:00+08:00",
"refund_time": null,
"rcv_amount": 1.50,
"fee_amount": 0.01,
"channel_amount": 1.49
}
金额字段是 JSON 数字(不是字符串);时间字段是 ISO-8601 带时区偏移的字符串。
⚠️ 回调里的
order_no与 10.1 响应里的order_no不是同一个号。 回调里的order_no是上游订单号(等于 10.1 响应里的upstream_order_no); 回调里的third_order_no才是平台订单号(等于 10.1 响应里的order_no)。 名字是反的,但对账必须靠它们,请按这层对应关系取用。 最省事的做法:始终用你自己生成的out_biz_no作主键做幂等,不要依赖这两个order_no。
你方需要做的:
out_biz_no + status 做幂等。notify_status 停在 FAILED。⚠️ 推送的触发不止「上游通知到达」这一种。 平台在本地订单状态收敛后推送:既包括收到上游通知,也包括平台自己的查询任务发现的状态变化(上游通知丢了的那条腿)。但重试有上限(见上)—— 超过上限就不会再推。请仍给长期停在
"0"的订单做主动查询兜底,别把回调当作唯一渠道。⚠️ 退款的最终结果(
"4"→"2"/"1")走的是同一条回调,content.status里就是新状态,refund_time也在里面。所以上面的兜底对退款同样适用,而且更要紧:退款停在"4"时你等的是钱有没有回来,回调不响就只能自己查。给长期停在"4"的订单也做主动查询。⚠️ 回调不带签名。 不能仅凭收到回调就认定收款成功。请以 10.2 订单查询 的结果为准,把回调当作「该查了」的触发信号。
trade_status 是数字字符串(JSON 里带引号,不是数字):
| 值 | 含义 | 是否终态 |
|---|---|---|
"0" |
等待支付 | 否 |
"1" |
支付成功,钱已到账 | 是 |
"2" |
已退款 | 是 |
"4" |
退款中 | 否 |
"8" |
交易关闭 | 是 |
判断「是否收到过钱」用 "1" / "2";判断「是否还要等」用 "0" / "4"。
判断「退款是否已经到账」只看 "2" —— 停在 "4" 表示平台还在向上游确认,不代表已退(见 10.3)。
订单 notify_status(平台向你推送回调的结果):
| 值 | 含义 |
|---|---|
PENDING |
尚未发起过推送(订单刚生成 / 状态刚变化,推送还没回写结果) |
SUCCESS |
你方已成功接收 |
FAILED |
推送失败(你方返回非 2xx、超时异常,或你还没配置回调地址)。平台会自动重试,累计最多 3 次(含首推)后停止;补配地址后未达上限的订单仍会被重推 |
price 用元、写成最简小数(不要 1.50、0.010)pay_url 并能渲染成二维码trade_status 的数字字符串分支处理("1" 才是钱到账)content 再解析一次 JSONout_biz_no 做幂等"0" 的订单有主动查询兜底trade_status = "1" 的订单发起,且发起前用 10.2 确认过没有退过退款结果未知… 时不重发,改用 10.2 查到终态"2"(已到账)判定,不把 "4" 当已退款