微信支付中文乱码,带中文签名不成功
- 在做微信公众号支付和H5支付时发现支付参数带中文就会签名失败,试过很多种办法如:
new String(xml.toString().getBytes(), "ISO8859-1");
把xml转为ISO8859-1提交到微信统一下单接口签名不正确,网上一般都是说这种做法。
- 后面我用这样的方式能签名成功,也能支付
paraMap.put("body", URLEncoder.encode("棋牌", "UTF-8"));
但是这样编码后,微信支付显示的body是编码后的一串乱码。
-
最后终于查到是需要改md5签名前字符串的编码。
-
统一下单的代码:WeChatH5PayImpl.java 微信h5支付
@Override
public JsonObject gateway(PayPlatform payPlatform, PayOrder order, Map<String, String> params) {
JsonObject result = new JsonObject();
try {
//字典序列排序
//第一次签名
Map<String, String> paraMap = new HashMap<>();
// paraMap.put("total_fee", order.getPrice().toString());
paraMap.put("total_fee", "1");
paraMap.put("appid", APPID);
paraMap.put("out_trade_no", order.getMerchantOrderNo());
paraMap.put("attach", order.getMerchantOrderNo());
//TODO 中文编码有问题
paraMap.put("body", "棋牌" ); //如果不转码,参数带中文会签名失败
paraMap.put("mch_id", MCH_ID);
paraMap.put("nonce_str", WeChatPublicNumberPayImpl.getNonceStr());
paraMap.put("notify_url", notifyUrl);
//paraMap.put("openid", params.getOrDefault("operId", ""));//"oPKW80lcsqmHLWvPLElQoN2p6Eow");
String spbill_create_ip = params.getOrDefault("spbillCreateIp","127.0.0.1");
if(-1 != spbill_create_ip.indexOf(",")){
spbill_create_ip = spbill_create_ip.split(",")[0];
}
paraMap.put("spbill_create_ip", spbill_create_ip);
paraMap.put("trade_type", "MWEB");
paraMap.put("scene_info","{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"http://www.test.com\",\"wap_name\": \"棋牌\"}} ");
//字典序列排序
String url = WeChatPublicNumberPayImpl.formatUrlMap(paraMap, false, true);
url = url + "&key=" + MCH_ID_KEY;
String sign = MD5Utils.MD5Encoding(url).toUpperCase();
StringBuffer xml = new StringBuffer();
xml.append("<xml>");
for (Map.Entry<String, String> entry : paraMap.entrySet()) {
xml.append("<" + entry.getKey() + ">");
xml.append(entry.getValue());
xml.append("</" + entry.getKey() + ">" + "\n");
}
xml.append("<sign>");
xml.append(sign);
xml.append("</sign>");
xml.append("</xml>");
log.info("xml \n {} ", xml.toString());
String responseBosy = HttpUtils.sentPost(PAYURL, xml.toString(), "UTF-8");
log.info("responseBosy \n {} " , responseBosy );
Map<String,String> respBodyMap = WeChatPublicNumberPayImpl.readStringXmlOut(responseBosy);
String return_code = respBodyMap.getOrDefault("return_code","");
if(null != return_code && "SUCCESS".equals(return_code)){
//成功
result.put("code", 1);
result.put("type", ReturnType.JUMP_PAGE_TYPE.getCode());
result.put("redirect", respBodyMap.getOrDefault("mweb_url","")+"&redirect_url=http://h5.ccac7.com/api/login");
}else{
//失败
result.put("code",0);
result.put("message","sign error");
}
} catch (Exception e) {
result.put("code", 0);
result.put("message","创建订单失败");
}
return result;
}
- WeChatPublicNumberPayImpl.java ASCII 码从小到大排序的代码
/**
* 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
特别注意如果是微信公众号第二次签名使用这个 (prepay_id=wx2018011916085772ffb69ce20165288425)拼接出来的url package会有问题
* 实现步骤: <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;
}
- MD5Utils.java
package com.rw.common.utils;
import java.security.MessageDigest;
public class MD5Utils {
private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
//重要的就是这里,要调这个方法签名才可以
public static String MD5Encoding(String s) {
byte[] btInput = null;
try {
btInput = s.getBytes("UTF-8");
}catch (Exception e){
}
return MD5(btInput, 32);
}
public static String MD5(String s) {
byte[] btInput = s.getBytes();
return MD5(btInput, 32);
}
public static String MD5_16(String str) {
byte[] btInput = str.getBytes();
return MD5(btInput, 16);
}
private static String MD5(byte[] btInput, int length) {
try {
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// MessageDigest mdInst = MessageDigest.getInstance("SHA-1");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
String result = new String(str);
return length == 16 ? result.substring(8, 24) : result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
-
这是最后执行结果:
-
还有注意的:微信公众号支付的支付授权地址路径不能只写域名地址。