跳至主要內容

微信支付API v3

逸尘.Lycodx大约 6 分钟后端PHP微信支付

微信支付API v3

背景

微信支付推出了API v3,相对于之前的安全性更高但是对接稍微麻烦了一点,官方文档虽然很全,但是连贯性不是很好,让人不能很丝滑的拷贝,我就用我自己实现的思路总结一下。

一、关于商户API证书

1、申请API证书

我们先登录微信支付商户平台进入【账户中心】->【API安全】

登录地址:https://pay.weixin.qq.com/index.php/core/home/loginopen in new window

这里有个【申请证书】,先申请一下证书

申请步骤:

参考官方文档:https://kf.qq.com/faq/161222NneAJf161222U7fARv.htmlopen in new window

按照官方文档操作完毕后会生成一个文件夹,里面有4个文件,如下图

2、设置APIv3密钥

设置这个是为了接收微信回调用的,后面会讲

不会的同学可以参考文档:https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.htmlopen in new window

二、签名和验证

配置工作做完,又到了大家最喜欢的拷贝代码环节了

官方文档给出了SDK,并且推荐使用,那我们就直接使用restfulAPI请求。

1、签名生成

官方文档参考:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtmlopen in new window

1.1 构造签名

签名串一共有五行,每一行为一个参数。行尾以 \n(换行符,ASCII编码值为0x0A)结束,包括最后一行。如果参数本身以\n结束,也需要附加一个\n

 HTTP请求方法\n
 URL\n
 请求时间戳\n
 请求随机串\n
 请求报文主体\n

说明

HTTP 请求方法是指 GET、POST、PUT、DELETE 等,注意这里使用的是大写字母;URL 是指请求的 api 接口地址,带https的完整地址,时间戳获取当前时间戳即可;请求随机数随便给给就行;请求报文主体 GET 就传空(空的情况下 \n 不能少),POST 传请求体...

1.2 计算签名值

提示

绝大多数编程语言提供的签名函数支持对签名数据进行签名。强烈建议商户调用该类函数,使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行 Base64编码 得到签名值。

1.3 设置HTTP头

微信支付商户API v3要求请求通过HTTP Authorization头来传递签名。 Authorization认证类型签名信息两个部分组成。

注意

在这里官方文档只是说设置 Authorization ,但是实际并不是只设置这一个,在另一个地方(点击这里open in new window)说了另外两个参数 Accept 和 User-Agent ,

代码演示(验证签名是否正确)

提示

https://api.mch.weixin.qq.com/v3/certificatesopen in new window 接口为例,这个接口可以返回[证书序列号]如果返回的证书序列号和你自己的相同,则说明正确,反之则错误。

查看自己的证书序列号:

1.4 代码演示

<?php
class Demo
{
    //商户号
    protected $mchid = "xxxxxxxx";
    //证书序列号
    protected $serial_no = "XXXXXXXXX"; 
    
    //示例获取正式序列号
    public function example()
    {
        $url = "https://api.mch.weixin.qq.com/v3/certificates";
        $privateKey = $this->getPrivateKey();
        $sign = $this->getSignature("GET", $url, "", $privateKey);
        $header[] = 'User-Agent: https://zh.wikipedia.org/wiki/User_agent';
        $header[] = 'Accept: application/json';
        $header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign; //注意拼接地方有空格
        $back = $this->httpRequest($url, $header);
        var_dump(json_decode($back, true));
    }
    
    //获取私钥
	private function getPrivateKey()
    {
        $filePath = "文件地址,就是上面那个打死也不能让人知道的文件地址";
        return openssl_get_privatekey(file_get_contents($filePath));
    }
    
    //获取签名
    protected function getSignature($method, $targetUrl, $body, $privateKey)
    {
        $url_parts = parse_url($targetUrl);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $timestamp = time();
        $nonceStr = $timestamp . mt_rand(10000, 99999);
        $message = $method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonceStr . "\n" .
            $body . "\n"; //注意这里不能用PHP_EOL
        openssl_sign($message, $raw_sign, $privateKey, "sha256WithRSAEncryption");
        $sign = base64_encode($raw_sign);
        $token = sprintf('mchid="%s",nonce_str="%s",signature="%s",timestamp="%d",serial_no="%s"',
            $this->mchid, $nonceStr, $sign, $timestamp, $this->serial_no);
        return $token;
    }
}

