微信支付之html5支付开发遇到的问题记录

微信支付有好几种情况,扫码支付、app支付、公众号内支付、html5支付、小程序支付、刷卡支付。

1.不管选择哪一种支付方式,客户端都要求用户是安装了微信的,如果没有安装微信,就没法完成支付,这点和支付宝支付略有不同。

2.公众号内支付和小程序支付这个都是在嵌入在微信内部的。

插一句题外话,小程序的开发要求你一定要有一个支持https请求的域名服务器,并且支持TLS1.2,想知道自己的网站是否支持TLS1.2,请移步 https://www.ssllabs.com/ssltest/index.html 进行查询 。一般情况下,如果使用tomcat的话,至少tomcat7+配合jdk1.7+ 的环境版本才能支持TLS1.2.

3.app支付就是直接在app内部调起微信支付完成支付

4.html5支付是只在微信浏览器之外的任何其他浏览器中直接调起微信支付,要集成html5支付,前提是你一定要有一个通过icp备案的域名。

app中已经集成了支付宝支付,采用的是html支付的方式,所以微信支付也采用html5支付方式,保持一致。这样将支付以及退款功能几乎全部集中在后端以及html,同原生代码的开发区分开来。

这里稍微说一下支付宝支付的接入。

我个人感觉支付宝支付的接入对于开发者来说,更简单,更容易接入。支付宝提供的接口,包装的非常完善,整个支付页面都会给你包裹好, 你只需要按照接口规范,请求他的接口,将返回的内容写入到页面中返回给前端即可,不用你自己再去写页面。同时包括返回的url设置以及通知的url设置,都是有专门的方法可以进行传参设置。但是相比之下,微信支付这方面就稍微繁琐一点。尤其的返回后的url设置感觉不是特别的友好方便。但是总体来说,文档说明都还是很清楚,按照文档来,也很容易的接入成功。

 

一、前提:

         想接入微信支付,你至少得申请一个微信公众号,同时开通微信支付的功能,然后微信支付相关的设置是到微信商户平台去设置的,这个没什么好摆的,人微信的平台做的还是很完善,一般人都能看得明白。

申请好微信支付之后,记得要开通html5支付的权限,这个权限的申请是在微信商户平台申请的,一般需要2到3天的时间。申请成功之后设置回调域名,这个很重要,一定要设置html5的回调域名,后面有用。

这里也插一句题外话:

关于微信商户平台各种用户角色的登录及使用。默认的管理员用户就是你申请微信支付时候提交上去的那个用户,微信现在都要求实名制了,所以登录微信商户平台的时候,拿申请人的微信扫一波直接登录就行了,什么?你说申请人不用微信,那你还搞毛线微信支付啊。

如果想新增其他的管理员或者其他角色的人,可以在商户平台里直接新增,届时其他人拿手机扫描登录即可,第一次登录的时候会提交申请给管理员,管理员通过后,后续就可以自己扫码登录了。

他这个多角色用户登录主要是针对运维的,对于码农的我来说,设置相关参数之类的,还是必须使用管理员角色登录才行。

 

二、流程概括

微信html5支付的基本流程不是很复杂,微信支付的官方文档有一个很详细的流程图,结合我自己的使用场景,我总结了下,大概概括如下:

  • 1.客户端请求后端的接口,入参包括订单号和订单金额
  • 2.后端接口根据微信官方文档的要求,组装各种需要的参数,然后请求微信的交统一下单接口
  • 3.统一下单接口会返回一个mweb_url的出参。ps:这玩意很重要,全靠它调起微信支付的,这个mweb_url可以再拼接一个redirect_url,用来指定支付操作完成后,页面跳转到哪里,不指定的话,默认是返回上一个页面,即:前面的立即支付的那个页面。
  • 4.将mweb_url复制给页面的立即支付按钮
  • 5.剩下的就交给浏览器和微信客户端了,用户点击立即支付,调起微信支付的客户端,进行支付即可

以上就是支付的基本流程,虽说简单,但是里面有不少地方如果不小心,都很容易出错。

 

三、下面说下几点注意事项

1.微信支付相关接口的出参和入参都是xml格式

这点和支付宝支付不同,要注意。

2.支付或者退款金额的单位

微信里是分,是按照分为单位的!!

这点和支付宝是不一样的,我因为先接入的支付宝支付,再搞微信支付,在这里稍微停顿了下,提交微信接口的时候,一直报错,后来发现原来人微信要求金额的单位是分。

 

3.关于客户端ip的获取

微信支付中有一个安全的校验,在请求mweb_url调起微信支付的客户端的ip要和调用统一下单接口提交过去的spbill_create_ip参数一直,spbill_create_ip这个参数就是客户端的ip。关于这个服务器端获取客户端ip这个,我稍微遇到点坑,因为有些客户端可能有代理,所以通过常规的getRemoteAddr是取不到,例如部分华为手机会自己添加代理,你就是取不到正确ip。这里,提供一个相对完善的获取客户端真是ip的方法,如下:

