微信 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("支付成功.\n微信异步通知--end--\n\n");
        $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>

 

posted @ 2020-04-07 10:24  gentsir  阅读(504)  评论(0编辑  收藏  举报