看了好多关于讲解微信H5支付开发的文章,大多数都是通过微信内部浏览器来调用支付接口(其实就是公众号支付),可能是因为H5支付接口刚开放不久吧。 微信官方体验链接: wxpay.wxutil.com/mch/pay/h5.… 看了上面的体验链接,如果感兴趣,可以接着往下看,希望对你有所帮助。

一、Android端

Android端代码相对来说比较简单一些,我这边直接调用系统浏览器打开H5支付页面

 Intent intent = new Intent();
 intent.setAction("android.intent.action.VIEW");
 Uri content_url = Uri.parse(url); //url里面包含了后端需要用到的参数,例如金额,用户ID
 intent.setData(content_url);
 startActivity(intent);

刚开始考虑过使用webview来加载支付页面,但是调试接口的时候发现一直报如下错误:

根据微信官方的报错示例可以看出,应该是webview中没有设置referer导致的。于是,我按照说明加上了referer,然鹅,并没有什么卵用,依然报错。我只好放弃使用webview改用系统浏览器。使用系统浏览器的弊端还是挺明显的,客户端和后台传值不好处理(正在看文章的你,如果用webview调用成功了,还请赐教)

二、PHP端

1.获取从Android端传过来的参数

目前我只想到一种方法,就是通过url携带参数给服务端传值,OK,获取方式如下:

//当前页面的URL地址为:http://www.XXXXX.com/wechatpay/h5Pay.php?money=100&uid=337828932
$string = $_SERVER['QUERY_STRING'];//通过$_SERVER['QUERY_STRING']函数获取url地址?后面的值
$androidData = convertUrlQuery($string);//将字符串$string转化为数组
$money = $androidData['money '];//获取money值 100
$uid = $androidData['uid'];//获取用户ID 337828932

上面用到的convertUrlQuery函数代码

