tp applet wechat payment part code

Small program WeChat payment development guide: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_2.shtml
Flowchart:
insert image description here

<?php
namespace app\api\controller;

use \think\Cache;
use \think\Controller;
use think\Loader;
use think\Db;
use \think\Cookie;
use \think\Session;
use \think\Request;
use app\api\controller\Base;

// class Pay extends Base
class Pay extends Controller
{
    
    
    // private $apiV3key =config('wx_key');
    //微信解密https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
    private $aesKey;
	const KEY_LENGTH_BYTE = 32;
	const AUTH_TAG_LENGTH_BYTE = 16;

    public function _empty()
    {
    
    
        //把所有城市的操作解析到city方法
       echo "迷路了!";
    }
    //微信支付调用接口,可根据情况调整
    //所需字段 $price   $cost_type  $product_id
    //产品id
    public function payPrice()
    {
    
    
       
        //随机生产的订单号,可根据情况资讯修改
        $out_trade_no=time().session("userinfo.id");
        if(empty($out_trade_no)) return json(['code'=>0,'msg'=>'缺少参数order_sn']);        
        //价格
         $price=(float)input('post.price');
         $product_id=(string)input('post.product_id');
        //JSAPI下单官方说明,请求参数,返回参数
        //https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
        // 官方提供网址
        $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        $urlarr = parse_url($url); //拆解为:[scheme=>https,host=>api.mch.weixin.qq.com,path=>/v3/pay/transactions/native]
        $time = (string)time(); //时间戳
        $noncestr = $this->getNonceStr();
        $appid = config('appid');//appID
        $mchid = config('mch_id');//商户ID
        $xlid  = config('xlid');//证书序列号
        $data  = array();
        $data['appid'] = $appid;
        $data['mchid'] = $mchid;
        $data['description'] = '商品描述'
        
        $data['out_trade_no'] = $out_trade_no;//订单编号,订单号在微信支付里是唯一的
        $data['notify_url'] = config('notify_url');//异步接收微信支付结果通知的回调地址,需根据自己的情况修改回调接口,也可以为空
        $data['amount']['total'] = $price*100;//金额 单位 分
        $data['scene_info']['payer_client_ip'] = $_SERVER["REMOTE_ADDR"];;//场景ip
        //用户在直连商户appid下的唯一标识openid
        $data['payer']['openid']=session("userinfo.open_id"); //openid
        //var_dump($data);
        // var_dump(session('userinfo'));
        // exit();
        //json加密成json对象
        $data = json_encode($data);
        $http_method = 'POST';
        //微信支付API v3通过验证签名来保证请求的真实性和数据的完整性,所有向微信支付发送请求之前都要先进行签名,所有微信支付返回的信息都要进行验签,涉及都 php rsa 加密解密,参考地址:https://blog.csdn.net/wkj001/article/details/129093672?spm=1001.2014.3001.5501
        //微信支付签名生成:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
        //微信签名,包含了$data数据、微信指定地址、随机数和时间
        $key = $this->getSign($http_method,$data,$urlarr['path'],$noncestr,$time);
        // echo $key;
        // exit();
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"',$mchid,$xlid,$noncestr,$time,$key);
        //微信支付商户API v3要求请求通过HTTP Authorization头来传递签名。 Authorization由认证类型和签名信息两个部分组成。
        //头部信息
        $header  = array(
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent:*/*',
            'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
        );
        // var_dump($data);
        // exit();
        //向微信接口地址提交json格式的$data和header的头部信息,得到返回值
        $res = $this->curl_post_https($url,$data,$header);
        // var_dump($res);
        // exit();
        $prepay_id=json_decode($res,true)['prepay_id'];
        //通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的前端JS方法调起公众号支付
        //计算调起支付的签名
        $paySign=$this->getWechartSign($appid,$time,$noncestr,'prepay_id='.$prepay_id);
        $payData=[
            'timeStamp'=>$time,
            'nonceStr'=>$noncestr,
            'package'=>'prepay_id='.$prepay_id,
            'signType'=>'RSA',
            'paySign'=>$paySign
        ];
        return json(['code'=>1,'msg'=>'ok','data'=>$payData]);
       
    }
    

    //获取随机字符串
    public function getNonceStr(){
    
    
        $strs="QWERTYUIOPASDFGHJKLZXCVBNM1234567890";
        $name=substr(str_shuffle($strs),mt_rand(0,strlen($strs)-11),32);
        return $name;
    }

    //微信支付签名
    function getSign($method,$data=array(),$url,$randstr,$time){
    
    
        $str = $method."\n".$url."\n".$time."\n".$randstr."\n".$data."\n";
        $key = file_get_contents('../cert/apiclient_key.pem');//在商户平台下载的秘钥,读取到变量
        $str = $this->getSha256WithRSA($str,$key);
        return $str;
    }

    //调起支付的签名
    function getWechartSign($appid,$timeStamp,$noncestr,$prepay_id){
    
    
        //JSAPI调起支付的参数需要按照签名规则进行签名加密:
        //https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml#menu1
        $str = $appid."\n".$timeStamp."\n".$noncestr."\n".$prepay_id."\n";
        $key = file_get_contents('../cert/apiclient_key.pem');//在商户平台下载的秘钥,读取到变量
        $str = $this->getSha256WithRSA($str,$key);
        return $str;
    }

    //加密
    public function getSha256WithRSA($content, $privateKey){
    
    
        $binary_signature = "";
        $algo = "SHA256";
        openssl_sign($content, $binary_signature, $privateKey, $algo);
        $sign = base64_encode($binary_signature);
        return $sign;
    }

    /* PHP CURL HTTPS POST */
    function curl_post_https($url,$data,$header){
    
     // 模拟提交数据函数
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); // 从证书中检查SSL加密算法是否存在,如果出错则修改为0,默认为1
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        $tmpInfo = curl_exec($curl); // 执行操作
        if (curl_errno($curl)) {
    
    
            echo 'Errno'.curl_error($curl);//捕抓异常
        }
        curl_close($curl); // 关闭CURL会话
        return $tmpInfo; // 返回数据,json格式
    }
    
        /* PHP CURL HTTPS POST */
    function curl_get_https($url,$header){
    
     // 模拟提交数据函数
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); // 从证书中检查SSL加密算法是否存在,如果出错则修改为0,默认为1
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        $tmpInfo = curl_exec($curl); // 执行操作
        if (curl_errno($curl)) {
    
    
            echo 'Errno'.curl_error($curl);//捕抓异常
        }
        curl_close($curl); // 关闭CURL会话
        return $tmpInfo; // 返回数据,json格式
    }
    //什么是平台证书
    //https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml#part-2
    //获取微信支付平台证书序列,解密后是平台公钥 回调验证签名要用到
    //如何获取平台证书 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml
    public function get_cert()
    {
    
    
        $url = 'https://api.mch.weixin.qq.com/v3/certificates';
        $url_parts = parse_url($url);
        $http_method = 'GET';
        $timestamp = (string)time();
        $appid = config('appid');//appID
        $mchid = config('mch_id');//商户ID
        $xlid  = config('xlid');//证书序列号
        
//        $nonce = md5(time() . $out_trade_no);
        $noncestr = $this->getNonceStr();//生成随机字符串
        $body = '';
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
         //给微信支付发送请求前,先签名,包含了$data数据、微信指定地址、随机数和时间
        $key = $this->getSign($http_method,$body,$canonical_url,$noncestr,$timestamp);
        //$sign = $this->wechat_sign($message);//签名 和统一下单 调起支付签名一样
        $token = sprintf('mchid="%s",serial_no="%s",nonce_str="%s",timestamp="%d",signature="%s"',$mchid,$xlid,$noncestr,$timestamp,$key);
        //头部信息
        $header  = array(
            'Accept: application/json',
            'Content-Type: application/json',
            'User-Agent:*/*',
            'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
        );
         //GET 获取平台证书列表
        $res = $this->curl_get_https($url,$header);
        //$res=json_decode($res,true);
        // var_dump($res);
        // exit();
//         //$rs = $this->wechat_req($nonce, $timestamp, $sign, $url, $http_method, '');
//         //保存
// //        $path = APPPATH . '/Wechat/wxp_cert.pem';//平台证书序列
// //        file_put_contents($path, json_encode($rs));
        return $res;
    }

