微信公众号支付扫码(PHP)

版权声明:转发原创文章请复制文章链接地址 https://blog.csdn.net/weixin_42579642/article/details/83505658

基本思路:

1、用户扫码进入我们的系统页面(自己定义的一个用户输入金额的页面)

      通过获取CODE然后获取openid

2、用户输完金额后,点击支付按钮,进入统一支付接口

      获取微信支付的相应参数

3、调用微信支付JS SDK

下面上代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <!-- 控制浏览器缓存 -->
    <meta http-equiv="Cache-Control" content="no-store" />
    <!-- 优先使用 IE 最新版本和 Chrome -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <link rel="stylesheet" type="text/css" href="/luo/public/weui/dist/style/weui.css">
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
    <title>微信安全支付</title>
    <script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
    <script>
        wx.config({
            debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            appId: 'wx4fbc93aa6fb594cf', // 必填,公众号的唯一标识
            timestamp: '{{$sign['timestamp']}}', // 必填,生成签名的时间戳
            nonceStr: '{{$sign['noncestr']}}', // 必填,生成签名的随机串
            signature: '{{$sign['sign']}}',// 必填,签名
            jsApiList: [
                'chooseImage','updateAppMessageShareData','onMenuShareAppMessage','uploadImage','previewImage','scanQRCode'
            ] // 必填,需要使用的JS接口列表
        });
 
    </script>
 
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
 
        html,
        body {
            height: 100%;
            overflow: hidden;
        }
 
        .clearfix:after {
            content: "\200B";
            display: block;
            height: 0;
            clear: both;
        }
 
        .clearfix {
            *zoom: 1;
        }
 
 
        /*IE/7/6*/
 
        .shuru div::-webkit-scrollbar {
            width: 0;
            height: 0;
            -webkit-transition: 1s;
        }
 
        .shuru div::-webkit-scrollbar-thumb {
            background-color: #a7afb4;
            background-clip: padding-box;
            min-height: 28px;
        }
 
        .shuru div::-webkit-scrollbar-thumb:hover {
            background-color: #525252;
            background-clip: padding-box;
            min-height: 28px;
        }
 
        .shuru div::-webkit-scrollbar-track-piece {
            background-color: #ccd0d2;
        }
 
        .wrap {
            position: relative;
            margin: auto;
            max-width: 640px;
            min-width: 320px;
            width: 100%;
            height: 100%;
            background: #F0EFF5;
            overflow: hidden;
        }
 
        .layer-content {
            position: absolute;
            left: 50%;
            bottom: -200px;
            width: 100%;
            max-width: 640px;
            height: auto;
            z-index: 12;
            -webkit-transform: translateX(-50%);
            transform: translateX(-50%);
        }
 
        /* 输入表单 */
 
        .edit_cash {
            display: block;
            margin-top: 15px;
            padding: 15px;
            margin: 0 auto;
            width: 90%;
            border: 1px solid #CFCFCF;
            border-radius: 10px;
            background-color: #fff;
        }
 
        .edit_cash p {
            font-size: 14px;
            color: #8D8D8F;
        }
 
        .shuru {
            position: relative;
            margin-bottom: 10px;
        }
 
        .shuru div {
            border: none;
            width: 100%;
            height: 50px;
            font-size: 25px;
            line-height: 50px;
            border-bottom: 1px solid #CFCFCF;
            text-indent: 30px;
            outline: none;
            white-space: pre;
            overflow-x: scroll;
        }
 
        .shuru span {
            position: absolute;
            top: 5px;
            font-size: 25px;
        }
 
        .submit {
            display: block;
            margin: 20px auto 0;
            width: 90%;
            height: 40px;
            font-size: 16px;
            color: #fff;
            border-radius: 3px;
            background: #80D983;
            border: 1px solid #47D14C;
            font-weight: 600;
        }
 
 
        /* 键盘 */
 
        .form_edit {
            width: 100%;
            background: #D1D4DD;
        }
 
        .form_edit> div {
            margin-bottom: 2px;
            margin-right: 0.5%;
            float: left;
            width: 33%;
            height: 45px;
            text-align: center;
            color: #333;
            line-height: 45px;
            font-size: 18px;
            font-weight: 600;
            background-color: #fff;
            border-radius: 5px;
        }
 
        .form_edit> div:nth-child(3n) {
            margin-right: 0;
        }
 
        .form_edit> div:last-child {
            background-color: #DEE1E9;
        }
    </style>
