纯手动打造微信H5网页内唤起微信完成支付,
底层操作类库
<?php //微信H5支付类 //By:sevstudio class WeiXinH5{ const _APPID = 'wx6910e78018a20000'; //公众账号ID const _MCHID = '1502480000'; //商户号 const _KEY = 'FIvAYI2CkLvaJ2Ne3qYYriRLHx180000';//商户key const _NOTIFY_URL = 'http://www.xxxx.net/m_weixin_notify.php'; //异步通知URL const _WAP_URL = 'http://www.xxxx.net'; const _WAP_NAME = '名字'; //发起支付,返回唤起微信支付的URL,失败返回null public function toPay($order_no,$total_fee,$body = 'test'){ $arr = array( 'appid' => self::_APPID, 'mch_id' => self::_MCHID, 'nonce_str' => $this->getNonceStr(), 'body' => $body, 'out_trade_no' => $order_no, 'total_fee' => $total_fee, //单位:分 'spbill_create_ip' => $this->get_client_ip(), 'notify_url' => self::_NOTIFY_URL, 'trade_type' => 'MWEB', 'scene_info' =>'{"h5_info": {"type":"Wap","wap_url": "'.self::_WAP_URL.'","wap_name": "'.self::_WAP_NAME.'"}}', ); $info = $this->zidianxu($arr);//字典序 $info['sign'] = $this->md5sign($info);//签名 $xml = $this->ToXml($info); $response = $this->curl_post("https://api.mch.weixin.qq.com/pay/unifiedorder",$xml); //将微信返回的XML 转换成数组 //$objectxml = (array)simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA); $objectxml = $this->FromXml($response); if(!$objectxml || empty($objectxml['return_code']) || empty($objectxml['return_msg'])){ throw new Exception('通信失败'); } if(empty($objectxml['mweb_url'])){ throw new Exception($objectxml['return_msg']); } return $objectxml['mweb_url']; //该URL可唤起微信支付窗口 } //获取异步通知,完成基本信息校验 public function notify(){ if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) { # 如果没有数据,直接返回失败 throw new Exception('没有数据来源'); } $xml = $GLOBALS['HTTP_RAW_POST_DATA']; $result = $this->FromXml($xml); if(!$result || !is_array($result) || count($result) <1){ throw new Exception('数据转换失败'); } if(empty($result['return_code']) || $result['return_code'] != 'SUCCESS'){ throw new Exception('通信标识失败'); } //验证签名 $tmp = array(); foreach($result as $k=>$v){ if(!is_array($v) && $v != '' && $k != 'sign'){ $tmp[$k] = $v; } } ksort($tmp); $tmp_sign = $this->md5sign($tmp); if($tmp_sign != $result['sign']){ throw new Exception('签名校验失败'); } //基本信息校验 if($result['mch_id'] != self::_MCHID){ throw new Exception('商户ID校验失败'); } return $result; } //告知微信可以停止异步通知了 public function stopNotify(){ $res = array( 'return_code' => 'SUCCESS', 'return_msg' => 'OK', ); $txt = $this->ToXml($res); header("Content-type:text/xml;charset=utf-8"); echo $res; exit(); } //查询订单支付结果 public function query($order_no){ $arr = array( 'appid' =>self::_APPID, 'mch_id' =>self::_MCHID, 'out_trade_no' =>$order_no, 'nonce_str' =>$this->getNonceStr() ); $info = $this->zidianxu($arr);//字典序 $info['sign'] = $this->md5sign($info);//签名 $xml = $this->ToXml($info); $response = $this->curl_post("https://api.mch.weixin.qq.com/pay/orderquery",$xml); $result = $this->FromXml($response); if(!$result || !is_array($result)){ throw new Exception('拉取支付结果失败'); } if(empty($result['return_code']) || empty($result['return_msg'])){ throw new Exception('拉取支付结果通信失败'); } if($result['return_code'] != 'SUCCESS'){ throw new Exception($result['return_msg']); } //验证签名 $tmp = array(); foreach($result as $k=>$v){ if(!is_array($v) && $v != '' && $k != 'sign'){ $tmp[$k] = $v; } } ksort($tmp); $tmp_sign = $this->md5sign($tmp); if($tmp_sign != $result['sign']){ throw new Exception('签名验证失败'); } return $result; } //获取随机字符串 public function getNonceStr($length = 32) { $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str =""; for ( $i = 0; $i < $length; $i++ ) { $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1); } return $str; } //数组转xml格式 public function ToXml($data){ if(!is_array($data) || count($data) <= 0){ throw new WxPayException("数组数据异常!"); } $xml = "<xml>"; foreach ($data as $key=>$val){ if (is_numeric($val)){ $xml.="<".$key.">".$val."</".$key.">"; }else{ $xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; } } $xml.="</xml>"; return $xml; } /** * 将xml转为array * @param string $xml */ public function FromXml($xml) { if(!$xml){ return null; } //将XML转为array //禁止引用外部xml实体 libxml_disable_entity_loader(true); return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); } //数组排序 public function zidianxu($arr){ /* sort() - 以升序对数组排序 rsort() - 以降序对数组排序 asort() - 根据值,以升序对关联数组进行排序 ksort() - 根据键,以升序对关联数组进行排序 arsort() - 根据值,以降序对关联数组进行排序 krsort() - 根据键,以降序对关联数组进行排序 */ ksort($arr); return $arr; } //获取客户端IP public function get_client_ip(){ if(isset($_SERVER)){ if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ $realip = $_SERVER['HTTP_X_FORWARDED_FOR']; }elseif(isset($_SERVER['HTTP_CLIENT_IP'])){ $realip = $_SERVER['HTTP_CLIENT_IP']; }else{ $realip = $_SERVER['REMOTE_ADDR']; } }else{ if(getenv("HTTP_X_FORWARDED_FOR")){ $realip = getenv( "HTTP_X_FORWARDED_FOR"); }elseif (getenv("HTTP_CLIENT_IP")){ $realip = getenv("HTTP_CLIENT_IP"); }else{ $realip = getenv("REMOTE_ADDR"); } } $arr = explode(",",trim($realip)); foreach($arr as $o){ if(preg_match("/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/",$o)){ return $o; } } return "unknown"; } //获取MD5签名 public function md5sign($info){ $str = ''; foreach($info as $k=>$v){ $str.= $str == '' ? "$k=$v":"&"."$k=$v"; } $str.="&key=".self::_KEY; return strtoupper(md5($str)); } public function curl_post($postUrl,$data,$header = ""){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL,$postUrl); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); if(substr($postUrl,0,5) == "https"){ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); } if(is_array($header) && count($header)>0){ curl_setopt($ch, CURLOPT_HTTPHEADER, $header); } $response = curl_exec($ch); curl_close($ch); return $response; } }
然后是业务操作部分,
$act = empty($_REQUEST['act']) ? '' : trim($_REQUEST['act']); if($act == 'topay'){ $order_no = trim($_POST['order_no']); $total_fee = intval($_POST['total_fee']); $body = trim($_POST['body']); $hp = new WeiXinH5(); $result = null; try{ $result = $hp->toPay($order_no,$total_fee,$body); }catch(Exception $e){ __log($e->getmessage()); ajax('失败'); } ajax($result,true); }else if($act == 'query'){ $order_no = trim($_POST['order_no']); $hp = new WeiXinH5(); $result = null; try{ $result = $hp->query($order_no); }catch(Exception $e){ __log($e->getmessage()); ajax('订单支付失败'); } if($result['trade_state'] != 'SUCCESS'){ ajax('订单支付失败'); } $order = $con->find("select * from #__order where #__order.pay_no='{$result['out_trade_no']}'"); if(empty($order)){ ajax('订单信息不存在'); } if(intval($order['status_pay']) == STATUS_PAY_YES){ ajax('订单已经支付'); } if(intval($order['fee']) * 100 != intval($result['total_fee'])){ ajax('订单金额不匹配'); } //更新订单信息 $time_pay = strtotime($result['time_end']);//订单完成支付的时间 //更新订单步骤,省略 ajax('订单支付成功',true); } ajax(''); function ajax($info,$flag=false){ $res = array( 'flag' => $flag, 'info' => $info, ); echo json_encode($res); exit(); } function __log($msg){ file_put_contents('log.txt',$msg."\t\t".date('YmdHis').PHP_EOL,FILE_APPEND); }
微信H5支付流程简要说明:
1、新建一个页面A,通过ajax请求上面的文件 act=topay,得到一个微信的URL地址;
2、在页面上A上弹出URL地址,同时显示中间页面如下图,即可唤起微信支付;
3、若支付流程走完,则默认会返回到页面A,可以选择点击上图中的第2/3行,点第2行则通过ajax请求上面的文件 act=query去抓取支付结果,并显示支付结果。当然可以点击第3行回到步骤1;
4、设置的notify页面只有在支付流程走完后才会去通知,如果支付没走完则不会通知,故需要通过步骤2种的图片所示进行引导查询支付结果。