代码 支付宝退款返回 10000 就标「已退款」:fund_change、退款查询与 out_request_no 的判定链

2026-11-07 09:01:23

支付宝退款返回 10000 就标「已退款」:fund_change、退款查询与 out_request_no 的判定链

现象:接口返回成功,钱没退,对账不平

两种表现最常见:

  1. 退款接口 alipay.trade.refund 返回 code=10000,本地直接把订单标成「已退款」,但买家账户没收到钱,或者只收到一部分。
  2. 调退款查询接口 alipay.trade.fastpay.refund.query 返回 code=10000 Success,响应里却没有任何退款明细,代码把「没有明细」当成了「退款成功」。

这两种情况根源相同:把「请求被受理」当成了「退款已完成」。code=10000 只代表本次退款请求成功,不代表退款成功。code=10000 不能写成本地已退款。

判定链:code → fund_change → 退款查询

退款结果按下面顺序判定,不能跳步:

  • code != 10000:请求失败,走错误码分诊,不要改状态为成功。
  • code = 10000 且 fund_change = Y:退款成功。
  • code = 10000 且 fund_change = N 或无该字段:必须调 alipay.trade.fastpay.refund.query 进一步确认,查询结果里有退款明细才算成功。

退款接口返回 code=10000 时,如果响应带 refund_fee / send_back_fee 字段,只能作参考,不能替代 fund_change 和退款查询。

一个容易忽略的细节:trade_no 与 out_trade_no 同时存在时,接口优先取 trade_no。本地记录要两个都存,查询和重试时保持同一套单号。

退款查询的静默空结果

退款查询接口的 out_request_no 是必传参数,值必须是退款时传的那个 out_request_no;如果退款时没传,则用创建交易时的外部交易号(out_trade_no)。

问题出在这里:如果退款查询传了一个退款时没用过的、不同的 out_request_no,接口返回 code=10000 Success,但响应里没有任何退款明细字段。它不报错,也不提示参数不对应,就是空结果。

正常带明细的响应示意(字段按接口返回,数值仅作示例):

{
  "alipay_trade_fastpay_refund_query_response": {
    "code": "10000",
    "msg": "Success",
    "trade_no": "2024010122001xxxx",
    "out_trade_no": "T20240101001",
    "out_request_no": "R20240101001-01",
    "total_amount": "100.00",
    "refund_amount": "60.00"
  }
}

out_request_no 对不上时的静默空结果:

{
  "alipay_trade_fastpay_refund_query_response": {
    "code": "10000",
    "msg": "Success"
  }
}

另外,out_request_no 前后带空格也会报 ACQ.INVALID_PARAMETER(参数无效:退款请求号不能为空)。请求号入库前统一 trim,不要带空白字符。

out_request_no 的规则

  • 标识一次退款请求,同一笔交易多次退款必须唯一,64 字符内。
  • 全额退款选填;部分退款(退款金额小于付款金额)必传。
  • 重试时必须保证请求号不变,支付宝保证同样请求号多次请求只退一次。
  • out_request_no 传值后,若该请求已部分退款成功,不修改参数再次调用不报错,但返回 fund_change=N(资金未变动)。这正是「重试拿到 10000 但钱没动」的来源。
  • 部分退款不传 out_request_no 且退款金额≠付款金额:报 ACQ.REFUND_AMT_NOT_EQUAL_TOTAL(撤销或退款金额与订单金额不一致)。
  • 传了 out_request_no 但退款金额大于实际付款金额:报 ACQ.REASON_TRADE_REFUND_FEE_ERR(退款金额无效)。
  • 同一请求号但本次退款金额与上次不一致:报 ACQ.DISCORDANT_REPEAT_REQUEST(请求信息不一致)。

refund_amount 单位元,两位小数,不能大于订单金额。含营销时退款金额包含营销金额,默认优先退买家自有资金。示例:总额 100 = 80 自有 + 20 营销券,首次退 60 全从商家资金扣;再退 40 则扣 20 现金 + 退 20 券。

错误码分诊表