    //微信支付成功后的回调信息
    public function wx_notify(){
    
    
        //file_put_contents("hd.txt", '微信回调');
        //https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_5.shtml
        //支付成功结果通知数据回调  
        $header =Request::instance()->header();
        $response_body = file_get_contents('php://input');

        // // file_put_contents("head.txt", $header);
        // // file_put_contents("body.txt", $response_body);
        //微信签名验证
        //微信支付发来的信息要进行验签
        //https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
        $res=$this->verify_wechatnotify($header, $response_body);
        
        file_put_contents("yq.txt", $res);
        
        if(!$res){
    
    
            return json(['code'=>0,'msg'=>'签名验证失败']);
            exit();
        }
       
		//验签通过后,进行内容解密
        $data = file_get_contents('php://input');
        //json解密成数组
        $data = json_decode($data, true);
        //$data['resource'] 通知数据
        //随机串
        $nonceStr = $data['resource']['nonce'];
        //附加数据
        $associatedData = $data['resource']['associated_data'];
        //数据密文Base64编码
        $ciphertext = $data['resource']['ciphertext'];
        $ciphertext = base64_decode($ciphertext);
        //php>7.1,为了使用这个扩展,你必须将extension=php_sodium.dll添加到php.ini
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
    
    
            //$APIv3_KEY就是在商户平台后端设置是APIv3秘钥
            $orderData = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, config('wx_key'));
            file_put_contents("jieguo.txt", $orderData.PHP_EOL,FILE_APPEND);     
            $orderData = json_decode($orderData, true);
            
            // $str12=print_r($orderData,true);
            // file_put_contents("yewu12.txt", $str12.PHP_EOL,FILE_APPEND); 
                
            if ($orderData['trade_state']=='SUCCESS'){
    
    
                // file_put_contents("SUCCESS.txt", $orderData['attach'].PHP_EOL,FILE_APPEND);
                $order_sn=$orderData['out_trade_no']; //商户订单号
                $transaction_id=$orderData['transaction_id']; //微信订单号
                $open_id=$orderData['payer']['openid'];
                
             $str13=print_r($orderData,true);
            file_put_contents("yewu13.txt", $str13.PHP_EOL,FILE_APPEND);   
            // file_put_contents("test.txt", $orderData['attach'].PHP_EOL,FILE_APPEND);
            file_put_contents("yewu14.txt", (string)$orderData['attach']); 
                //如果附加数据不为空
               $attach=explode("*",(string)$orderData['attach']);
               $cost_type=$attach['0']?$attach['0']:'';
               $product_id=$attach['1']?$attach['1']:'';
               
               file_put_contents("cost_type.txt", $cost_type.PHP_EOL,FILE_APPEND);
               file_put_contents("product_id.txt", $product_id.PHP_EOL,FILE_APPEND);


               
                // 启动事务
                Db::startTrans();
                try{
    
    
                    /*业务处理*/
                    $str1=print_r($orderData,true);
                    file_put_contents("yewu.txt", $str1.PHP_EOL,FILE_APPEND); 
                    //业务1
                    $re['t1']=Db::name('user')->insertGetId($id);
                    //业务2
                     $re['t2']=Db::name('product')->insertGetId($id);
                    //业务3
                     $re['t2']=Db::name('order')->insertGetId($id);
                 
                     //任意一个表写入失败都会抛出异常:
                      if (in_array('0', $re)) {
    
            
                                throw new Exception('意不意外?');
                      }
                    
                     //最后提交事务       
                    Db::commit();
                    //应答微信支付已处理该订单的通知
                    return json(['code' => 'SUCCESS', 'message' =>'ok']);
                } catch (\Exception $e) {
    
    
                    // 回滚事务
                    Db::rollback();
                    return json(['code' => 'ERROR', 'message' =>'no']);
                }
            }
        }
    }
    
  //微信验证签名
   	public function verify_wechatnotify($header, $response_body)