</head>
<body>
@csrf
<div class="wrap">
    <form action="" class="edit_cash">
        <p>消费总额</p>
        <div class="shuru">
            <span>&yen;</span>
            <div id="div"></div>
        </div>
        <p>可询问工作人员应缴费用总额</p>
    </form>
    <input type="button" value="支  付" class="submit" />
</div>
<div class="layer-content">
    <div class="form_edit clearfix">
        <div class="num">1</div>
        <div class="num">2</div>
        <div class="num">3</div>
        <div class="num">4</div>
        <div class="num">5</div>
        <div class="num">6</div>
        <div class="num">7</div>
        <div class="num">8</div>
        <div class="num">9</div>
        <div class="num">.</div>
        <div class="num">0</div>
        <div id="remove">删除</div>
    </div>
</div>
 
<script src="https://cdn.bootcss.com/layer/3.1.0/layer.js"></script>
<script>
    $(function(){
        // 监听#div内容变化,改变支付按钮的颜色
        $('#div').bind('DOMNodeInserted', function(){
            if($("#div").text()!="" || $("#div").text()>'0'){
                $('.submit').removeClass('active');
                $('.submit').attr('disabled', false);
                $(".submit").css("background-color","#47D14C");
            }else{
                $('.submit').addClass('active');
                $('.submit').attr('disabled', true);
                $(".submit").css("background-color","#80D983");
            }
        });
 
        $('#div').trigger('DOMNodeInserted');
 
        $('.shuru').click(function(e){
            $('.layer-content').animate({
                bottom: 0
            }, 200)
            e.stopPropagation();
        })
        $('.wrap').click(function(){
            $('.layer-content').animate({
                bottom: '-200px'
            }, 200)
        })
 
        $('.form_edit .num').click(function(){
            var oDiv = document.getElementById("div");
            oDiv.innerHTML += this.innerHTML;
        })
        $('#remove').click(function(){
            var oDiv = document.getElementById("div");
            var oDivHtml = oDiv.innerHTML;
            oDiv.innerHTML = oDivHtml.substring(0,oDivHtml.length-1);
            Inserted();
        });
 
        function weixinpay(data){
            wx.chooseWXPay({
                timestamp: data.timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
                nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
                package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
                signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
                paySign: data.paySign, // 支付签名
                success: function (res) {
 
                }
            });
        }
        $('input[type="button"]').click(function(){
            var money = $("#div").text();
            var zz = /^\d+(\.\d{1,2})?$/;
            var i = layer.load(2);
            if (zz.test(money) && money != 0) {
                $.ajax({
                    url:"/luo/public/index.php/pay_do",
                    type:'get',
                    dataType:'json',
                    data:{openid:'{{$openid}}',money:money},
                    success:function(data){
                        layer.close(i);
                        if(data.status == 1){
                            weixinpay(data);
                        }else{
                            alert('请求失败!')
                        }
                    }
                });
            } else {
                layer.close(i);
                alert('请输入正确金额')
            }
 
        });
    })
</script>
 
</body>
</html>

PHP后台代码:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
 
