zoukankan      html  css  js  c++  java
  • 微信 JSAPI 支付

    一、申请微信公众号、开通微信支付,通过【APPID】将两者关联,具体操作步骤参考:点击查看

    二、在公众号管理后台设置【接收微信支付异步回调通知域名】,

    三、在微信支付管理后台设置【支付授权域名】及【KEY】,支付授权域名与接收回调通知域名最好为同域名,

    四、生成支付需要的配置文件

    五、首次访问网站时静默获取用户 OPENID

    六、用户点击支付时,调用微信【统一下单接口】,获取 PREPAY_ID

    七、生成 JSAPI 支付需要的参数

    八、用户输完支付密码,前台轮询订单状态,后台在 NOTIFY_URL 中处理订单

    九、PHP demo如下:

    <?php
    
    return $config = array(
        'SITE_URL' => 'http://www.gentsir.com/',
        'WEIXINPAY_CONFIG' => array(
            'APPID'      => 'wxf96fa703d64967cc', // 公众号后台获取
            'APPSECRET'  => 'e2e87179cfe614dfa0ca16146b0cdfe3', // 公众号后台获取,用于获取用户OPENID
            'MCHID'      => '1582427110', // 微信支付后台获取,
            'PAY_KEY'    => 'a5f5764bc7905be3075c79d1ce216014', // 微信支付后台设置,用于参数签名
            'NOTIFY_URL' => 'http://www.gentsir.com/home/wxpay_sync_notice/', // 异步接收微信支付结果地址,不能有任何鉴权逻辑,能在浏览器中访问
            'TRADE_TYPE' => 'JSAPI', // 支付类型
        ),
    );
    
    ?>
    
    
    <?php
    
    class HomeController extends Controller
    {
        public function __construct()
        {
            parent::__construct();
        }
    
        /**
         * 首页
         *
         */
        public function panel()
        {
            # code here...
        }
            
        /**
         * 引导页
         *
         */
        public function index()
        {
            // 微信静默授权
            if (empty($_SESSION['wx_openid'])) {
                $appid = $config['WEIXINPAY_CONFIG']['APPID'];
                $jump_url = $config['SITE_URL'] . 'home/wxoauth2/';
                $oauth2_url  = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $appid;
                $oauth2_url .= '&redirect_uri=' . urlencode($jump_url);
                $oauth2_url .= '&response_type=code';
                $oauth2_url .= '&scope=snsapi_base';
                $oauth2_url .= '&state=STATE#wechat_redirect';
                redirect($oauth2_url);
            } else {
                redirect('/home/panel/');
            }
        }
    
        /**
         * 不弹出询问获取用户OPENID
         *
         */
        public function wxoauth2()
        {
            $url  = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' . $config['WEIXINPAY_CONFIG']['APPID'];
            $url .= '&secret=' . $config['WEIXINPAY_CONFIG']['APPSECRET'];
            $url .= '&code=' . trim($_GET['code']);
            $url .= '&grant_type=authorization_code';
    
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            $output = curl_exec($ch);
            if (curl_error($ch)) {
                redirect('/home/index');
            }
            curl_close($ch);
    
            $result = json_decode($output, true);
            if (!empty($result['openid'])) {
                $_SESSION('wx_openid', $result['openid']);
                redirect('/home/panel/');
            }
            redirect('/home/index');
        }
    
        /**
         * 用户发请AJAX支付请求
         *
         * 请自先定义 api_error(), api_success(), array2xml(), xml2array(), write_log()
         *
         */
        public function wxpay()
        {
            is_weixin() or exit(api_error('请在微信中打开...'));
    
            if (empty($_SESSION['user_id'])) {
                redirect($config['SITE_URL']);
            }
            if (empty($_POST['total_fee'])) {
                exit(api_error('支付金额为0'));
            }
            if (empty($_POST['goods_id'])) {
                exit(api_error('待支付商品不存在'));
            }
    
            $nonce_str = md5(uniqid(null, true) . mt_rand());
            $out_trade_no = crc32($nonce_str);
    
            // 调用统一下单接口获取prepay_id
            $unifiedorder_params = array(
                'appid'             => $config['WEIXINPAY_CONFIG']['APPID'],
                'mch_id'            => $config['WEIXINPAY_CONFIG']['MCHID'],
                'trade_type'        => $config['WEIXINPAY_CONFIG']['TRADE_TYPE'],
                'notify_url'        => $config['WEIXINPAY_CONFIG']['NOTIFY_URL'],
                'openid'            => $_SESSION['wx_openid'],
                'nonce_str'         => $nonce_str,
                'spbill_create_ip'  => $_SERVER['REMOTE_ADDR'],
                'out_trade_no'      => $out_trade_no,
                'body'              => '微信支付后台商家名称-商品类目名' . rand(1, 100),
                'total_fee'         => $_POST['total_fee'] * 100,
                'product_id'        => $_POST['goods_id'],
            );
    
            ksort($unifiedorder_params);
            $tmp_str  = http_build_query($unifiedorder_params);
            $tmp_str .= '&key=' . $config['WEIXINPAY_CONFIG']['PAY_KEY'];
            $sign = md5($tmp_str);
            $unifiedorder_params['sign'] = strtoupper($sign);
    
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, 'https://api.mch.weixin.qq.com/pay/unifiedorder');
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: text/xml'));
            curl_setopt($ch, CURLOPT_POSTFIELDS, array2xml($unifiedorder_params));
            $output = curl_exec($ch);
            if ($errmsg = curl_error($ch)) {
                exit(api_error($errmsg));
            }
            curl_close($ch);
            $unifiedorder = xml2array($output);
            if (empty($unifiedorder['prepay_id'])) {
                exit(api_error('微信预支付订单生成失败'));
            }
            write_log($unifiedorder);
    
            // 生成JSAPI参数
            $jsapi_params = array(
                'appId'     => $config['WEIXINPAY_CONFIG']['APPID'],
                'timeStamp' => time(),
                'nonceStr'  => $nonce_str,
                'package'   => 'prepay_id=' . $unifiedorder['prepay_id'],
                'signType'  => 'MD5',
            );
    
            ksort($jsapi_params);
            $tmp_str  = http_build_query($jsapi_params);
            $tmp_str .= '&key=' . $config['WEIXINPAY_CONFIG']['PAY_KEY'];
            $sign = md5($tmp_str);
            $jsapi_params['paySign'] = strtoupper($sign);
            $jsapi_params['order_no'] = $out_trade_no; // 用于前台轮询订单状态
            write_log($jsapi_params);
    
            // 商户订单入库
            $order = array(
                'pay_state'   => 0, // 0待支付 1支付成功 2支付失败
                'pay_price'   => $_POST['total_fee'],
                'pay_type'    => $config['WEIXINPAY_CONFIG']['TRADE_TYPE'],
                'pay_order'   => $out_trade_no,
                'user_id'     => $_SESSION['user_id'],
                'goods_id'    => $_POST['goods_id'],
                'create_time' => time(),
            );
            if (!(M('t_order')->add($order))) {
                $order['errmsg'] = '商户订单入库失败';
                write_log($order);
                exit(api_error('商户订单创建失败'));
            }
    
            exit(api_success($jsapi_params));
        }
    
        /**
         * 微信支付异步通知
         *
         */
        public function wxpay_sync_notice()
        {
            write_log('微信异步通知--start--');
    
            // 获取微信通知
            $xml = file_get_contents('php://input', 'r');
            $notify = xml2array($xml);
            if (!isset($notify['return_code'], $notify['result_code'])
                || $notify['return_code'] !== 'SUCCESS'
                || $notify['result_code'] !== 'SUCCESS'
            ) {
                $log = array(
                    'errmsg' => '微信未返回return_code、result_code为SUCCESS',
                    'wx_sync_notice' => $notify, 
                );
                write_log($log);
                $pay_fail = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
                exit($pay_fail);
            }
    
            if (empty($notify['sign']) || $notify['out_trade_no']) {
                $log = array(
                    'errmsg' => '微信未返回签名或订单号',
                    'wx_sync_notice' => $notify, 
                );
                write_log($log);
                $pay_fail = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
                exit($pay_fail);
            }
    
            // 验证签名
            $wx_sign = $notify['sign'];
            unset($notify['sign']);
            ksort($notify);
            $tmp_str  = http_build_query($notify);
            $tmp_str .= '&key=' . $config['WEIXINPAY_CONFIG']['PAY_KEY'];
            $valid_sign = strtoupper(md5($tmp_str));
            if ($wx_sign !== $valid_sign) {
                $log = array(
                    'errmsg' => '微信返回的签名未通过验证',
                    'wx_sync_notice' => $notify,
                    'valid_sign' => $valid_sign,
                );
                write_log($log);
                $pay_fail = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
                exit($pay_fail);
            }
    
            // 验证订单金额及状态
            $where = "order_no = " . $notify['out_trade_no'];
            $order = M('t_order')->where($where)->find();
            if (empty($order) || $order['pay_price'] != $notify['total_fee'] / 100) {
                $log = array(
                    'errmsg' => '商户订单不存在或微信返回的订单金额与商户订单金额不一致',
                    'wx_sync_notice' => $notify,
                    'order_info' => $order,
                );
                write_log($log);
                $pay_fail = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
                exit($pay_fail);
            }
            if ($order['pay_state'] == 1) {
                $log = array(
                    'errmsg' => '订单已被标记为‘支付成功’(重复异步通知)',
                    'wx_sync_notice' => $notify,
                    'order_info' => $order,
                );
                write_log($log);
                $pay_success = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
                exit($pay_success);
            }
    
            // 更新订单
            $data = array(
                'pay_state' => 1,
                'pay_time' => time(),
            );
            $update = M('t_order')->where($where)->save($data);
            if ($update === false) {
                $log = array(
                    'errmsg' => '商户更新订单状态为‘成功’时失败',
                    'wx_sync_notice' => $notify,
                    'order_info' => $order,
                    'update_order' => $data,
                );
                write_log($log);
                $pay_fail = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
                exit($pay_fail);
            }
    
            // 销量+1 库存-1
            // code here...
    
            write_log("支付成功.
    微信异步通知--end--
    
    ");
            $pay_success = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
            exit($pay_success);
        }
    
        /**
         * 检查订单支付状态
         *
         */
        public function order_state()
        {
            $order_no = $_POST['order_no'];
            if (empty($order_no)) {
                exit('FAIL');
            }
    
            $map['pay_order'] = $order_no;
            $map['pay_state'] = 1;
            $order = M('t_order')->where($map)->find();
    
            if (empty($order)) {
                exit('FAIL');
            }
            exit('SUCCESS');
        }
    
        // end all
    }
    
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>微信支付</title>
        <style type="text/css">
            input#pay_btn.disabled {
                pointer-events: none;
                background: #ccc;
            }
    
            input#pay_btn:link,
            input#pay_btn:visited,
            input#pay_btn:hover,
            input#pay_btn:active {
                outline: none;
                box-shadow: none;
            }
        </style>
    </head>
    <body>
        <form id="pay_form">
            <input type="text" name="total_fee" value="5.9">
            <input type="hidden" name="goods_id" value="100256">
            <input type="button" name="" value="点击支付" id="pay_btn" class="">
        </form>
    </body>
    
    <script src="//layer-v3.0.3/layer/layer.js"></script>
    <script type="text/javascript">
        function check_order_state(order_no)
        {
            intvl = setInterval(function () {
                $.ajax({
                   url: '<?= $config['SITE_URL'] . 'home/order_state/' ?>',
                   type: 'POST',
                   data: {order_no: order_no}
                })
                .done(function (msg) {
                    if (msg === 'SUCCESS') {
                        clearInterval(intvl);
                        alert('支付成功');
                    } else {
                        console.log('pay fail...');
                    }
                });
            }, 1000);
        }
    
        function onBridgeReady(data)
        {
            WeixinJSBridge.invoke('getBrandWCPayRequest', data, function (res) {
                layer.closeAll();
                if (res.err_msg == "get_brand_wcpay_request:ok") {
                    // 使用以上方式判断前端返回,微信团队郑重提示:
                    //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
                    check_order_state(data.order_no);
                } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                    alert('已取消支付');
                } else {
                    alert('支付失败');
                }
                return false;
            }); 
        }
    
        $('#pay_btn').click(function () {
            $.ajax({
                url: '<?= $config['SITE_URL'] . 'home/wxpay/' ?>',
                type: 'post',
                data: $('#pay_form').serialize(),
                dataType: 'json',
                beforeSend: function () {
                    $('#pay_btn').addClass('disabled');
                    layer.msg('支付中,请稍候...', {icon: 16, shade: 0.3});
                },
            })
            .done(function (data) {
                setTimeout(function () {
                    layer.closeAll();
                    $('#pay_btn').removeClass('disabled');
                    if (data.errmsg) {
                        layer.msg(data.errmsg);
                        return false;
                    }
                    // 调起微信支付
                    onBridgeReady(data.data);
                }, 2000);
            })
            .fail(function () {
                layer.closeAll();
                $('#pay_btn').removeClass('disabled');
                layer.msg('支付失败,请重新点击支付!');
            });
        });
    </script>
    
    </html>
  • 相关阅读:
    复杂json后端解析出现第二层无数据的问题
    idea启动springboot项目报Error running 'ServiceStarter': Command line is too long. Shorten command line for ServiceStarter or also for Application
    docker强制关闭命令
    scala下实现actor多线程基础
    orcale数据库分配用户
    多线程实现互相通信
    从一份配置清单详解 Nginx 服务器配置
    PostgreSQL CentOS 7 安装配置
    .net core session部分浏览器或移动客户端不可用
    VS2019 远程调试
  • 原文地址:https://www.cnblogs.com/gentsir/p/12651826.html
Copyright © 2011-2022 走看看