欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

微信 JSAPI 支付

程序员文章站 2022-03-25 17:38:20
一、申请微信公众号、开通微信支付,通过【APPID】将两者关联,具体操作步骤参考:点击查看 二、在公众号管理后台设置【接收微信支付异步回调通知域名】, 三、在微信支付管理后台设置【支付授权域名】及【KEY】,支付授权域名与接收回调通知域名最好为同域名, 四、生成支付需要的配置文件 五、首次访问网站时 ......

一、申请微信公众号、开通微信支付,通过【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/index/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>