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