yansongda / pay

可能是我用过的最优雅的 Alipay/WeChat/Douyin/Unipay/江苏银行 的支付 SDK 扩展包了
http://pay.yansongda.cn
MIT License
5.04k stars 1.03k forks source link

微信下载账单问题 #882

Closed yqsphp closed 10 months ago

yqsphp commented 10 months ago

包版本号

v3.*

问题描述

通过插件“GetTradeBillPlugin”获取到交易下载地址后,再次使用下载插件“DownloadBillPlugin”下载对账单,对账单已经响应成功,但是微信服务端返回没有“Wechatpay”等响应头,而执行Pay::wechat()->pay($plugin, $params);方法后逻辑会走Functions.php中的verify_wechat_sign函数验证,就会抛异常sign为空异常

你的代码

`

    Pay::config($this->config);
    $plugin = Pay::wechat()->mergeCommonPlugins([GetTradeBillPlugin::class]); //申请交易账单
    $params = [
        'bill_date' => $date,
        'bill_type' => 'ALL',
        'tar_type'  => 'GZIP'
    ];
    $result = Pay::wechat()->pay($plugin, $params); 获取账单
    $plugin = Pay::wechat()->mergeCommonPlugins([DownloadBillPlugin::class]); 下载账单
    $params = [
        'download_url' => $result->download_url,
    ];
    $bill = Pay::wechat()->pay($plugin, $params);  这里$bill返回是文件流,但是就是在这步对响应数据验证,验证响应头

**以下是响应后打印数据**

    object(GuzzleHttp\Psr7\Response)#81 (6) {
        ["reasonPhrase":"GuzzleHttp\Psr7\Response":private] => string(2) "OK"
        ["statusCode":"GuzzleHttp\Psr7\Response":private] => int(200)
        ["headers":"GuzzleHttp\Psr7\Response":private] => array(7) {
            ["Server"] => array(1) {
              [0] => string(5) "nginx"
            }
            ["Date"] => array(1) {
              [0] => string(29) "Wed, 13 Dec 2023 06:56:02 GMT"
            }
            ["Content-Type"] => array(1) {
              [0] => string(18) "application/x-gzip"
            }
            ["Transfer-Encoding"] => array(1) {
              [0] => string(7) "chunked"
            }
            ["Connection"] => array(1) {
              [0] => string(10) "keep-alive"
            }
            ["Keep-Alive"] => array(1) {
              [0] => string(9) "timeout=8"
            }
            ["Request-Id"] => array(1) {
              [0] => string(42) "0882ABE5AB06101418F9DAB74C20F36628DCEE02-0"
            }
        }
        ["headerNames":"GuzzleHttp\Psr7\Response":private] => array(7) {
            ["server"] => string(6) "Server"
            ["date"] => string(4) "Date"
            ["content-type"] => string(12) "Content-Type"
            ["transfer-encoding"] => string(17) "Transfer-Encoding"
            ["connection"] => string(10) "Connection"
            ["keep-alive"] => string(10) "Keep-Alive"
            ["request-id"] => string(10) "Request-Id"
        }
        ["protocol":"GuzzleHttp\Psr7\Response":private] => string(3) "1.1"
        ["stream":"GuzzleHttp\Psr7\Response":private] => object(GuzzleHttp\Psr7\Stream)#68 (7) {
            ["stream":"GuzzleHttp\Psr7\Stream":private] => resource(191) of type (stream)
            ["size":"GuzzleHttp\Psr7\Stream":private] => NULL
            ["seekable":"GuzzleHttp\Psr7\Stream":private] => bool(true)
            ["readable":"GuzzleHttp\Psr7\Stream":private] => bool(true)
            ["writable":"GuzzleHttp\Psr7\Stream":private] => bool(true)
            ["uri":"GuzzleHttp\Psr7\Stream":private] => string(10) "php://temp"
            ["customMetadata":"GuzzleHttp\Psr7\Stream":private] => array(0) {
            }
        }
    }

`

Error details

以下是报错函数部分,(响应头中没有包含Wechatpay等信息)文件所在地址:pay/src/Function.php 函数:verify_wechat_sign `

    $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
    $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
    $random = $message->getHeaderLine('Wechatpay-Nonce');
    $sign = $message->getHeaderLine('Wechatpay-Signature');
    $body = (string) $message->getBody();
    $content = $timestamp." \n".$random." \n".$body." \n";
    $public = get_wechat_config($params)['wechat_public_cert_path'][$wechatSerial] ?? null;

    if (empty($sign)) {
        throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
    }

    $public = get_public_cert(
        empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
    );

    $result = 1 === openssl_verify(
        $content,
        base64_decode($sign),
        $public,
        'sha256WithRSAEncryption'
    );

    if (!$result) {
        throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
    }

`

SDK logs

nginx/apache logs

Please post questions about asynchronous notifications and synchronous notifications

yansongda commented 10 months ago

方便看看怎么改么?然后帮忙提个 PR?

yqsphp commented 10 months ago

我再调用下载插件时多传入了一个参数,然后再verify_wechat_sign函数中验证这个参数是否存在,存在直接跳过 `

    $plugin = Pay::wechat($this->config)->mergeCommonPlugins([DownloadBillPlugin::class]); //下载账单
    $params = [
        'download_url'    => $url,
        // 自定义的参数【下载账单响应取消验证签名】 ,修改函数verdor/yansongda/pay/src/Functions.php  verify_wechat_sign
        'download_verify' => false,
    ];

    $bill = Pay::wechat()->pay($plugin, $params);

在verify_wechat_sign函数中加入判断

    function verify_wechat_sign(MessageInterface $message, array $params): void
    {
    if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
        return;
    }
    //针对下载账单不验证响应
    if(isset($params['download_verify'])){
        return;
    }

    $wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
    $timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
    $random = $message->getHeaderLine('Wechatpay-Nonce');
    $sign = $message->getHeaderLine('Wechatpay-Signature');
    $body = (string) $message->getBody();
    $content = $timestamp."\n".$random."\n".$body."\n";
    $public = get_wechat_config($params)['wechat_public_cert_path'][$wechatSerial] ?? null;

    if (empty($sign)) {
        throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
    }

    $public = get_public_cert(
        empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public
    );

    $result = 1 === openssl_verify(
        $content,
        base64_decode($sign),
        $public,
        'sha256WithRSAEncryption'
    );

    if (!$result) {
        throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
    }
}

`

这只是我个人的,应该可以在初始化配置中加入参数来取消验证。

yansongda commented 10 months ago

别 mergeCommonPlugins ,直接使用所有插件试试:

Pay::wechat()->pay([
PreparePlugin::class,
DownloadBillPlugin::class,
RadarSignPlugin::class,
ParserPlugin::class,
], $params);