三、下单接口

我们以JSAPI下单为例

官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtmlopen in new window

1、代码演示

<?php
class Demo 
{
    //商户号
    protected $mchid = "xxxxxxxx";
    //小程序appid
    protected $appId = "xxxxx";
    
    //创建支付订单
	public function createPayOrder($orderNo, $description, $price, $openid)
    {
        $privateKey = $this->getPrivateKey(); //上面的demo里有
        $prepayId = $this->getPrepayId($description, $orderNo, $price, $openid);
        if (empty($prepayId)) return '';
        $timestamp = time();
        $nonceStr = mt_rand(10000, 99999);
        $message = self::$appId . "\n" .
            $timestamp . "\n" .
            $nonceStr . "\n" .
            "prepay_id=" . $prepayId . "\n";
        openssl_sign($message, $raw_sign, $privateKey, "sha256WithRSAEncryption");
        $sign = base64_encode($raw_sign);
        return [
            'timeStamp' => $timestamp,
            'nonceStr' => $nonceStr,
            'package' => "prepay_id={$prepayId}",
            'signType' => 'RSA',
            'paySign' => $sign
        ];
    }
    
    //获取预支付id
    protected function getPrepayId($description, $orderNo, $price, $openid)
    {
        $targetUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        $method = "POST";
        $body = json_encode([
            'appid' => $this->appId,
            'mchid' => $this->mchid,
            'description' => $description,
            'out_trade_no' => $orderNo,
            'notify_url' => 'https://xxxxxx', //回调地址(一定是要外网能访问的)
            'amount' => [
                'total' => 100 * $price,
                'currency' => 'CNY'
            ],
            'payer' => [
                'openid' => $openid
            ]
        ]);
        $privateKey = $this->getPrivateKey();
        $sign = $this->getSignature($method, $targetUrl, $body, $privateKey);//上面有
        $header[] = 'User-Agent: https://zh.wikipedia.org/wiki/User_agent';
        $header[] = 'Content-Type: application/json';
        $header[] = 'Accept: application/json';
        $header[] = 'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign; //注意拼接地方有空格
        $res = $this->http_Request($targetUrl, $header, $body);
        $arr = json_decode($res, true);
        if (isset($arr['prepay_id'])) {
            return $arr['prepay_id'];
        }
        return "";
    }
    
    //curl请求
    function http_Request($url, $header = array(), $post_data = "")
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 2);
        if ($post_data != "") {
            curl_setopt($ch, CURLOPT_POST, TRUE);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        }
        $output = curl_exec($ch);
        if ($output === FALSE) {
            $output = "curl 错误信息: " . curl_error($ch);
        }
        curl_close($ch);
        return $output;
    }
}

四、支付回调处理

参考文档:https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-miopen in new window

文档里给出了三种方法,这里就不一一介绍了,直接使用libsodium

1、代码演示

<?php
class Demo 
{
    protected $apiv3PrivateKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"//一、2设置的那个32位密码
    
    //解密
    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= 16) {
            return false;
        }
        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
            return sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->apiv3PrivateKey);
        }
        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
}    

以上的代码拷下来不一定能正常运行,因为PHP一般是没有libsodium这个扩展的,对PHP的版本也有一定要求(求求那些还在用5.6以下版本的老板赶紧把项目重构吧( ̄▽ ̄)")

2、扩展安装

官方说php版本大于7.2默认是安装了扩展的,只需要去php.ini开启扩展即可extension=php_sodium.dll

但是我这里没有看到(如果有的同学直接开启就行了)。

没有的就按照下面的步骤安装即可

参考:点击这里,感谢这位朋友的分享open in new window

Linux安装

下载:https://download.libsodium.org/libsodium/releases/open in new window

安装

./configure
make && make check
sudo make install

pecl安装

pecl install libsodium

添加到php.ini中

extension=sodium.so
上次编辑于: