没有不值得去解决的问题,也没有不值得去学习的技术!

Alipay Refund Error With riverslei/payment: Invalid Arguments and Signature Verification Failed

Alipay Refund Error With riverslei/payment: Invalid Arguments and Signature Verification Failed

作者:

在

1. When implementing an Alipay refund based on riverslei/payment, an error occurred: request get failed, msg[Invalid Arguments], sub_msg[Signature verification failed. It is recommended to check whether the signature string or the signature private key matches the application public key]. As shown in Figure 1

When implementing an Alipay refund based on riverslei/payment, an error occurred: request get failed, msg[Invalid Arguments], sub_msg[Signature verification failed. It is recommended to check whether the signature string or the signature private key matches the application public key].
Figure 1

2. The RSA private key you provided is in PKCS#1 format (starting with MIIEvQ…, without header and footer markers). You need to convert it to a PKCS#8 format RSA private key using an OpenSSL command for Alipay to recognize it. Copy the private key content to the file pkcs1.pem, then execute the following command. As shown in Figure 2

The RSA private key you provided is in PKCS#1 format (starting with MIIEvQ..., without header and footer markers). You need to convert it to a PKCS#8 format RSA private key using an OpenSSL command for Alipay to recognize it. Copy the private key content to the file pkcs1.pem, then execute the following command.
Figure 2
Plaintext

$ openssl pkcs8 -topk8 -inform PEM -in pkcs1.pem -out pkcs8.pem -outform PEM -nocrypt

wangqiang@DESKTOP-F6AO3M2 MINGW64 /C/wwwroot/apply-server/src (feature/alipay)


3. Finally, save the content from pkcs8.pem to ali_rsa_private_key. As shown in Figure 3

Finally, save the content from pkcs8.pem to ali_rsa_private_key
Figure 3

4. The prompt changed to: sign error, sign type is [RSA2]. msg: [The private key format you used is incorrect, please check the RSA private key configuration]. As shown in Figure 4

The prompt changed to: sign error, sign type is [RSA2]. msg: [The private key format you used is incorrect, please check the RSA private key configuration]
Figure 4

5. Edit the file vendor/riverslei/payment/src/Helpers/Rsa2Encrypt.php,

PHP

    /**
     * RSA2签名, 此处秘钥是私有秘钥
     * @param string $data 签名的数组
     * @throws \Exception
     * @return string
     * @author Leo
     */
    public function encrypt($data)
    {
        if ($this->key === false) {
            return '';
        }

        \Yii::info(
            [
                'data' => $data,
                'key' => $this->key
            ]
        );
        $res = openssl_get_privatekey($this->key);
        if (empty($res)) {
            throw new \Exception('您使用的私钥格式错误,请检查RSA私钥配置1');
        }

        openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
        openssl_free_key($res);

        //base64编码
        $sign = base64_encode($sign);
        return $sign;
    }

6. Inspecting $this->key, there are unexpectedly 2 instances of —–BEGIN RSA PRIVATE KEY—– and —–BEGIN PRIVATE KEY—–. As shown in Figure 5

Inspecting $this->key, there are unexpectedly 2 instances of -----BEGIN RSA PRIVATE KEY----- and -----BEGIN PRIVATE KEY-----
Figure 5

7. When saving the content from pkcs8.pem to ali_rsa_private_key, first remove —–BEGIN RSA PRIVATE KEY—–. As shown in Figure 6

When saving the content from pkcs8.pem to ali_rsa_private_key, first remove -----BEGIN RSA PRIVATE KEY-----
Figure 6
PHP

                $aliConfig = [
                    'use_sandbox' => false, // 是否使用沙盒模式
                    'app_id' => $payee->ali_app_id,
                    'sign_type' => $payee->ali_sign_type, // RSA  RSA2
                    // 支付宝公钥字符串
                    'ali_public_key' => $payee->ali_public_key,
                    // 自己生成的密钥字符串
                    'rsa_private_key' => $payee->ali_rsa_private_key,
                    'fee_type' => 'CNY', // 货币类型  当前仅支持该字段
                ];