class IndexController extends Controller
{
    private $arr;
    public function __construct()
    {
        $url = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $url = "$url$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
 
        $ticket=$this -> getTicket();
 
        $timestamp = time();
        $nonceStr = uniqid();
        $params = [
            'noncestr' => $nonceStr,
            'jsapi_ticket' =>$ticket,
            'timestamp'	=> $timestamp,
            'url'		=> $url
        ];
        ksort($params);
        $str = urldecode(http_build_query($params));
        $sign = sha1($str);
 
        $this -> arr = ["timestamp"=>$timestamp,"noncestr"=>$nonceStr,"sign"=>$sign,'appid'=> self::APPID];
 
    }
    public function index(){
        $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx4fbc93aa6fb594cf&redirect_uri=http://dtxb.zjyhj.cn/luo/public/index.php/pay&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect';
        header('location:'.$url);
    }
    public function pay(Request $request){
        $code = $request -> get('code');
        $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx4fbc93aa6fb594cf&secret=6be5c84bdd0cb2567c64f51aefe8b5d7&code='.$code.'&grant_type=authorization_code';
        $data = $this -> curlRequest($url);
        $arr = json_decode($data,true);
        $openid = $arr['openid'];
        return view('pay',['openid'=>$openid,'sign' => $this->arr]);
    }
    public function pay_do(Request $request){
        $openid = $request -> get('openid');
        $money = $request -> get('money');
        $out_trade_no = date('Ymd').time().rand(10000,99999);
        $arr = $this -> getQrUrl($out_trade_no,$money * 100 , $openid);
        if($arr){
            $info['appid'] = self::APPID;
            $info['package'] = "prepay_id=".$arr['prepay_id'];
            $info['timestamp'] = time();
            $info['nonceStr'] = uniqid();
            $info['signType'] = "MD5";
            $params = [
                'appId' => $info['appid'],
                'package' =>$info['package'],
                'timeStamp'	=> $info['timestamp'],
                'nonceStr'		=> $info['nonceStr'],
                'signType' => $info['signType']
            ];
            $sign = $this -> getSign($params);
            $info['paySign'] = $sign;
            $info['status'] = 1;
            return $info;
        }else{
            return ['status'=>2];
        }
    }
    public function getQrUrl($out_trade_no,$money,$openid){
        //调用统一下单API
        $params = [
            'appid'=> self::APPID,
            'mch_id'=> self::MCHID,
            'nonce_str'=>uniqid(),
            'body'=> '支付测试',
            'out_trade_no'=> $out_trade_no,
            'total_fee'=> $money,//2分
            'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'],
            'notify_url'=> self::NOTIFY,
            'trade_type'=>'JSAPI',
            'openid'=> $openid,
        ];
        $arr = $this->unifiedorder($params);
        if($arr){
            return $arr;
        }else{
            return false;
        }
    }
    public function curlRequest($url,$data = ''){
        $ch = curl_init();
        $params[CURLOPT_URL] = $url;    //请求url地址
        $params[CURLOPT_HEADER] = false; //是否返回响应头信息
        $params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回
        $params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向
        $params[CURLOPT_TIMEOUT] = 30; //超时时间
        if(!empty($data)){
            $params[CURLOPT_POST] = true;
            $params[CURLOPT_POSTFIELDS] = $data;
        }
        $params[CURLOPT_SSL_VERIFYPEER] = false;//请求https时设置,还有其他解决方案
        $params[CURLOPT_SSL_VERIFYHOST] = false;//请求https时,其他方案查看其他博文
        curl_setopt_array($ch, $params); //传入curl参数
        $content = curl_exec($ch); //执行
        curl_close($ch); //关闭连接
        return $content;
    }
    public function notify(){
 
    }
}

Controller类:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use DB;
 
