导读:本期聚焦于公主创作的《微信公众号支付退款结果通知验签失败?很可能是用错了API密钥》,敬请观看详情。你正在接入微信公众号支付退款结果通知,支付和退款都成功,后台却一直收到签名验证失败,排序和拼接逻辑检查了很多遍也没发现问题。其实这种场景下最常见的根因不是算法错误,而是验签时选错了API密钥。微信公众号支付体系里同时存在AppSecret、APIv2密钥、APIv3密钥、商户证书私钥等多种凭据,退款结果通知属于微信支付v2报文,只能使用商户平台里配置的32位API密钥进行MD5或HMAC-SHA256验签,同时该密钥还用于req_info的AES-256-ECB解密。本文会拆解退款通知的报文结构、签名字符串生成规则、易混淆的凭据对照,并给出可运行的验签与解密代码,帮你快速定位退款回调验签失败的问题。

微信支付退款结果通知的验签逻辑本身并不复杂,真正容易出错的是密钥来源。你拿着计算出来的MD5去和微信下发的sign对比,只要比对不上,系统就会判定签名验证失败,但这不一定代表排序、拼接或哈希函数有误。退款通知属于微信支付v2接口的异步回调,外层验签和内层req_info解密都依赖同一个32位API密钥。如果这个密钥被误填成AppSecret、APIv3密钥或其他凭据,即使代码和微信官方示例完全一致,也会稳定验签失败。

微信公众号支付退款结果通知验签失败?很可能是用错了API密钥

一、退款结果通知里的两层数据

微信支付v2的退款结果通知以XML形式推送到商户服务器,典型字段包括return_code、appid、mch_id、nonce_str、req_info和sign。其中req_info是加密后的退款详情,不能直接读取;sign是微信对整个通知参数做的签名,商户需要先验签确认报文确实来自微信,再解密req_info处理退款状态。

这里要强调一个容易忽视的点:外层签名把req_info作为普通参数参与拼接,并不会先解密再签。验签时任何对req_info的改写、解密、URL解码,都会改变参与签名的原值,最终导致计算出的摘要与微信下发的sign不一致。因此处理回调的第一条原则是,必须基于微信POST过来的原始参数集合验签。

签名字符串的生成规则如下:剔除sign字段,过滤参数值为空的项,按参数名ASCII码从小到大排序,再拼成key=value&key2=value2这种形式,最后追加&key=商户API密钥。如果商户平台配置的是MD5,就对整串做MD5并转大写;如果配置的是HMAC-SHA256,则使用同样拼接后的字符串做HMAC-SHA256并转大写。

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <appid><![CDATA[wx1234567890abcdef]]></appid>
  <mch_id><![CDATA[1900000109]]></mch_id>
  <nonce_str><![CDATA[5K8264ILTKCH16CQ2502SI8ZNMTM67VS]]></nonce_str>
  <req_info><![CDATA[此处是一长串加密后的Base64文本]]></req_info>
  <sign><![CDATA[9F4E...]]></sign>
</xml>

这段XML只是示意结构,重点在于sign是外层字段,req_info是密文。验签通过之前,不要把req_info拿去解密,否则会浪费排查时间。

二、用错API密钥是最典型的失败原因

微信公众号支付体系内的密钥不止一套。公众号有AppSecret,微信支付v2有API密钥,微信支付v3有APIv3密钥,商户还有API证书私钥和平台证书公钥。它们长度可能看起来相似,但用途完全不同。退款结果通知属于v2报文,只能使用商户平台里配置的32位API密钥。这个密钥在商户平台的位置通常叫API密钥,而不是APIv3密钥,也不是公众号开发者密码。

凭据名称常见用途是否可用于退款通知验签
API密钥v2支付、退款、回调验签、req_info解密是
APIv3密钥v3回调报文AES-256-GCM解密否
AppSecret公众号网页授权、用户信息否
商户API证书私钥v3请求签名否
微信支付平台证书公钥验证v3应答签名否

如果误把APIv3密钥传入v2验签函数,拼出来的字符串没有任何异常,只是最后MD5结果与微信通知对不上。尤其当APIv3密钥和API密钥都是32位字符串时,肉眼很难第一时间发现问题。另一个常见情况是多商户号并存,支付使用A商户号,退款通知里的mch_id是B商户号,但系统只配置了A商户号的API密钥,也会稳定验签失败。