public static String getRemoteLoginUserIp(HttpServletRequest request)
	{
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
		{
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
		{
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
		{
			ip = request.getHeader("HTTP_CLIENT_IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
		{
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
		{
			ip = request.getRemoteAddr();
		}
		System.out.println("THE REAL IPS IS:"+ip);
		if (ip != null && ip.length() > 15)
		{   
			if (ip.indexOf(",") > 0)
			{
				ip = ip.substring(0, ip.indexOf(","));
			}
		}
		return ip;
	}


方法在生产环境中已验证测试过,路过的大大们可以放心使用。

 

4.签名的问题

一般情况,首次集成都容易有签名对不上的问题,从而调用统一下单接口总是报错,关于这个,其实人官方的文档已经说得很明白了,生成sign的拼装的一个个串是有先后顺序的,也就说,他是按照入参参数名的首字母进行排序的,有了正确的顺序,再按照规则加密生成即可。

这里提供一个我使用的生成sign的方法,如下:

public static String createSign(String characterEncoding, HashMap parameters)
	{
		StringBuffer sb = new StringBuffer();
		Object[] key_arr = parameters.keySet().toArray();
		Arrays.sort(key_arr);//这个是重点,这一句不能少,要排序
		for (Object key : key_arr)
		{
			Object value = parameters.get(key);
			if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key))
			{
				sb.append(key + "=" + value + "&");
			}
		}
		sb.append("key=" + WeiChatPayService.key);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8
		String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
		return sign;
	}

上面代码经过生产测试,路过的大大可以放心使用。

 

5.关于redirect_url

说到这个,要插一句,说一下支付宝支付关于return_url的不同,支付宝支付的提供了两个方法setNotify_url和setReturn_url,其中setReturn_url和redirect_url的作用很类似。都是支付操作完成之后,页面跳转到哪里。支付宝在请求的时候通过方法直接设置即可。

但是微信略不一样,微信设置这个redirect_url是在统一下单接口出参得到mweb_url之后,对这个mweb_url进行拼接处理,在后面添加 &redirect_url=xxx的方式实现的,这个redirect_url的url串要进行urlencode操作。

 

6.Android的webview请求mweb_url时候出错问题

关于这个,出现两种错误情况

第一种、不处理setWebViewClient的shouldOverrideUrlLoading方法

这时候,在点击自己页面中的“立即支付”按钮,即请求mweb_url的时候,webview会直接跳转到错误页面,提示找不到weixin://wap/pay....这样的连接。

第二种、不处理setWebViewClient的shouldOverrideUrlLoading方法

shouldOverrideUrlLoading方法里,return true。这时候,当点击自己页面中“立即支付”按钮即请求mweb_url的时候,就会提示“网络环境未能通过安全验证,请稍后重试”这样的提示,根据微信支付官方的问题,这个出现这个提示说明,微信支付接口获取到客户端的ip和请求统一下单接口送过去的ip不一致。

针对上面两种情况,首先,肯定要重新处理shouldOverrideUrlLoading方法的。其次,合理的代码如下所示:

@Override
			public boolean shouldOverrideUrlLoading(WebView view, String url)
			{
				// 如下方案可在非微信内部WebView的H5页面中调出微信支付  
				if (url.startsWith("weixin://wap/pay?"))
				{
					try
					{
						Intent intent = new Intent();
						intent.setAction(Intent.ACTION_VIEW);
						intent.setData(Uri.parse(url));
						startActivity(intent);
						return true;
					}
					catch (ActivityNotFoundException e)
					{
						Toast.makeText(MainActivity.this, "请安装最新微信", Toast.LENGTH_LONG).show();
					}
				}
				return false;
			}


乍一看这段代码没什么特别,但是关键问题在return true还是return false。

如果返回false,则是默认走webview的处理,返回ture的话,就是不走webview的默认处理,而是自己处理。

之前在这一点一直没有理解太清楚,以至于根本就无法拦截到 weixin://wap/pay这样的url请求,所以一直无法起微信支付。

顺便说一下,在webview中直接调用其他应用,是必须使用Intent的,所以,这里是必须这样处理。

这也解释了为何通过手机自带浏览器可以顺利起微信支付,但是通过自己app内部的webview就一直不行的原因。还是在于没有处理好shouldOverrideUrlLoading函数中的代码。

 

7.关于原路返回退款

这个也是一个坑之一,看了文档,本以为这个会很简单,直接请求微信的接口即可,使用之前支付时候写好的http请求直接做,但是一直不行,一直报错。后来查了下发现,微信的退款是双向证书认证,也就是说,你必须使用https请求并且把证书带上去,证书到微信商户平台里下载即可。具体的使用,可以移步这里,  这位大大写的非常的好。我也验证通过了。

 

 

 

你可能感兴趣的:(Android)