class Controller extends BaseController
{
    const KEY = ''; //支付秘钥需要更改成自己的
    const APPID = ''; //APPID需要更改为自己的
    const MCHID = ''; //商户号需要更改成自己的
    const SECRET = ''; //开发者密码需要更改为自己的
    const UOURL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //无需更改 统一下单API地址
    const NOTIFY = '';   //支付通知地址需要更改成你自己服务器的地址
    public function __construct() {
 
    }
    public function getToken(){
        //公众号id和密匙
 
        $appid=self::APPID;
        $secret=self::SECRET;
 
        $token = DB::table('token')  -> where('k','token') -> first();
        if(empty($token) || time() - $token -> time > 3600){
            //获取全局access_token
            $url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$secret."";
            $data=$this->curlRequest($url);
            //print_r($data);
            $data=json_decode($data,true);
            $token=$data['access_token'];
            if(empty($token)){
                DB::table('token') -> insert(['k'=>'token','v'=>$token]);
            }else{
                DB::table('token') -> where('k','token') -> update(['v'=>$token]);
            }
        }
        return $token;
 
    }
 
    public function  getTicket(){
        $ticket = DB::table('token')  -> where('k','ticket') -> first();
        if(empty($ticket) || time() - $ticket -> time > 3600){
            $token=$this ->getToken();
            $url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$token."&type=jsapi";
            $js_api=file_get_contents($url);
            //print_r($js_api);
            $js_api=json_decode($js_api,true);
            $ticket=$js_api['ticket'];
            if(empty($ticket)){
                DB::table('token') -> insert(['k'=>'ticket','v'=>$ticket]);
            }else{
                DB::table('token') -> where('k','ticket') -> update(['v'=>$ticket]);
            }
        }
        return $ticket;
    }
    //获取签名
    public function getSign($arr){
        //去除数组的空值
        array_filter($arr);
        if(isset($arr['sign'])){
            unset($arr['sign']);
        }
        //排序
        ksort($arr);
        //组装字符
        $str = $this->arrToUrl($arr) . '&key=' . self::KEY;
        //使用md5 加密 转换成大写
        return strtoupper(md5($str));
    }
    //获取带签名的数组
    public function setSign($arr){
        $arr['sign'] = $this->getSign($arr);
        return $arr;
    }
    //校验签名
    public function checkSign($arr){
        //生成新签名
        $sign = $this->getSign($arr);
        //和数组中原始签名比较
        if($sign == $arr['sign']){
            return true;
        }else{
            return false;
        }
    }
    //数组转URL字符串 不带key
    public function arrToUrl($arr){
        return urldecode(http_build_query($arr));
    }
    //记录到文件
    public  function logs($file,$data){
        $data = is_array($data) ? print_r($data,true) : $data;
        file_put_contents('./logs/' .$file, $data);
    }
    public function getPost(){
        return file_get_contents('php://input');
    }
    //Xml 文件转数组
    public function XmlToArr($xml)
    {
        if($xml == '') return '';
        libxml_disable_entity_loader(true);
        $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $arr;
    }
    //数组转XML
    public function ArrToXml($arr)
    {
        if(!is_array($arr) || count($arr) == 0) return '';
 
        $xml = "<xml>";
        foreach ($arr as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    //post 字符串到接口
    public function postStr($url,$postfields){
        $ch = curl_init();
        $params[CURLOPT_URL] = $url;    //请求url地址
        $params[CURLOPT_HEADER] = false; //是否返回响应头信息
        $params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回
        $params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向
        $params[CURLOPT_POST] = true;
        $params[CURLOPT_SSL_VERIFYPEER] = false;//禁用证书校验
        $params[CURLOPT_SSL_VERIFYHOST] = false;
        $params[CURLOPT_POSTFIELDS] = $postfields;
        curl_setopt_array($ch, $params); //传入curl参数
        $content = curl_exec($ch); //执行
        curl_close($ch); //关闭连接
        return $content;
    }
    //统一下单
    public function unifiedorder($params){
        //获取到带签名的数组
        $params = $this->setSign($params);
        //数组转xml
        $xml = $this->ArrToXml($params);
        //发送数据到统一下单API地址
        $data = $this->postStr(self::UOURL, $xml);
        $arr = $this->XmlToArr($data);
        if($arr['result_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS'){
            return $arr;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42579642/article/details/83505658