前段时间一直在做微信相关的业务,虽说不是什么新技术,但之前一直没有机会接触到,然后踩了些坑,抽空整理记录下。
微信支付一共分为7种,分为是:付款码支付、JSAPI支付、Native支付、APP支付、H5支付、小程序支付、人脸支付。
此次业务中使用到的是微信JSAPI支付:用户通过微信扫码、关注公众号等方式进入商家H5页面,并在微信内调用 JSSDK完成支付
文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
SDK:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1
JSAPI支付需要在微信中的浏览器打开才能唤起微信支付,效果如下图
附上界面代码
代码中使用了模板引擎
html:
<div class="am-modal-bd"> <img src="{$competition['avatar']}" alt=""><br> <span style="font-size: 13px;color: #72c6ef">{$competition['username']}</span><br> <span id="tips" style="font-size: 13px"></span> <ul class="ul_box"> <li> <div></div> <div class="label_box"> <label> <input type="radio" name="price" value="{$prices[0]}" checked=""> <div class="active"><span class="am-icon-diamond"></span> {$prices[0]}钻</div> </label> <label> <input type="radio" name="price" value="{$prices[1]}"> <div><span class="am-icon-diamond"></span> {$prices[1]}钻</div> </label> <label> <input type="radio" name="price" value="{$prices[2]}"> <div><span class="am-icon-diamond"></span> {$prices[2]}钻</div> </label> </div> </li> <li> <div></div> <div class="label_box"> <label> <input type="radio" name="price" value="{$prices[3]}"> <div><span class="am-icon-diamond"></span> {$prices[3]}钻</div> </label> <label> <input type="radio" name="price" value="{$prices[4]}"> <div><span class="am-icon-diamond"></span> {$prices[4]}钻</div> </label> <label> <input type="number" name="price" id="price" class="input" placeholder="自定义"> </label> </div> </li> </ul> <span style="font-size: 13px">注:1钻=1元,1钻={$activity['offset']}票</span><br> <button type="button" class="am-btn am-btn-primary am-radius" onclick="callpay()" style="margin-top: 5px;">立即微信支付</button> </div>
JavaScript:
// 投票 var offset = {$activity['offset']}; $('#tips').html('正在给{$competition['code']}号赠送{$prices[0]}钻='+({$prices[0]}*offset)+'票'); $('#vote').click(function(){ $.post('/index/index/detailed.html?cid={$cid}&aid={$aid}',{ formhash :'{FORMHASH}', submit:'1', type:1, openid:'{$_G['member']['openid']}' },function(res){ alert(res.msg); if (res.code == 0) { $('.box-1 span').text(res.data.all); $('.box-2 span').text(res.data.rank); $('.box-3 span').text(res.data.up + '票'); } }); }) /* jQuery对象级别插件扩展 */ $.fn.extend({ /* 单选框 */ hlRadio:function () { var radioEl=$(this); radioEl.click(function () { var price = 0; price = $('input:radio:checked').val(); $('#price').val(''); $('#tips').html('正在给{$competition['code']}号赠送'+price+'钻='+(price*offset)+'票'); radioEl.siblings("div").removeClass("active"); $(this).siblings("div").addClass("active"); }); }, }); $("input[name='price']").hlRadio(); $('#price').bind('input propertychange', function(){ var price = 0; price = $('#price').val(); $('#tips').html('正在给{$competition['code']}号赠送'+price+'钻='+(price*offset)+'票'); })
CSS部分
.ul_box { margin:0 auto; padding:0; list-style:none; width: 344px; } .ul_box>li { padding:10px 10px 0 10px; overflow:hidden; border-bottom:#e5e5e5 solid 1px; } .ul_box>li:last-child { border-bottom:none; } .ul_box>li>div { float:left; } .ul_box>li>div:nth-child(1) { width:100px; } .ul_box>li>div:nth-child(2) { width:480px; overflow:hidden; } .label_box>label { display:block; float:left; margin:0 10px 10px 0; position:relative; overflow:hidden; } .label_box>label>input { position:absolute; top:0; left:-20px; } .label_box>label>div { width:100px; text-align:center; border:#dddddd solid 1px; height:40px; line-height:40px; color:#666666; user-select:none; overflow:hidden; position:relative; height: 75px; } .label_box>label>div.active{ border:#d51917 solid 1px; background-color: #fff9f8; color:#d51917; } .label_box>label>div.active:after { content:''; display:block; width:20px; height:20px; background-color:#d51917; transform:skewY(-45deg); position:absolute; bottom:-10px; right:0; z-index:1; } .label_box>label>div.active:before { content:''; display:block; width:3px; height:8px; border-right:#ffffff solid 2px; border-bottom:#ffffff solid 2px; transform:rotate(35deg); position:absolute; bottom:2px; right:4px; z-index:2; } .input{ height: 75px!important; border: 1px solid #DDD!important; position: initial!important; width: 100px!important; text-align: center!important; padding: 5px!important; font-size: 19px!important; } .am-modal-bd{ border-bottom: none; margin-top: 20px; } .am-modal-bd img{ width: 50px; border-radius: 50px; } .am-icon-diamond{ font-size: 20px; display: block; text-align: center; margin-bottom: -7px; }
唤起微信支付,此处的js代码仅在微信手机浏览器中生效
//调用微信JS api 支付 function jsApiCall(appId,timeStamp,nonceStr,package,signType,paySign) { WeixinJSBridge.invoke( 'getBrandWCPayRequest',{ "appId":appId, //公众号名称,由商户传入 "timeStamp":timeStamp, //时间戳,自1970年以来的秒数 "nonceStr":nonceStr, //随机串 "package":package, "signType":signType, //微信签名方式: "paySign":paySign //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ){ WeixinJSBridge.log(res.err_msg); alert('投票成功'); // window.history.go(-1); var url = '/index/index/detailed.html?cid={$cid}&aid={$aid}'; window.location.href=url; // location.reload(); return false; } alert('投票失败'); } ); } // 支付 function callpay() { var price = ''; if ($('#price').val()) { price = $('#price').val(); }else if($('input:radio:checked').val()){ price = $('input:radio:checked').val(); }else{ alert('请输入购买数量'); } if (Number(price) <= 0) { alert('请输入一个大于0的正数'); $('#price').val(''); return false } $.post('/index/index/weixin.html?cid={$cid}&aid={$aid}',{ formhash :'{FORMHASH}', submit:'1', type:1, pay:1, price: price, openid:'{$_G['member']['openid']}' },function(res){ if (res.code ==0) { jsApiCall( res.data.appId, res.data.timeStamp, res.data.nonceStr, res.data.package, res.data.signType, res.data.paySign, ); }else{ alert(res.msg); } }); // if (typeof WeixinJSBridge == "undefined"){ // if( document.addEventListener ){ // document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); // }else if (document.attachEvent){ // document.attachEvent('WeixinJSBridgeReady', jsApiCall); // document.attachEvent('onWeixinJSBridgeReady', jsApiCall); // } // }else{ // } }
界面基于妹子UI,效果如下图
PHP后端,创建本地订单
//①、获取用户openid $tools = new JsApiPay(); $openid = $tools->Getopenid(); //如果已经有了,可以直接赋值 //②、统一下单 $input = new WxPayUnifiedOrder(); $input->SetBody("购买钻石票"); $input->SetAttach(json_encode([ //附加参数 'order' => $order_id, 'cid' => $cid, 'uid' => $_G['uid'], 'aid' => $aid, 'md5' => $md5 ])); $input->SetOut_trade_no(WxPayConfig::MCHID.date("YmdHis")); $input->SetTotal_fee($price); //价格,单位分,“元记得*100” $input->SetTime_start(date("YmdHis")); $input->SetTime_expire(date("YmdHis", time() + 600)); $input->SetGoods_tag("test"); $input->SetNotify_url(WX_NOTIFY_URL); $input->SetTrade_type("JSAPI"); $input->Setopenid($openid); $order = WxPayApi::unifiedOrder($input); $jsApiParameters = $tools->GetJsApiParameters($order); //保存本地订单 DB::insert('vote_order', [ 'order' => $order_id, 'time' => time(), 'price' => intval($_GET['price']), 'uid' => $_G['uid'], 'openid' => $openid, 'ip' => $ip ]); Json(['code' => 0, 'msg' => '成功','data' => json_decode($jsApiParameters,true)]); //获取共享收货地址js函数参数 // $editAddress = $tools->GetEditAddressParameters(); // debug($jsApiParameters); //③、在支持成功回调通知中处理成功之后的事宜,见 notify.php /** * 注意: * 1、当你的回调地址不可访问的时候,回调通知会失败,可以通过查询订单来确认支付是否成功 * 2、jsapi支付时需要填入用户openid,WxPay.JsApiPay.php中有获取openid流程 (文档可以参考微信公众平台“网页授权接口”, * 参考http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html) */
<?php /** * * 回调基础类 * @author widyhu * */ class WxPayNotify extends WxPayNotifyReply { /** * * 回调入口 * @param bool $needSign 是否需要签名输出 */ final public function Handle($needSign = true) { $msg = "OK"; //当返回false的时候,表示notify中调用NotifyCallBack回调失败获取签名校验失败,此时直接回复失败 $result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg); // 这是一个调试技巧 // file_put_contents('a.txt',print_r($msg,true)); if ($result == false) { $this->SetReturn_code("FAIL"); $this->SetReturn_msg($msg); $this->ReplyNotify(false); return; } else { //该分支在成功回调到NotifyCallBack方法,处理完成之后流程 $attach = json_decode($msg['attach'], true); // file_put_contents('aaaa.txt',print_r($attach,true)); $cid = $attach['cid']; $aid = $attach['aid']; $uid = $attach['uid']; $order = $attach['order']; $md5 = $attach['md5']; $vote_order = DB::fetch_first("SELECT * FROM " . DB::table('vote_order') . " WHERE `order`='{$order}' and status = 0"); if (!$vote_order) { $this->SetReturn_code("SUCCESS"); $this->SetReturn_msg("OK"); } $sign_md5 = md5($vote_order['openid'] . $vote_order['order'] . $vote_order['uid'] ); if ($sign_md5 == $md5) { // 所有活动信息 $activity = DB::fetch_first("SELECT * FROM " . DB::table('vote_activity') . " WHERE aid={$aid} "); $ip = $vote_order['ip']; $jewel_vote = $vote_order['price'] * $activity['offset']; // 投票 DB::query("UPDATE `pre_vote_competition` SET `jewel`=jewel+{$vote_order['price']},jewel_vote=jewel_vote+{$jewel_vote} WHERE (`cid`='{$cid}')"); // 增加投票记录 DB::insert('vote_record',[ 'time' => TIMESTAMP, 'openid' => $vote_order['openid'], 'type' => 1, 'aid' => $aid, 'cid' => $cid, 'ip' => $ip, 'price' => $vote_order['price'], 'jewel_vote' => $jewel_vote, 'uid' => $uid, 'order' => $vote_order['order'] ]); // 更新订单状态 DB::query("UPDATE `pre_vote_order` SET `status` = 1 WHERE `order`={$order}"); // 更新活动总收益 DB::query("UPDATE `pre_vote_activity` SET `profit`=profit+{$vote_order['price']} WHERE `aid`={$aid}"); } $this->SetReturn_code("SUCCESS"); $this->SetReturn_msg("OK"); } $this->ReplyNotify($needSign); } /** * * 回调方法入口,子类可重写该方法 * 注意: * 1、微信回调超时时间为2s,建议用户使用异步处理流程,确认成功之后立刻回复微信服务器 * 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入 * @param array $data 回调解释出的参数 * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法 * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调 */ public function NotifyProcess($data, &$msg) { //TODO 用户基础该类之后需要重写该方法,成功的时候返回true,失败返回false return true; } /** * * notify回调方法,该方法中需要赋值需要输出的参数,不可重写 * @param array $data * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调 */ final public function NotifyCallBack($data) { $msg = "OK"; $result = $this->NotifyProcess($data, $msg); if ($result == true) { $this->SetReturn_code("SUCCESS"); $this->SetReturn_msg("OK"); } else { $this->SetReturn_code("FAIL"); $this->SetReturn_msg($msg); } return $result; } /** * * 回复通知 * @param bool $needSign 是否需要签名输出 */ final private function ReplyNotify($needSign = true) { //如果需要签名 if ($needSign == true && $this->GetReturn_code($return_code) == "SUCCESS") { $this->SetSign(); } WxpayApi::replyNotify($this->ToXml()); } }
回调类调用
<?php if(!defined('IN_DISCUZ')) { exit('Access Denied'); } require_once "./source/plugin/vote/class/WxpayAPI_php_v3/lib/WxPay.Api.php"; require_once "./source/plugin/vote/class/WxpayAPI_php_v3/lib/WxPay.Notify.php"; require_once "./source/plugin/vote/class/WxpayAPI_php_v3/example/WxPay.JsApiPay.php"; require_once './source/plugin/vote/class/WxpayAPI_php_v3/example/log.php'; //初始化日志 // $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); // $log = Log::Init($logHandler, 15); // Log::DEBUG("begin notify"); $notify = new WxPayNotify(); $notify->Handle(false);
注意:微信支付需要在后台配置一个微信支付授权目录,其中的APPID等参数在微信支付后台可获取到。
微信公众号信息配置
APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
MCHID:商户号(必须配置,开户邮件中可查看)
KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)
设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置),
获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
这要怎么复制代码呀 我想实战操作下 求博主给个代码的百度云链接可否
@ 苏宝宝 @苏宝宝:微信官方给的SDK中有调用示例,文章中只是基本的html代码,复制去你也是没办法直接使用的,仅供参考
表情包怎么整合的,能不能出个教程大佬
@ 译丶浅夏 @译丶浅夏:这个有现成的插件,可以去搜一下,但是由于每个博客的模板都不一样,得花点时间自己整上去
前提是要有个可以开通支付的微信公众号
朕已阅