微信H5支付:
1,微信外部H5支付:
名词解释:就是在自己的H5网站页面里调用微信支付功能,注意,这里只能是在微信外部支付,在微信内打开网站是无法支付的,
要另外使用微信公众号支付
调用微信H5支付接口前提条件:
1,注册公众号并且通过认证
2,在公众号里申请微信支付,成为商户号
3,在商户平台里申请H5支付
4,在商户平台里的开发设置里设置好H5支付域名
以上4个条件都满足时,便可以调用微信H5支付接口
https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1
上面网址是微信支付API文档,先了解调用接口需要传递的参数,仅看必填项:
appid:微信分配的公众账号ID(企业号corpid即为此appId)
mch_id:微信支付分配的商户号
nonce_str:随机字符串,不长于32位
sign:签名
body:商品简单描述,该字段须严格按照规范传递
out_trade_no:商户系统内部的订单号,32个字符内、可包含字母
total_fee:订单总金额,单位为分
spbill_create_ip:必须传正确的用户端IP
notify_url:接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数
trade_type:H5支付的交易类型为MWEB
scene_info:场景信息,API文档上标注是必填项,但是我没用到,也成功了
以上就是我们要调用微信H5支付接口需要的参数,然后按照以下步骤处理参数:
先调用统一下单接口,获取微信跳转的支付页面URL地址
1,先生成一个随机的32位字符串
String nonceStr = UUID.randomUUID().toString().trim().replaceAll("-", "").toUpperCase();
2,生成签名sign
把参数封装成一个map,传递到以下方法中,得到一个排序后的字符串stringA:
Map<String, String> map = new HashMap<String, String>();
map.put("nonce_str", nonceStr);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("body", body);
map.put("out_trade_no", outTradeNo);
map.put("total_fee", totalFee);
map.put("spbill_create_ip", "113.87.162.45");
map.put("notify_url", notifyUrl);
map.put("trade_type", tradeType);
map.put("key", key);
注意:这个key是商户平台里面设置的一个秘钥key,要拼接到参数中
String stringA = UnicodeUtils.formatUrlMap(map, false, true);
/**
*
* 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
* 实现步骤: <br>
*
* @param paraMap 要排序的Map对象
* @param urlEncode 是否需要URLENCODE
* @param keyToLower 是否需要将Key转换为全小写
* true:key转化成小写,false:不转化
* @return
*/
public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){
String buff = "";
Map<String, String> tmpMap = paraMap;
try{
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
// 构造URL 键值对的格式
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, String> item : infoIds){
if (StringUtils.isNotBlank(item.getKey())){
String key = item.getKey();
String val = item.getValue();
if (urlEncode){
val = URLEncoder.encode(val, "utf-8");
}
if (keyToLower){
buf.append(key.toLowerCase() + "=" + val);
} else {
buf.append(key + "=" + val);
}
buf.append("&");
}
}
buff = buf.toString();
if (buff.isEmpty() == false){
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e){
return null;
}
return buff;
}
通过上面方法得到排序后的字符串stringA后,拼接key并用MD5加密:
String stringSignTemp = stringA + "&key=" + key;
String sign = MD5Utils.getMD5(stringSignTemp);
签名sign就得到了
3,在把包括签名sign的所有参数转成XML格式:
Map<String, String> map = new HashMap<String, String>();
map.put("nonce_str", nonceStr);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("body", body);
map.put("out_trade_no", outTradeNo);
map.put("total_fee", totalFee);
map.put("spbill_create_ip", "113.87.162.45");
map.put("notify_url", notifyUrl);
map.put("trade_type", tradeType);
map.put("key", key);
String sign = WeChatUtil.buildSign(map);
log.info("生成的支付签名:" + sign);
map.put("sign", sign);
String xmlBody = XmlUtils.map2Xml(map);
log.info("微信支付XML参数:" + xmlBody);
4,把XML作为参数调用微信的支付接口:
String payUrl = wechatConfig.getPayUrl();
String result = HttpUtils.getInstance().postXml(payUrl, xmlBody);
log.info("微信支付结果:" + result);
Map<String, Object> resultMap = XmlUtils.xml2Map(result);
//微信返回的微信支付页面URL
mwebUrl = (String)resultMap.get("mweb_url");
mwebUrl = mwebUrl + "|" + outTradeNo;
String prepayId = (String)resultMap.get("prepay_id");
String returnCode = (String)resultMap.get("return_code");
if(returnCode.equals("SUCCESS")){
//开始业务操作....
}
如果参数都没有问题调用接口返回就会包含mweb_url这样一个参数
这是微信支付的页面跳转链接,当返回结果为SECCUSS的时候处理完业务,把mweb_url返回到前端页面进行跳转
注意:如果trade_type设置的不是mweb_url的话是没有这个参数返回的
5,验证支付结果
当跳转到微信支付页面的时候,支付的过程其实是在微信APP里面操作的了,跟我们没什么关系
当支付完成后,默认会跳转回我们发起支付的页面,2种方式验证:
1,被动验证:
通过前面设置的notify_url地址,微信回给这个地址发送支付结果,在这个地址处理微信发送的信息再做业务处理
public Object receiveWechatPayResult(HttpServletResponse response, HttpServletRequest request) {
String resXml = "";
InputStream inStream;
try {
inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");// 获取微信调用我们notify_url的返回信息
log.error("微信支付----result----=" + result);
Map<String, Object> map = XmlUtils.xml2Map(result);
//业务操作.....
resXml = "<xml>"
+ "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>"
+ "</xml> ";
} catch (IOException e) {
// TODO Auto-generated catch block
log.error("支付回调发布异常:" + e);
e.printStackTrace();
}
return resXml;
}
这里要注意的是返回的内容
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
如果没有正确返回,会连续收到微信服务器推送的消息
如果正确返回了,只会收到3次微信推送的消息
这是本人的猜测,如果有大神清楚的请留言告知....
这里会有个延时的问题,就是微信有可能没有及时的往你这个地址发送,所以为了不影响业务,我们还可以主动验证
2,主动验证:
调用微信的订单查询接口查询刚刚的微信支付是否成功
Map<String, String> map = new HashMap<String, String>();
String nonceStr = UUID.randomUUID().toString().trim().replaceAll("-", "").toUpperCase();
String appId = wechatConfig.getAppId();
String mchId = wechatConfig.getMchId();
String key = wechatConfig.getKey();
map.put("nonce_str", nonceStr);
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("out_trade_no", tradeNo);
map.put("key", key);
String sign = WeChatUtil.buildSign(map);
map.put("sign", sign);
log.info("生成的支付签名:" + sign);
String xmlBody = XmlUtils.map2Xml(map);
log.info("微信查询订单状态XML参数:" + xmlBody);
try {
String queryPayStatusUrl = wechatConfig.getQueryPayStatusUrl();
String result = HttpUtils.getInstance().postXml(queryPayStatusUrl, xmlBody);
log.info("微信查询订单状态结果:" + result);
Map<String, Object> resultMap = XmlUtils.xml2Map(result);
tradeState = (String)resultMap.get("trade_state");
if(tradeState.equals("SUCCESS")){
//处理业务....
}
} catch (WrongHttpParameterException | IOException
| InvalidHttpResponseException | EmptyDestinationException e) {
e.printStackTrace();
}
测试过程中有个问题,就是spbill_create_ip这个参数的问题,说是要用发起支付的用户端IP,但是根据文档获取了之后发现一直提示这个IP不对,然后写死了113.87.162.45这个IP居然还能支付成功,搞不懂
2,微信内部H5支付
前面几个条件参照上面写的外部H5支付,
然后在商户平台里的开发设置里配置好公众号支付的支付授权目录
也是要先调用统一下单接口,这个步骤参照上面写的,只有2个区别
1,trade_type这个参数的取值改为JSAPI
2,新增openId参数
openId的获取自己看一下API,很简单
微信内部H5支付与外部H5支付的最大区别在于微信外部H5支付是通过统一下单返回的mweb_url这个参数跳转到微信支付页面
而微信内部H5支付是把统一下单接口返回的prepay_id参数跟其他参数一起返回到前端页面,再由前端页面调用微信支付JS的API来跳转
Map<String, String> premap = new HashMap<String, String>();
premap.put("appId", appId);
premap.put("timeStamp", timeStamp.toString());
premap.put("nonceStr", nonceStr);
premap.put("package", "prepay_id=" + prepayId);
premap.put("signType", "MD5");
premap.put("key", key);
sign = WeChatUtil.buildSign(premap, false, false);
log.info("生成的支付签名:" + sign);
jsonObject.put("appId", appId);
jsonObject.put("timeStamp", timeStamp.toString());
jsonObject.put("nonceStr", nonceStr);
jsonObject.put("package", "prepay_id=" + prepayId);
jsonObject.put("signType", "MD5");
jsonObject.put("paySign", sign);
这里要注意的是以下几点:
1,又要重新获取一次签名sign,而且这里的appId,这个i是大写的,之前是小写的
2,timeStamp参数要用字符串,不然后面转成JSON的时候会没有双引号,导致微信服务器解析不到这个字段
3,加密方式,有些人会出现支付签名验证失败,是由于后台的加密跟前端的加密不一致导致的,统一加密方式即可
前端代码:
1,先引入微信支付JS API
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
2,调用JS触发微信APP支付
WeixinJSBridge.invoke(
'getBrandWCPayRequest', data, function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//doit 这里处理支付成功后的逻辑,通常为页面跳转
$("#returnpay").show();
}else{
alert('支付失败,请联系管理员!res.err_msg:' + res.err_msg);
}
}
);
这里的data是后台封装好的JSON对象,作为参数代入,res是微信返回的数据
验证支付状态参照上面写的即可
支付宝手机网站支付:
还在研究中 ....