错误码含义处理
ACQ.TRADE_STATUS_ERROR交易状态非法调 alipay.trade.query 确认 trade_status;TRADE_SUCCESS 才可退;TRADE_CLOSED 已关闭不能退;TRADE_FINISHED 已完结(超过可退款时间)不能退
ACQ.TRADE_HAS_FINISHED交易已完结不能退,人工核对
ACQ.TRADE_HAS_CLOSE交易已关闭不能退
ACQ.TRADE_NOT_EXIST单号错核对 trade_no / out_trade_no
ACQ.NOT_ALLOW_PARTIAL_REFUND用了特定优惠券等不支持部分退款只能全额退或线下处理
ACQ.REASON_TRADE_REFUND_FEE_ERR退款金额无效;累计退款超总额核对本次金额与历史退款合计
ACQ.SELLER_BALANCE_NOT_ENOUGH卖家余额不足充值后重发,请求号不变
ACQ.REFUNDALLOC_UNAUTH_LIMIT分账接收方未开启分账回退授权不能退分账,先让接收方在商家平台开启授权;接收方为个人的分账单不支持
ACQ.REFUND_AMT_NOT_EQUAL_TOTAL撤销或退款金额与订单金额不一致部分退款补传 out_request_no
ACQ.DISCORDANT_REPEAT_REQUEST请求信息不一致同一请求号的金额不能变,换号前先查
ACQ.INVALID_PARAMETER参数无效:退款请求号不能为空检查 out_request_no 是否为空或带空格
ACQ.SYSTEM_ERROR请求可能成功也可能失败同参数重试,请求号与金额不变;前一次已成功会幂等返回成功;或间隔大于 5 秒调退款查询

幂等与重试规则

  • 重试时 out_request_no 和退款金额都不变,支付宝按请求号幂等。
  • 同一笔交易发起退款至少间隔 3 秒;退款查询与退款请求间隔需 5 秒以上,否则查询可能拿不到结果。
  • ACQ.SYSTEM_ERROR 不要换请求号重试。换号等于发起一笔新退款,已有成功记录时会导致重复退款。
  • 该退款接口不可与其他退款产品混用,混用可能重复退款。
  • 使用撤销接口 alipay.trade.cancel 在交易已成功时会触发退款,容易被误认为「无故退款」,日志里要把两条链路分开记。

本地状态机建议

建议把退款状态和交易状态分开存,退款表以 (out_trade_no, out_request_no) 为唯一键:

  • INIT:请求已生成,未发出。
  • REQUESTED:退款接口返回 code=10000,但未确认。不要写成已退款。
  • CONFIRMED:fund_change=Y,或退款查询返回了对应 out_request_no 的明细。
  • FAILED:明确的失败错误码。
  • UNKNOWN:ACQ.SYSTEM_ERROR、查询为空、网络超时等,留在待查队列。

状态推进只由 fund_change=Y 或退款查询明细驱动;异步通知只作补充,不单独作为成功依据。并发部分退款时,本地扣减「已退 + 在途」的额度要用条件更新,避免超退(可参考站内 #8203 的并发退款处理)。

判定片段(未实测,仅示意顺序):

// 未实测:退款结果判定链
func judgeRefund(r RefundResult, tradeNo, outRequestNo string) string {
    if r.Code != "10000" {
        return "FAILED_OR_RETRY" // 交给错误码分诊
    }
    if r.FundChange == "Y" {
        return "CONFIRMED"
    }
    // fund_change=N 或缺字段:不能标成功
    time.Sleep(5 * time.Second) // 查询需与退款请求间隔 5 秒以上
    q := callRefundQuery(tradeNo, outRequestNo)
    if q.Code != "10000" {
        return "UNKNOWN"
    }
    if q.RefundAmount == "" {
        return "UNKNOWN" // 静默空结果,多半是 out_request_no 对不上
    }
    return "CONFIRMED"
}

退款通知怎么对应到具体一笔退款

  • 部分退款必有异步通知,全额退款视产品触发。
  • 通知里 refund_fee 是总退款金额,gmt_refund 用于区分退款通知与支付通知。
  • out_biz_no = 退款时的 out_request_no;退款时未传则为 out_trade_no。
  • 全额退款通知含 gmt_close。

所以部分退款必须传 out_request_no,否则通知里 out_biz_no 退化成 out_trade_no,多笔退款无法区分,只能靠金额和时间猜。

两个状态细节:全额退款成功后 trade_status 变为 TRADE_CLOSED;部分退款后仍为 TRADE_SUCCESS。银行卡原路退款时资金先冻结在支付宝账户,待银行结算才到卡,通常约 2 小时,这段时间「接口成功但卡里没钱」是正常现象。

边界说明:以上判定只覆盖支付宝退款接口与退款查询的返回语义,具体到账时间以银行和支付宝结算为准,不以接口返回时间为准。

信源

复制全文 生成海报 支付宝 退款 支付接口 幂等 异步通知

推荐文章

程序员茄子在线接单