//  	public function verify_wechatnotify()
    {
    
    
// var_dump( $header);
// exit();
        //$header 回调头部信息 //$response_body回调主体
        //获取平台证书
        $plat = $this->get_cert();
        
        //平台证书返回为json格式,json_decode转为数组格式
        $plat=json_decode($plat,true);
        
        // $string = print_r($plat, true);
        // file_put_contents("plat1.txt", $string);
        
        // $string2 = print_r($header, true);
        // file_put_contents("header1.txt", $string2);


        //serial_no平台证书序列号 //跟商户证书序列号不一样 不要搞混了
        $serial_no = $plat['data'][0]['serial_no'];
        
        //验证回调头部信息中的平台证书序列号跟平台证书序列号是否一致
        if ($header['wechatpay-serial'] != $serial_no) {
    
    
            return $data=['code'=>'0','msg'=> '平台证书序列号不一致,签名验证不通过'];
        }

        
        //解密平台证书序列获取公钥
        // var_dump($plat);
        // exit();
        $associatedData = $plat['data'][0]['encrypt_certificate']['associated_data'];
        $nonceStr = $plat['data'][0]['encrypt_certificate']['nonce'];
        $ciphertext = $plat['data'][0]['encrypt_certificate']['ciphertext'];
		//官方解密文档 https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi
		//平台证书解密后即获取公钥
        $plat_cert_decrtpt = $this->decryptToString($associatedData, $nonceStr, $ciphertext);
        // var_dump($plat_cert_decrtpt);
        // exit();
        //这个解密方法看官方文档https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi
        //$pub_path = 'XX/Wechat/wxp_pub.pem';
//        file_put_contents($pub_path, $plat_cert_decrtpt);//平台公钥 //保存平台公钥 回调验签要用
        //$plat_cert_decrtpt = file_get_contents($pub_path);
        //提取平台公钥
        $pub_key = openssl_pkey_get_public($plat_cert_decrtpt);
        // var_dump($pub_key);
        // exit();
        //组装验签名串【未加密数据】
        $message = $header['wechatpay-timestamp'] . "\n" . $header['wechatpay-nonce'] . "\n" . $response_body . "\n";
        //对 Wechatpay-Signature的字段值使用Base64进行解码,得到应答签名。【加密数据】
        $Signature = $header['wechatpay-signature'];
        $sign = base64_decode($Signature);
        //进行验证签名:使用微信支付平台公钥对验签名串和签名进行SHA256 with RSA签名验证
        $ok = openssl_verify($message, $sign, $pub_key, OPENSSL_ALGO_SHA256);//回调验签
        if ($ok == 1) {
    
    
//            return $this->error('1', '回调签名验证成功');
            return true;
        } else {
    
    
            return false;
        }
    }
   //微信支付解密字符串
   //https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
	public function decryptToString($associatedData, $nonceStr, $ciphertext,$aesKey = '') {
    
    
	    
	    if (empty($aesKey)) {
    
    
            $this-> aesKey =config('wx_key'); //微信商户平台 api安全中设置获取
        }else{
    
    
            $this-> aesKey=$aesKey;
        }

		$ciphertext = \base64_decode($ciphertext);
		if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
    
    
			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-> aesKey);
		}

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

		// 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', $this-> aesKey, \OPENSSL_RAW_DATA, $nonceStr,
				$authTag, $associatedData);
		}

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

    
}

Guess you like

Origin blog.csdn.net/wkj001/article/details/129310381