还有一种混淆发生在退款解密环节。微信支付v2退款通知的req_info使用AES-256-ECB解密,密钥同样是API密钥。如果验签通过了,但解密出来是乱码或openssl_decrypt直接返回false,就要检查是不是把APIv3密钥或AppSecret传进了AES解密函数。验签和解密共用同一个API密钥,这是v2退款通知和v3回调最大的区别之一。

三、PHP验签与解密代码

下面这段PHP代码演示了v2通知的验签过程。它首先取出sign并从参数集合中移除,再对剩余参数排序、过滤空值、拼接字符串,最后计算MD5或HMAC-SHA256。使用时传入的参数必须是微信POST的原始键值对,不要先做XML转对象后再拼字符串,因为有些解析库会改变参数顺序,虽然排序后顺序通常一致,但空值和CDATA处理可能引入差异。

function verifyWxPayNotify(array $data, string $apiKey, string $signType = 'MD5'): bool
{
    $receivedSign = $data['sign'] ?? '';
    unset($data['sign']);

    ksort($data);
    $parts = [];
    foreach ($data as $k => $v) {
        if ($v === '' || $v === null) {
            continue;
        }
        $parts[] = $k . '=' . $v;
    }

    $str = implode('&', $parts);
    $str .= '&key=' . $apiKey;

    if (strtoupper($signType) === 'HMAC-SHA256') {
        $expect = strtoupper(hash_hmac('sha256', $str, $apiKey));
    } else {
        $expect = strtoupper(md5($str));
    }

    return hash_equals(strtoupper($receivedSign), $expect);
}

验签通过后,可以继续解密req_info。解密算法是AES-256-ECB,密钥为同一个API密钥,密文先做Base64解码,再交给openssl_decrypt处理。注意第四个参数必须传OPENSSL_RAW_DATA,否则PHP会让输出继续按Base64处理,解析出来的内容会不对。

function decryptWxRefundInfo(string $reqInfo, string $apiKey): ?array
{
    $encrypted = base64_decode($reqInfo, true);
    if ($encrypted === false) {
        return null;
    }

    $xml = openssl_decrypt(
        $encrypted,
        'AES-256-ECB',
        $apiKey,
        OPENSSL_RAW_DATA
    );

    if ($xml === false) {
        return null;
    }

    libxml_use_internal_errors(true);
    $data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
    if ($data === false) {
        return null;
    }

    return json_decode(json_encode($data), true);
}

解密成功后得到的数组里会有refund_id、out_refund_no、refund_status等字段。线上代码建议把这两个函数分开,因为验签是安全边界,解密是业务解析。如果验签失败,应直接记录日志并终止处理,不要继续解密,更不能把业务状态改为已退款。

四、快速定位并修复验签失败

当退款通知持续签名失败时,可以按以下顺序排查。第一步,打印微信POST过来的原始XML或表单参数,确认mch_id、sign、sign_type是否存在。第二步,根据mch_id找到对应的商户号配置,确认系统里加载的API密钥和该商户号在商户平台配置的API密钥完全一致。第三步,检查代码是否在验签前对参数做了多余处理,例如过滤了req_info、转义了XML、或把sign也拼进了签名串。

  • 确认回调中的商户号与API密钥是否匹配,不要使用默认商户号的密钥处理所有回调。
  • 在商户平台重新设置一个包含明确前缀的API密钥,避免与AppSecret、APIv3密钥混淆。
  • 如果通知中带有sign_type,按对应算法验签;如果不带,默认使用MD5。
  • 写一个临时脚本,用候选密钥分别计算签名,找到哪一个能匹配微信下发的sign。
  • 验签通过后返回微信要求的成功XML,例如<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>,否则微信会重复通知。

这里需要特别注意,返回给微信的成功XML不需要再使用API密钥签名,直接用文本输出即可。很多开发者因为担心不签名会被攻击,反而在响应里多加了签名字段,结果微信没有解析到正确状态导致重复回调。退款通知的验签只针对微信发给商户的请求,商户响应不参与签名。

最终如果确认所有逻辑都没问题,仍然验签失败,再登录商户平台查看API密钥是否被修改过。API密钥一旦重置,历史回调里的旧签名都无法用新密钥验证,但这通常只影响很短时间。线上系统修改密钥后,应同步更新配置并重启回调服务,避免不同实例读到的密钥不一致。

微信支付退款通知API密钥签名验证修改时间:2026-09-20 07:31:46

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0920/59557.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。