8. The error appeared again: request get failed, msg[Invalid Arguments], sub_msg[Signature verification failed. It is recommended to check whether the signature string or the signature private key matches the application public key. The signature verification string generated by the gateway is

9. Inspecting the file vendor/riverslei/payment/src/Helpers/StrUtil.php, I found that it automatically converts a PKCS#1 format RSA private key (starting with MIIEvQ…, without header and footer markers) into a legacy PKCS#1 format RSA private key. Ultimately, the error persisted: sub_msg[Signature verification failed. It is recommended to check whether the signature string or the signature private key matches the application public key. The signature verification string generated by the gateway is

PHP

    /**
     * 获取rsa密钥内容
     * @param string $key 传入的密钥信息, 可能是文件或者字符串
     * @param string $type
     *
     * @return string
     */
    public static function getRsaKeyValue($key, $type = 'private')
    {
        if (is_file($key)) {// 是文件
            $keyStr = @file_get_contents($key);
        } else {
            $keyStr = $key;
        }
        if (empty($keyStr)) {
            return null;
        }

        $keyStr = str_replace(PHP_EOL, '', $keyStr);
        // 为了解决用户传入的密钥格式,这里进行统一处理
        if ($type === 'private') {
            $beginStr = '-----BEGIN RSA PRIVATE KEY-----' . PHP_EOL;
            $endStr   = PHP_EOL . '-----END RSA PRIVATE KEY-----';
        } else {
            $beginStr = '-----BEGIN PUBLIC KEY-----' . PHP_EOL;
            $endStr   = PHP_EOL . '-----END PUBLIC KEY-----';
        }

        $rsaKey = $beginStr . wordwrap($keyStr, 64, "\n", true) . $endStr;

        return $rsaKey;
    }

10. Edit vendor/riverslei/payment/src/Supports/HttpRequest.php, and signature verification succeeds

PHP

    /**
     * @param string $method
     * @param string $url
     * @param array $options
     * @return array|ResponseInterface|string|mixed
     */
    private function sendRequest(string $method, string $url, array $options = [])
    {
        \Yii::info(
            [
                '$method' => $method,
                '$url' => $url,
                '$options' => $options
            ]
        );
        // return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($url, $options));
        return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($url, [
            'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'],
            'body' => http_build_query($options['query'], '', '&', PHP_QUERY_RFC3986),
        ]));
    }

11. However, because there were garbled characters in the response, it caused a json_decode error. As shown in Figure 7

However, because there were garbled characters in the response, it caused a json_decode error
Figure 7
PHP

            $ret    = $this->get($this->gatewayUrl, $params);

            if (!mb_check_encoding($ret, 'UTF-8')) {
                $ret = mb_convert_encoding($ret, 'UTF-8', 'GBK');
            }

12. Error: Signature verification failed. Please ensure the charset parameter is placed in the URL query string and that all parameter values are encoded using the character set indicated by the charset parameter. As shown in Figure 8

Error: Signature verification failed. Please ensure the charset parameter is placed in the URL query string and that all parameter values are encoded using the character set indicated by the charset parameter
Figure 8

13. Carefully comparing the signature strings before and after the request, I noticed that return_url= was missing, so I decided to remove return_url from the configuration. The reason is that

PHP

    /**
     * 获取基础数据
     * @param string $method
     * @param array $bizContent
     * @return array
     */
    private function getBaseData(string $method, array $bizContent)
    {
        $requestData = [
            'app_id'     => self::$config->get('app_id', ''),
            'method'     => $method,
            'format'     => 'JSON',
            'return_url' => self::$config->get('return_url', ''),
            'charset'    => 'utf-8',
            'sign_type'  => self::$config->get('sign_type', ''),
            'timestamp'  => date('Y-m-d H:i:s'),
            'version'    => '1.0',
            'notify_url' => self::$config->get('notify_url', ''),
            // 'app_auth_token' => '', // 暂时不用
            'biz_content' => json_encode($bizContent, JSON_UNESCAPED_UNICODE),
        ];
        return ArrayUtil::arraySort($requestData);
    }

PHP

