微信 JSAPI 支付

1、申請微信公衆號、開通微信支付,經過【APPID】將二者關聯,具體操做步驟參考:點擊查看javascript

2、在公衆號管理後臺設置【接收微信支付異步回調通知域名】,php

3、在微信支付管理後臺設置【支付受權域名】及【KEY】,支付受權域名與接收回調通知域名最好爲同域名,css

4、生成支付須要的配置文件html

5、首次訪問網站時靜默獲取用戶 OPENID前端

6、用戶點擊支付時,調用微信【統一下單接口】,獲取 PREPAY_IDjava

7、生成 JSAPI 支付須要的參數ajax

8、用戶輸完支付密碼,前臺輪詢訂單狀態,後臺在 NOTIFY_URL 中處理訂單json

9、PHP demo以下:api

<?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>