签名算法(微信支付同款)

<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

const API_KEY = '192006250b4c09247ec02edce69f6a2d'; // API密钥

/**
 * 过滤空值参数并排序
 * 说明:空值参数不参与签名(调用API时也无需传递),需要将其删除,空值只有空字符串''和null两种情况。
 *
 * @param array $params 签名参数(数组键值对)
 * @return array 过滤空值参数并排序后的数组
 */
function params_filter_and_sort(array $params): array
{
    // 这里不能直接使用array_filter()函数,因为这样会把值为0的元素(不管是int还是string)也删除,而0不是空值。
    $arr = array_filter($params, static function (mixed $value): bool {
        return $value !== '' && $value !== null; // 返回true则会保留该元素,即删除数组中的空字符串''和null
    });

    // 按照参数名ASCII字典序排序(即按照a~z升序)
    ksort($arr);

    return $arr;
}

/**
 * 生成签名
 *
 * @param array $params 签名参数(数组键值对)
 * @param string $type 签名类型,可选值:MD5|HMAC-SHA256
 * @return string 签名字符串
 */
function generate_signature(array $params, string $type): string
{
    $paramsSign = params_filter_and_sort($params);

    // 将数组格式的参数转成签名原串(即“key1=value1&key2=value2&key3=value3”格式的字符串)
    // 重要说明:http_build_query()会自动将中文或某些特殊字符URLencode编码,故需要urldecode()解码。
    $stringA = urldecode(http_build_query($paramsSign));

    // 拼接API密钥
    $stringSignTemp = "$stringA&key=" . API_KEY;

    if (strtoupper($type) === 'MD5') {
        $sign = md5($stringSignTemp); // MD5签名类型
    } else {
        $sign = hash_hmac('sha256', $stringSignTemp, API_KEY); // HMAC-SHA256签名类型
    }

    return strtoupper($sign);
}

// API参数
$params = [
    'site' => '码农人生',
    'algorithm' => null,
    'appid' => 'wxd930ea5d5a258f4f',
    'mch_id' => 10000100,
    'device_info' => 1000,
    'body' => 'test',
    'desc' => '',
    'price' => 0,
    'site_encode' => '%E7%A0%81%E5%86%9C%E4%BA%BA%E7%94%9F',
    'nonce_str' => 'ibuaiVcKdpRxkhJA',
];

$signMd5 = generate_signature($params, 'MD5');
echo "MD5签名:$signMd5" . PHP_EOL;
// MD5签名:F93653F407056555AEAD160BF39C72D3

$signSha256 = generate_signature($params, 'HMAC-SHA256');
echo "HMAC-SHA256签名:$signSha256" . PHP_EOL;
// HMAC-SHA256签名:D560DAB7A4687B5A3CE9CC9BA43716EDD41BC572C2EE65091F0BB88ED55D212F

// 最终的提交XML
$paramsPost = params_filter_and_sort($params);
$xml = '<xml>' . PHP_EOL;
foreach ($paramsPost as $key => $value) {
    $xml .= "  <$key><![CDATA[$value]]></$key>" . PHP_EOL;
}
$xml .= "  <sign>$signMd5</sign>" . PHP_EOL;
$xml .= "  <sign>$signSha256</sign>" . PHP_EOL;
$xml .= '</xml>';
echo $xml;
// <xml>
//   <appid><![CDATA[wxd930ea5d5a258f4f]]></appid>
//   <body><![CDATA[test]]></body>
//   <device_info><![CDATA[1000]]></device_info>
//   <mch_id><![CDATA[10000100]]></mch_id>
//   <nonce_str><![CDATA[ibuaiVcKdpRxkhJA]]></nonce_str>
//   <price><![CDATA[0]]></price>
//   <site><![CDATA[码农人生]]></site>
//   <site_encode><![CDATA[%E7%A0%81%E5%86%9C%E4%BA%BA%E7%94%9F]]></site_encode>
//   <sign>F93653F407056555AEAD160BF39C72D3</sign>
//   <sign>D560DAB7A4687B5A3CE9CC9BA43716EDD41BC572C2EE65091F0BB88ED55D212F</sign>
// </xml>


// 签名校验工具:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1


//========== 总结 ==========//
// 1、只要参数不变,那么每次生成的签名都是一样的。
// 2、空值参数不参与签名(调用API时也无需传递),空值只有空字符串''和null两种情况。
// 3、构造“key1=value1&key2=value2&key3=value3”格式的签名原串时,value需要使用原值,如果value被URLencode编码了务必解码。

Copyright © 2024 码农人生. All Rights Reserved