echo html_entity_decode('app_id=2021005146654430&biz_content={"out_trade_no":"1831248803313247","refund_amount":"0.01","refund_currency":"CNY","refund_reason":"Refund for Conference Registration Fee (Ticket)","out_request_no":"1831248803313311"}&charset=utf-8&format=JSON&method=alipay.trade.refund&notify_url=https://5347-43-249-50-182.ngrok-free.app/ali-payment/notify-ticket-order/1831248803313247&sign_type=RSA2&timestamp=2025-05-10 16:39:56&version=1.0');

// 请求前的签名字符串
// app_id=2021005146654430&biz_content={"out_trade_no":"1831248803313247","refund_amount":"0.01","refund_currency":"CNY","refund_reason":"Refund for Conference Registration Fee (Ticket)","out_request_no":"1831248803313311"}&charset=utf-8&format=JSON&method=alipay.trade.refund&notify_url=https://5347-43-249-50-182.ngrok-free.app/ali-payment/notify-ticket-order/1831248803313247&return_url=&sign_type=RSA2&timestamp=2025-05-10 16:39:56&version=1.0

// 请求后报错,支付宝使用的签名字符串
// app_id=2021005146654430&biz_content={"out_trade_no":"1831248803313247","refund_amount":"0.01","refund_currency":"CNY","refund_reason":"Refund for Conference Registration Fee (Ticket)","out_request_no":"1831248803313311"}&charset=utf-8&format=JSON&method=alipay.trade.refund&notify_url=https://5347-43-249-50-182.ngrok-free.app/ali-payment/notify-ticket-order/1831248803313247&sign_type=RSA2&timestamp=2025-05-10 16:39:56&version=1.0

14. The signature verification string returned by Alipay indeed does not include empty-value fields, such as the return_url= (empty value) entry, but you included it when participating in the signature, causing an inconsistency in the signature string and thus a signature verification failure. I finally decided to adjust the configuration

PHP

                $aliConfig = [
                    'use_sandbox' => false, // 是否使用沙盒模式
                    'app_id' => $payee->ali_app_id,
                    'sign_type' => $payee->ali_sign_type, // RSA  RSA2
                    // 支付宝公钥字符串
                    'ali_public_key' => $payee->ali_public_key,
                    // 自己生成的密钥字符串
                    'rsa_private_key' => $payee->ali_rsa_private_key,
                    'limit_pay' => [
                        // 'balance', // 余额
                        // 'moneyFund', // 余额宝
                        // 'debitCardExpress', // 借记卡快捷
                        // 'creditCard', // 信用卡
                        // 'creditCardExpress', // 信用卡快捷
                        // 'creditCardCartoon', // 信用卡卡通
                        // 'credit_group', // 信用支付类型(包含信用卡卡通、信用卡快捷、花呗、花呗分期)
                    ], // 用户不可用指定渠道支付当有多个渠道时用“,”分隔
                    // 与业务相关参数
                    'notify_url' => 'notify_url',
                    'return_url' => 'return_url',
                    'fee_type' => 'CNY', // 货币类型  当前仅支持该字段
                ];


15. No more errors. Print the refund response

PHP

$result = $client->refund($refundData);

Yii::info(
	[
		'result' => $result,
		'data' => $refundData,
	],
	'refund ali'
);

	[
    'result' => [
        'code' => '10000',
        'msg' => 'Success',
        'buyer_logon_id' => 'shu***@163.com',
        'fund_change' => 'Y',
        'gmt_refund_pay' => '2025-05-10 17:01:59',
        'out_trade_no' => '1831248803313320',
        'refund_detail_item_list' => [
            [
                'amount' => '0.01',
                'fund_channel' => 'ALIPAYACCOUNT',
            ],
        ],
        'refund_fee' => '0.01',
        'send_back_fee' => '0.01',
        'trade_no' => '2025051022001491671421159148',
        'buyer_open_id' => '067IqA-DLOIYEoiWWh1KNCyoaiY-qUzekwwoGX_3VQKI9s7',
    ],
    'data' => [
        'trade_no' => '1831248803313320',
        'refund_fee' => '0.01',
        'reason' => 'Refund for Conference Registration Fee (Ticket)',
        'refund_no' => '1831248803313321',
    ],
]

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系