查单与退款

查询订单

Node.js
Python
Go
Java
const order = await pay.query_order({ out_trade_no: 'R202608180001' })
// 也可以用顽兔单号:pay.query_order({ order_id: 'po_xxx' })

参数:

参数必填说明
out_trade_no二选一商户订单号
order_id二选一顽兔支付单号。与 out_trade_no 传一个即可,两个都传以 out_trade_no 为准

返回:

字段说明
order_id顽兔支付单号
out_trade_no原样返回商户订单号
status订单状态,取值见下表
amount订单金额,单位元
paid_at支付时间,北京时间 yyyy-MM-dd HH:mm:ss,只有支付后才有
channel下单时选择的渠道:alipay 支付宝 / chinaums 银联商务聚合码
channel_trade_no渠道侧流水号(支付宝交易号 / 银商交易参考号),只有支付后才有,仅对账展示用

status 取值:

status含义
created已创建,用户未操作
paying用户操作中
paid已支付。paid_at、channel_trade_no 有值
partial_refunded已部分退款
refunded已全额退款
closed已关闭(超时未支付,或商户主动关闭),终态

查单是回调之外的兜底手段。建议对支付中的订单每 30 秒到 2 分钟查询一次,直到终态。 超时未支付的订单由网关自动关闭,不另行推送。

关闭订单

用户取消订单、改金额或换支付方式要重新下单时,先把旧单关掉,免得用户拿旧二维码、旧链接又付一笔:

Node.js
Python
Go
Java
await pay.close_order({ out_trade_no: 'R202608180001' })
// 也可以用顽兔单号:pay.close_order({ order_id: 'po_xxx' })

参数:

参数必填说明
out_trade_no二选一商户订单号
order_id二选一顽兔支付单号。与 out_trade_no 传一个即可,两个都传以 out_trade_no 为准

返回与查询订单同形(字段见上表),status 恒为 closed。

几条规则:

  • 只有未支付的单能关。已关闭的单再关幂等返回,网络超时后重试是安全的
  • 关单前网关会先到渠道确认一次。用户恰好这时付完了,订单转 paid、order.paid 照常推送,关单接口报 40902;已支付的单同样报 40902。收到 40902 就按已支付处理,要退钱走退款接口
  • 渠道暂时不可用报 50002,订单没有关掉,退避后重试
  • 银商聚合码关码后旧码立即失效。支付宝和银商公众号支付在用户打开收银台或扫码之前,渠道侧还没有交易,只能先关本地。好在下单时的过期时间已经同步到渠道,过了 expire_at 旧链接、旧码在渠道也付不了,所以关单后还能收到 order.paid 的,只有用户恰好在关单到 expire_at 之间打开旧链接或扫旧码这一种。收到就按已支付处理,入账或退款都行,别丢弃
  • 关单不推送回调,接口应答就是结果

退款

Node.js
Python
Go
Java
const refund = await pay.create_refund({
  out_trade_no: 'R202608180001',
  out_refund_no: 'RF202608180001_1',   // 商户退款单号,同一笔订单内唯一
  amount: '100.00',
  reason: '用户申请退款',
})

参数:

参数必填说明
out_trade_no是要退款的商户订单号
out_refund_no是商户退款单号,1-64 位字母、数字、下划线,同一笔订单内唯一
amount是本笔退款金额,单位元,字符串,最多两位小数。可小于订单金额(部分退款)
reason否退款原因,最长 256 字符,透传给支付渠道

返回与查询退款同形,字段见下面的「查询退款」。

几条规则:

  • out_refund_no 是幂等键。网络超时后用同一个单号重试,不会重复退款
  • 支持多次部分退款,累计金额不超过订单金额,超出返回 42201
  • 退款窗口一般为支付后 12 个月(以支付渠道规则为准),资金原路退回
  • 退款是异步流程:申请受理后先进入贵司审批——运营/财务在顽兔提供的商户后台 批准后才向支付渠道发起(超过 14 天未审批会自动拒绝),所以受理应答的状态 一律是 processing,不要把受理应答当退款结果
  • 终态以 refund.succeeded / refund.failed 回调为准,也可以用下面的退款查询接口确认

退款 status 取值:

status含义
processing处理中:等待商户后台审批,或渠道退款在途
succeeded退款到账,refunded_at 有值
failed终态失败,资金未动。按失败处理:提示用户,如需再退换新退款单号重新申请;fail_reason 说明原因(见下)

只有拿到渠道确认的结果,顽兔才会给出终态:渠道明确拒绝了才是 failed。向渠道发起退款时遇到超时、网络异常这类 结果说不准的情况,不会判失败——那时渠道可能已经把钱退了。这笔退款会保持 processing,顽兔在后台用同一个退款单号 向渠道查询,必要时原单号重发(渠道按单号去重,不会多退),拿到结果再推送 refund.succeeded 或 refund.failed。 所以 processing 偶尔会持续几十分钟甚至更久,按处理中展示、等回调即可,别自己换单号重退。

failed 时应答和 refund.failed 回调都带 fail_reason,非 failed 不返回:

fail_reason含义商户侧建议
rejected贵司运营/财务在商户后台拒绝了这笔退款按内部流程处理,拒绝备注请在商户后台查看
expired超过 14 天未审批,系统自动拒绝如仍需退款,换新退款单号重新申请并及时审批
channel支付渠道明确失败(如退款权限、头寸等渠道侧原因)先联系顽兔确认渠道原因,再换新单号重试

业务提醒:先定好退款规则再接接口。比如积分已消耗的订单是否允许退、 是否只支持全额退,这些由商户业务决定,资金操作永远放在最后一步。

查询退款

Node.js
Python
Go
Java
await pay.query_refund({ out_trade_no: 'R202608180001', out_refund_no: 'RF202608180001_1' })

参数:

参数必填说明
out_trade_no是商户订单号
out_refund_no是申请退款时用的商户退款单号

返回与申请退款的应答同形:

字段说明
refund_id顽兔退款单号
out_trade_no原样返回商户订单号
out_refund_no原样返回商户退款单号
amount本笔退款金额,单位元
statusprocessing / succeeded / failed,含义见上面的状态表
refunded_at退款到账时间,北京时间 yyyy-MM-dd HH:mm:ss,只有 succeeded 才有
fail_reason只有 failed 才有,取值见上面的 fail_reason 表

什么时候查:

  • refund.succeeded / refund.failed 回调迟迟没到,用它兜底确认终态,拿到结果后的处理和收到回调一样写
  • 想主动给用户展示退款进度。processing 表示还在等贵司后台审批或渠道在途,审批可能要几个小时甚至几天,别按秒轮询,几分钟到一小时查一次足够,到终态就停。渠道在途的退款顽兔会在后台自己跟进到终态,不靠商户查询推动
  • 申请退款时网络超时、不确定有没有受理成功,不必先查,直接用同一个 out_refund_no 重试就行,不会重复退款

返回 40401 是退款单不存在,多半是 out_refund_no 记错了,申请退款时传的哪个就查哪个;同一笔订单退过多次的,每次退款单号都不一样,别拿第一次的单号查第二次。