PHP对接微信APP支付

PHP对接微信APP支付

       前面跟大家分享了PHP对接支付宝APP支付,今天跟大家分享对接微信APP支付,还是参考之前微信V3加密那篇,自己封装函数,不使用微信的SDK,下面贴出相关代码,我这里使用的是thinkphp开发,大家可以参考代码后,根据实际情况进行修改。      

       一、微信类

       我们先创建一个微信的类,命名为WxPay,代码如下

<?php
/**
 * Created by PhpStorm.
 * User: zhangw
 * Date: 2024-03-23
 * Time: 10:11
 */

namespace app\service;

/**
 * 微信支付
 * Class WxPay
 * @package app\service
 */
class WxPay
{

    //偏移量
    const AUTH_TAG_LENGTH_BYTE = 16;

    /**
     * 获取微信支付参数
     * @param string $outTradeNo
     * @param int $amount
     * @param string $notifyUrl
     * @return mixed
     */
    static function getPayData($outTradeNo = "", $amount = 0 ,$notifyUrl = "" )
    {
        #获取配置参数
        $wxAppId = Config('appConfig.wx_pay_app_id');                           //微信appId
        $wxMerchantId = Config('appConfig.wx_mch_id');                          //商户号Id
        $wxApiSerialNo = Config('appConfig.wx_api_serial_no');                  //商户API证书序列号
        $wxMerchantApiPrivateKey = public_path() . "wx/apiclient_key.pem";      //商户私钥证书
        #设置超时时间
        $outTime = time() + 2 * 60;
        $timeExpire = date("Y-m-d", $outTime) . "T" . date("H:i:s", $outTime) . "+08:00";
        #请求接口地址
        $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
        #订单金额转换【单位为分】
        $amount = $amount * 100;
        #组合基本信息
        $params = array(
            "appid" => $wxAppId,                  //appId
            "mchid" => $wxMerchantId,             //直连商户号
            "description" => "商品购买",          //描述
            "out_trade_no" => $outTradeNo,        //商户订单号
            "time_expire" => $timeExpire,         //超时时间
            "notify_url" => $notifyUrl,           //回调地址
            "amount" => array(                    //订单金额
                "total" => $amount,               //订单金额
                "currency" => "CNY"               //金额单位
            ),
        );
        #获取authorization
        $authorization = self::RequestSign("POST", $url, json_encode($params), $wxMerchantId, $wxMerchantApiPrivateKey, $wxApiSerialNo);
        #请求接口
        $data = self::curlPostWithWx($url, $authorization, $params, 30);
        #返回
        return $data;

    }

    /**
     * 获取呼起支付参数
     * @param string $prepayId
     * @return array
     */
    static function getPayParams($prepayId = "")
    {
        #获取配置参数
        $wxAppId = Config('appConfig.wx_pay_app_id');                           //微信appId
        $wxMerchantId = Config('appConfig.wx_mch_id');                          //商户号Id
        $wxMerchantApiPrivateKey = public_path() . "wx/apiclient_key.pem";      //商户私钥证书
        #获取当前时间戳
        $timeStamp = time();
        #生成一个随机字符串
        $nonceStr = self::getNonceStr();
        #构造签名串
        $requestSign = sprintf("%s\n%s\n%s\n%s\n", $wxAppId, $timeStamp, $nonceStr, $prepayId);
        #计算计算签名值
        $sign = self::calculateSignatureValue($requestSign, $wxMerchantApiPrivateKey);
        #组合参数
        $params = array(
            "appid" => $wxAppId,
            "partnerid" => $wxMerchantId,
            "prepayid" => $prepayId,
            "package" => "Sign=WXPay",
            "noncestr" => $nonceStr,
            "timestamp" => (string)$timeStamp,
            "sign" => $sign
        );
        #返回
        return $params;
    }

