1. 程式人生 > >微信公眾號支付掃碼(PHP)

微信公眾號支付掃碼(PHP)

基本思路:

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;
        }
    }
}