什么是微信H5支付
H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付
主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付
微信官方也提供了一个体验链接,请在微信外浏览器打开
开发流程
1、用户在商户侧完成下单,使用微信支付进行支付
2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
4、中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)
5、如支付成功,商户后台会接收到微信侧的异步通知
6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
7、商户在展示页面,引导用户主动发起支付结果的查询
8、商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用订单查询接口确认订单状态
9、展示最终的订单支付结果给用户
php代码
class WxPay extends Base {
private $appid;//应用APPID
private $mch_id;//微信支付商户号
private $key;//微信商户API密钥
private $wap_url;//场景信息url
private $notify_url;//回调地址
private $wx_url;//微信h5支付地址
private $order_status_url;//支付完成查询订单状态地址
public function __construct()
{
$this->appid = '';
$this->mch_id = '';
$this->key = '';
$this->wap_url = "http://".$_SERVER['HTTP_HOST']."/tp5/public/index.php/WxPay/index";
$this->notify_url = "http://".$_SERVER['HTTP_HOST']."/tp5/public/index.php/WxPay/notify";
$this->wx_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
$this->order_status_url = "http://".$_SERVER['HTTP_HOST']."/tp5/public/index.php/WxPay/order_status";
}
/*
* 微信支付接口
* @param userid 用户id goodsid 商品id money 如果传入money,goodis就为0
*/
public function index() {
if (!isset($_GET['userid']) || (!isset($_GET['goodsid'])&&!isset($_GET['money']))) {
$this->error('充值信息错误');
}
$user = db_user::table("t_base")->field('nickname')->where(['userid' => $_GET['userid']])->find();
if (!$user) {
$this->error('用户信息错误');
}
if(isset($_GET['money'])){
$_GET['goodsid'] = 0;
}else{
$goods = db('goods')->where(['goodsid' => $_GET['goodsid']])->find();
if (!$goods) {
$this->error('商品信息错误');
}
$_GET['money'] = $goods['money'];
}
$money = $_GET['money']*100;//充值金额 微信支付单位为分
//$money =1;
$userip = $this->get_client_ip(); //获得用户设备IP
$appid = $this->appid; //应用APPID
$mch_id = $this->mch_id; //微信支付商户号
$key = $this->key; //微信商户API密钥
$out_trade_no = "PHP" . date("YmdHis", time()).rand(1000, 9999);//平台内部订单号
$nonce_str = $this->createNoncestr();//随机字符串
$body = "H5充值";//内容
$total_fee = $money; //金额
$spbill_create_ip = $userip; //IP
$notify_url = $this->notify_url; //回调地址
$trade_type = 'MWEB';//交易类型 具体看API 里面有详细介绍
$scene_info ='{"h5_info":{"type":"Wap","wap_url":$this->wap_url,"wap_name":"支付"}}';//场景信息 必要参数
$signA ="appid=$appid&attach=$out_trade_no&body=$body&mch_id=$mch_id&nonce_str=$nonce_str¬ify_url=$notify_url&out_trade_no=$out_trade_no&scene_info=$scene_info&spbill_create_ip=$spbill_create_ip&total_fee=$total_fee&trade_type=$trade_type";
$strSignTmp = $signA."&key=$key"; //拼接字符串 注意顺序微信有个测试网址 顺序按照他的来 直接点下面的校正测试 包括下面XML 是否正确
$sign = strtoupper(MD5($strSignTmp)); // MD5 后转换成大写
$post_data = "
$appid
$mch_id
$body
$out_trade_no
$total_fee
$spbill_create_ip
$notify_url
$trade_type
$scene_info
$out_trade_no
$nonce_str
$sign
";//拼接成XML 格式
$url = $this->wx_url;//微信传参地址
$dataxml = $this->postXmlCurl($post_data,$url); //后台POST微信传参地址 同时取得微信返回的参数
$objectxml = (array)simplexml_load_string($dataxml, 'SimpleXMLElement', LIBXML_NOCDATA); //将微信返回的XML 转换成数组
//var_dump($out_trade_no);die;
if($objectxml['return_code']=='SUCCESS'){
//var_dump($objectxml);die;
$res = db_user::table('t_pay')->data(['userid' => $_GET['userid'], 'orderid' => $out_trade_no, 'money' => $_GET['money'], 'status' => 0, 'createtime' => time(), 'type' => 'wxpay', 'goodsid' => $_GET['goodsid']])->insert();
if($res){
$returnUrl = $this->order_status_url.'?ordernum='.$out_trade_no;
$return_Url = urlencode($returnUrl);//支付完成后要跳转的url。例如做订单结果验证之类的
return view('index',['money'=>$money,'mweb_url'=>$objectxml['mweb_url'],'return_Url'=>$return_Url]);
}
}
}
/*
* 微信h5支付回调
*/
public function notify()
{
$postXml = $GLOBALS["HTTP_RAW_POST_DATA"]; //接收微信参数
if (empty($postXml)) {
return false;
}
wxpay_log('原数据'.PHP_EOL.var_export($postXml,true).PHP_EOL);//支付日志
//将xml格式转换成数组
$attr = $this->xmlToArray($postXml);
if($attr['result_code']=='SUCCESS'){
//验证签名
foreach( $attr as $k=>$v) {
if($k == 'sign') {
$xmlSign = $attr[$k];
unset($attr[$k]);
};
}
$sign = http_build_query($attr);
//md5处理
$sign = md5($sign.'&key='.$this->key);
//转大写
$sign = strtoupper($sign);
if ( $sign === $xmlSign){
//签名相等
$out_trade_no = $attr['out_trade_no'];//订单号
$order = db_user::table('t_pay')->where(['orderid' => $out_trade_no, 'status' => 0])->find();
if($order){
try{
db_user::table('t_pay')->where(['orderid' => $out_trade_no, 'status' => 0])->data(['status' => 1])->update();//订单完成
$goods = db('goods')->where(['goodsid' => $order['goodsid']])->find();
$userporo = db_user::table('t_userprops')->where(['userid' => $order['userid'], 'propid' => $goods['propid']])->field('userid,propnum')->find();
if (empty($userporo)) {
$userporo['propnum'] = 0;
db_user::table('t_userprops')->data(['userid' => $order['userid'], 'propid' => $goods['propid'], 'propnum' => $goods['propnum']])->insert();
} else {
db_user::table('t_userprops')->where(['userid' => $order['userid'], 'propid' => $goods['propid']])->setInc('propnum', $goods['propnum']);
}
//写入日志
db_log::table('t_prop_log')->data(['oper' => $goods['oper'], 'userid' => $order['userid'], 'time' => time(), 'oldnumber' => $userporo['propnum'], 'chgnumber' => $userporo['propnum'] + $goods['propnum']])->insert();
}catch(\Exception $e){
$this->error('执行错误');
}
echo exit(' ');//通知微信成功
}
}
}
}
//xml转数组
public function xmlToArray($xml)
{
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring), true);
return $val;
}
/*
* 订单状态查询页面
*/
public function order_status()
{
$ordernum = $_GET['ordernum'];
if($ordernum){
$order = db_user::table('t_pay')->where(['orderid' =>$ordernum, 'status' => 1])->find();
if($order){
echo "";
}else{
echo "";
}
}else{
echo "";
}
}
public function createNoncestr( $length = 32 ){
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str ="";
for ( $i = 0; $i < $length; $i++ ) {
$str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
public function postXmlCurl($xml,$url,$second = 30){
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
}else{
$error = curl_errno($ch);
curl_close($ch);
echo "curl出错,错误码:$error"."
";
}
}
public function get_client_ip($type = 0) {
$type = $type ? 1 : 0;
$ip = 'unknown';
if ($ip !== 'unknown') return $ip[$type];
if($_SERVER['HTTP_X_REAL_IP']){//nginx 代理模式下,获取客户端真实IP
$ip=$_SERVER['HTTP_X_REAL_IP'];
}elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {//客户端的ip
$ip = $_SERVER['HTTP_CLIENT_IP'];
}elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {//浏览当前页面的用户计算机的网关
$arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$pos = array_search('unknown',$arr);
if(false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
}elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];//浏览当前页面的用户计算机的ip地址
}else{
$ip=$_SERVER['REMOTE_ADDR'];
}
// IP地址合法验证
$long = sprintf("%u",ip2long($ip));
$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
return $ip[$type];
}
}
html代码
微信支付
{load href="__STATIC__/js/jquery.min.js" /}
支付页面,点确认支付会跳转到微信客户端,支付完成后会返回浏览器的redirect_url地址