    /**
     * 解密数据
     * @param $associatedData
     * @param $nonceStr
     * @param $cipherText
     * @return bool|string
     */
    public static function decryptToString($associatedData, $nonceStr, $cipherText)
    {
        $cipherText = \base64_decode($cipherText);
        if (strlen($cipherText) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }

        //微信API v3密钥
        $wxApiV3Key = Config('appConfig.wx_v3_key');

        // 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, $wxApiV3Key);
        }

        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
            \Sodium\crypto_aead_aes256gcm_is_available()) {
            return \Sodium\crypto_aead_aes256gcm_decrypt($cipherText, $associatedData, $nonceStr, $wxApiV3Key);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($cipherText, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($cipherText, -self::AUTH_TAG_LENGTH_BYTE);

            return \openssl_decrypt($ctext, 'aes-256-gcm', $wxApiV3Key, \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }

        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }

    /**
     * 生成签名
     * @param string $method
     * @param string $url
     * @param string $request
     * @param string $wxMerchantId
     * @param string $certKey
     * @param string $wxApiSerialNo
     * @return string
     */
    static function RequestSign($method = "POST", $url = "", $request = "", $wxMerchantId = "", $certKey = "", $wxApiSerialNo = "")
    {
        #截取获取当前请求地址【去除域名】
        $url_parts = parse_url($url);
        $path = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        #获取当前时间戳
        $timeStamp = time();
        #生成一个随机字符串
        $nonceStr = self::getNonceStr();
        #构造签名串
        $requestSign = sprintf("%s\n%s\n%s\n%s\n%s\n", $method, $path, $timeStamp, $nonceStr, $request);
        #计算计算签名值
        $sign = self::calculateSignatureValue($requestSign, $certKey);
        #设置HTTP头获取Authorization
        $token = self::createToken($wxMerchantId, $nonceStr, $timeStamp, $wxApiSerialNo, $sign);
        #返回
        return $token;
    }

    /**
     * 计算签名值
     * @param $requestSign
     * @param $certKey
     * @return string
     * 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值
     */
    static function calculateSignatureValue($requestSign, $certKey)
    {
        $certKey = file_get_contents($certKey);
        openssl_sign($requestSign, $raw_sign, $certKey, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        return $sign;
    }

    /**
     * 获取token
     * @param $merchant_id
     * @param $nonce
     * @param $timestamp
     * @param $serial_no
     * @param $sign
     * @return string
     */
    static function createToken($merchant_id, $nonce, $timestamp, $serial_no, $sign)
    {
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $merchant_id, $nonce, $timestamp, $serial_no, $sign);
        return $token;
    }

    /**
     * 产生随机字符串,不长于32位
     * @param int $length
     * @return string
     */
    static function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * post请求
     * @param string $url
     * @param string $authorization
     * @param array $params
     * @param int $timeout
     * @return mixed
     */
    static function curlPostWithWx($url = "", $authorization = "", $params = array(), $timeout = 30)
    {
        $paramsString = json_encode($params);
        // 初始化curl
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // post数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // post的变量
        curl_setopt($ch, CURLOPT_POSTFIELDS, $paramsString);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($paramsString),
                'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
                'Accept: application/json',
                'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
            )
        );
        // 运行curl,结果以jason形式返回
        $res = curl_exec($ch);
        curl_close($ch);
        // 取出数据
        $data = json_decode($res, true);
        return $data;
    }
}

       二、新增配置

       我这里是放在thinkphp的config下的appConfig.php文件下,分别是

//微信支付参数
'wx_pay_app_id' => '微信应用appId',
'wx_mch_id'=> '微信商户Id',
'wx_v3_key'=> '微信V3密钥Key',
'wx_api_serial_no'=> '微信商户API证书序列号',

然后我们在public目录下新建wx目录,将生成的私钥证书放到该目录下,并且命名为apiclient_key.pem。

       三、获取前端需要支付参数

#初始化数据
$order_sn = "1223333"           //订单编号
$total_money = 0.01;            //订单金额
$notify_url = "";               //回调地址
#获取微信付款参数
$wxPay = new WxPay();
$wxPayInfo = $wxPay->getPayData($order_sn,$total_money,$notify_url);
#获取支付参数
$wxPayUrl = $wxPay->getPayParams($wxPayInfo["prepay_id"]);

这样我们就可以获得前端呼起支付所有参数。

       四、回调处理

       我们同样新增一个接口来处理回调,代码如下

#获取用户参数
$signData = request()->post();
#记录请求参数
Log::write("微信回调",json_encode($signData));
#提取参数
$resource = isset($signData["resource"])?$signData["resource"]:[];   //加密信息体
#判断加密参数
if (!is_array($resource) || count($resource)<=0) {
    return json(["code"=>"FAIL","message"=>"加密体获取失败","msg"=>""]);
}
#判断加密要素是否存在
$associatedData = isset($resource["associated_data"]) ? $resource["associated_data"] : "";
$nonce = isset($resource["nonce"]) ? $resource["nonce"] : "";
$cipherText = isset($resource["ciphertext"]) ? $resource["ciphertext"] : "";
if (empty($associatedData) || empty($nonce) || empty($cipherText)) {
    return json(["code"=>"FAIL","message"=>"加密参数有误","msg"=>""]);
}
#解密数据
$wxPay = new WxPay();
$wxOrderInfo = $wxPay::decryptToString($associatedData, $nonce, $cipherText);
if (empty($wxOrderInfo)) {
    return json(["code"=>"FAIL","message"=>"回调数据解密失败","msg"=>""]);
}
$wxOrderInfo = json_decode($wxOrderInfo, true);
#提取数据
$tradeState = isset($wxOrderInfo["trade_state"])?$wxOrderInfo["trade_state"]:"";
$outTradeNo = isset($wxOrderInfo["out_trade_no"])?$wxOrderInfo["out_trade_no"]:"";
$successTime = isset($wxOrderInfo["success_time"])?$wxOrderInfo["success_time"]:"";
$tradeNo =  isset($wxOrderInfo["transaction_id"])?$wxOrderInfo["transaction_id"]:"";
if(empty($tradeState) || empty($outTradeNo) || empty($successTime) || empty($tradeNo)){
    return json(["code"=>"FAIL","message"=>"回调数据获取失败","msg"=>""]);
}
#判断是否支付成功
if($tradeState!="SUCCESS"){
    return json(["code"=>"FAIL","message"=>"支付失败","msg"=>""]);
}
#获取支付用户Id
$buyerId = $wxOrderInfo["payer"]["openid"];
#时间转化
$gmtPayment = strtotime($successTime);

以上是部分片段,这部分代码基本可以直接复制,下面的就是各自业务处理逻辑,我这里就不放上去了,仅供大家参考

      以上就是thinkphp不通过微信SDK,进行微信APP支付以及回调处理。  

0条评论

发表评论