//将字符串参数变为数组
function convertUrlQuery($query)
    $queryParts = explode('&', $query);
    $params = array();
    foreach ($queryParts as $param) {
        $item = explode('=', $param);
        $params[$item[0]] = $item[1];
    return $params;
2.调用微信统一下单API
2.1 具体参数名称以及各参数的用途请查看微信官方文档 统一下单API
    //配置需要传递给微信的参数
    $input = new WxPayUnifiedOrder();
    $input->SetBody("xxxx-商品购买");
    $input->SetAttach("xxxx");
    $input->SetDevice_info("WEB");
    $input->SetOut_trade_no(WxPayConfig::MCHID . date("YmdHis").$uid);//把UID加在末尾主要用于识别用户,给对应的用户充值余额
    $input->SetTotal_fee($money);
    $input->SetTime_start(date("YmdHis"));
    $input->SetTime_expire(date("YmdHis", time() + 600));
    $input->SetGoods_tag("t");
    $input->SetNotify_url("http://www.xxxxx.com/wechatpay/notify.php");//用于接收微信下发的支付结果通知
    $input->SetTrade_type("MWEB");
    $input->SetOpenid($openId);
    $input->SetScene_info("{\"h5_info\": \"h5_info\"{\"type\": \"Wap\",\"wap_url\": \"http://www.xxxxx.com/shop\",\"wap_name\": \"xxx\"}}");
2.2 接下来通过 $order = WxPayApi::unifiedOrder($input); 获取微信返回的参数,unifiedOrder函数具体实现如下
* 统一下单,WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填 * appid、mchid、spbill_create_ip、nonce_str不需要填入 * @param WxPayUnifiedOrder $inputObj * @param int $timeOut * @throws WxPayException * @ return 成功时返回,其他抛异常 public static function unifiedOrder( $inputObj , $timeOut = 6) $url = "https://api.mch.weixin.qq.com/pay/unifiedorder" ; //检测必填参数 if (! $inputObj ->IsOut_trade_noSet()) { throw new WxPayException( "缺少统一支付接口必填参数out_trade_no!" ); } else if (! $inputObj ->IsBodySet()){ throw new WxPayException( "缺少统一支付接口必填参数body!" ); } else if (! $inputObj ->IsTotal_feeSet()) { throw new WxPayException( "缺少统一支付接口必填参数total_fee!" ); } else if (! $inputObj ->IsTrade_typeSet()) { throw new WxPayException( "缺少统一支付接口必填参数trade_type!" ); //异步通知url未设置,则使用配置文件中的url if (! $inputObj ->IsNotify_urlSet()){ $inputObj ->SetNotify_url(WxPayConfig::NOTIFY_URL);//异步通知url $inputObj ->SetAppid(WxPayConfig::APPID);//公众账号ID $inputObj ->SetMch_id(WxPayConfig::MCHID);//商户号 $inputObj ->SetSpbill_create_ip(self::getIp());//终端ip // $inputObj ->SetSpbill_create_ip( "1.1.1.1" ); $inputObj ->SetNonce_str(self::getNonceStr());//随机字符串 $inputObj ->SetSign(); $xml = $inputObj ->ToXml(); $startTimeStamp = self::getMillisecond();//请求开始时间 $response = self::postXmlCurl( $xml , $url , false , $timeOut ); $result = WxPayResults::Init( $response ); self::reportCostTime( $url , $startTimeStamp , $result );//上报请求花费时间 return $result ;
2.3 上面代码中需要注意的有以下3点
  • 获取客服端的真实IP地址
  • 使用REMOTE_ADDR只能获取访问者本地连接中设置的IP。经本人粗略测试,使用UC浏览的时候将无法直接获取真实IP,这是如果把这个IP传过去,微信将会返回 网络环境未能通过安全验证,请稍后再试 的错误

    这时可以使用以下方式获取用户真实IP

    //获取用户真实IP
        public static function getIp(){
            $ip = '';
            if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
                $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
            }elseif(isset($_SERVER['HTTP_CLIENT_IP'])){
                $ip = $_SERVER['HTTP_CLIENT_IP'];
            }else{
                $ip = $_SERVER['REMOTE_ADDR'];
            $ip_arr = explode(',', $ip);
            return $ip_arr[0];
    
  • 把参数转换成XML格式
  • 在这JSON横行的年代,还使用XML格式传递数据,也许这是微信的高端操作,也许是他们以前的框架就是那样,这我们就不管了。直接上代码

    * 输出xml字符 * @throws WxPayException public function ToXml() if(!is_array($this->values) || count($this->values) <= 0) throw new WxPayException("数组数据异常!"); $xml = "<xml>"; foreach ($this->values as $key=>$val) if (is_numeric($val)){ $xml.="<".$key.">".$val."</".$key.">"; }else{ $xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; $xml.="</xml>"; return $xml;

    签名生成的通用步骤如下:

    第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    特别注意以下重要规则:

    ◆ 参数名ASCII码从小到大排序(字典序);
    ◆ 如果参数的值为空不参与签名;
    ◆ 参数名区分大小写;
    ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
    ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
    

    第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

    key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置

    * 生成签名 * @return 签名 public function MakeSign() //签名步骤一:按字典序排序参数 ksort($this->values); $string = $this->ToUrlParams(); //签名步骤二:在string后加入KEY $string = $string . "&key=".WxPayConfig::KEY; //签名步骤三:MD5加密 $string = md5($string); //签名步骤四:所有字符转为大写 $result = strtoupper($string); return $result; * 格式化参数 --格式化成url参数 public function ToUrlParams() $buff = ""; foreach ($this->values as $k => $v) if($k != "sign" && $v != "" && !is_array($v)){ $buff .= $k . "=" . $v . "&"; $buff = trim($buff, "&"); return $buff;
    2.4 通过返回的mweb_url参数调起微信APP
    if ($order['mweb_url']) {
            $orderId = $input->GetOut_trade_no();
            $payUrl = $order['mweb_url']."&redirect_url=http%3A%2F%2Fwww.xxxx.com%2Fwechatpay%2Forderquery.php%3Ftransaction_id%3D".$orderId;
            Log::DEBUG($payUrl);
            //通过JS打开链接,调起微信APP支付
            echo "<script LANGUAGE='Javascript'>";
            echo "location.replace('$payUrl')";
            echo "</script>";
    

    这里详细解释一下payUrl中redirect_url参数的含义

    正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面。 如,您希望用户支付完成后跳转至https://www.wechatpay.com.cn,则可以做如下处理: 假设您通过统一下单接口获到的MWEB_URL= wx.tenpay.com/cgi-bin/mmp… 则拼接后的地址为MWEB_URL= wx.tenpay.com/cgi-bin/mmp… 1.需对redirect_url进行urlencode处理 2.由于设置redirect_url后,回跳指定页面的操作可能发生在:1,微信支付中间页调起微信收银台后超过5秒 2,用户点击“取消支付“或支付完成后点“完成”按钮。因此无法保证页面回跳时,支付流程已结束,所以商户设置的redirect_url地址不能自动执行查单操作,应让用户去点击按钮触发查单操作,而我后面拼接的$orderId就是用于查单操作的参数。回跳页面展示效果可参考下图

    3.处理微信支付结果通知

    支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。

    支付回调基础类

    * 回调基础类 * @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); if($result == false){ $this->SetReturn_code("FAIL"); $this->SetReturn_msg($msg); $this->ReplyNotify(false); return; } else { //该分支在成功回调到NotifyCallBack方法,处理完成之后流程 $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());

    需要注意的是,如果用户只是打开付款界面,而没有执行付款操作的话,是不会触发通知的。

    当我们接收到的参数中同时含有return_code,result_code,trade_state并且每个返回值都为SUCCESS的时候,代表用户付款成功

    if(array_key_exists("return_code", $result)
            && array_key_exists("result_code", $result)
    	&&array_key_exists("trade_state", $resultData)
            && $resultData["trade_state"] == "SUCCESS"
            && $result["return_code"] == "SUCCESS"
           && $result["result_code"] == "SUCCESS")
    			//这里执行给用户充值余额的操作
    
    4.redirect_url回调页面处理

    如果在第2.4步骤中MWEB_URL后拼接上了redirect_url参数,用户支付完成后将返回到这个页面。 当用户点击界面的已完成按钮,将触发查单操作 微信查单操作API 查单操作相关代码如下

    //查询订单
    	public function Queryorder($transaction_id)
    		$input = new WxPayOrderQuery();
    		$input->SetTransaction_id($transaction_id);
    		$result = WxPayApi::orderQuery($input);
    		Log::DEBUG("query:" . json_encode($result));
    		if(array_key_exists("return_code", $result)
    			&& array_key_exists("result_code", $result)
    			&& $result["return_code"] == "SUCCESS"
    			&& $result["result_code"] == "SUCCESS")
    			return $result;
    		return false;
    

    界面布局以及相关的逻辑处理:

    if(isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != ""){
    	$out_trade_no = $_REQUEST["out_trade_no"];
    	$input = new WxPayOrderQuery();
    	$input->SetOut_trade_no($out_trade_no);
        Log::DEBUG("out_trade_no".$out_trade_no);
    //	printf_info(WxPayApi::orderQuery($input));
        $result=WxPayApi::orderQuery($input);
        if(array_key_exists("return_code", $result)
            && array_key_exists("result_code", $result)
            && array_key_exists("trade_state", $result)
            && $result["trade_state"] == "SUCCESS"
            && $result["return_code"] == "SUCCESS"
            && $result["result_code"] == "SUCCESS"){
            $successUrl="success.html";
            echo "<script LANGUAGE='Javascript'>";
            echo "location.replace('$successUrl')";
            echo "</script>";
        }else{
            $failUrl="fail.html";
            echo "<script LANGUAGE='Javascript'>";
            echo "location.replace('$failUrl')";
            echo "</script>";
    	exit();
    <script type="text/javascript">
        window.onload=function () {
            var Request=new UrlSearch(); //实例化
            document.getElementById("out_trade_no").value=Request.transaction_id;
            document.getElementById("transaction_id").style.height = 0;
            document.getElementById("out_trade_no").style.height = 0;
            document.getElementById("btn").style.height = 0;
            $.DialogByZ.Confirm({Title: "", Content: "请确认微信支付是否已完成",FunL:confirmL,FunR:Immediate})
        function confirmL(){
            $.DialogByZ.Close();
            document.getElementById('btn').click();
        function Immediate(){
            //location.href="http://sc.chinaz.com/jiaoben/"
            window.location.href="nonglegou://data/?state=fail";
        function UrlSearch()
            var name,value;
            var str=location.href; //取得整个地址栏
            var num=str.indexOf("?")
            str=str.substr(num+1); //取得所有参数   stringvar.substr(start [, length ]
            var arr=str.split("&"); //各个参数放到数组里
            for(var i=0;i < arr.length;i++){
                num=arr[i].indexOf("=");
                if(num>0){
                    name=arr[i].substring(0,num);
                    value=arr[i].substr(num+1);
                    this[name]=value;
    </script>
    

    微信H5支付的整个流程,大概就是这样子,以下是流程图

  • 用户在商户侧完成下单,使用微信支付进行支付
  • 由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
  • 统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
  • 中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)
  • 如支付成功,商户后台会接收到微信侧的异步通知
  • 用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
  • 商户在展示页面,引导用户主动发起支付结果的查询
  • 商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态
  • 展示最终的订单支付结果给用户
  • 转载请注明出处:www.jianshu.com/p/e49c8003c… 作者:TSY

    喜欢的话,可以关注下我的微信公众号

    会飞的小前端 前端开发工程师 文章被阅读  12,343 一、Android端 二、PHP端 1.获取从Android端传过来的参数 2.调用微信统一下单API 2.1 具体参数名称以及各参数的用途请查看微信官方文档 统一下单API 2.2 接下来通过$order = WxPayApi::unifiedOrder($input);获取微信返回的参数,unifiedOrder函数具体实现如下 2.3 上面代码中需要注意的有以下3点 2.4 通过返回的mweb_url参数调起微信APP 3.处理微信支付结果通知