微信支付服务端的一些坑及最终解决

来源:互联网 发布:免费蝙蝠侠埋雷软件 编辑:程序博客网 时间:2024/05/21 18:43

有1年多没搞微信支付了,最近跳槽,要重做APP,又来接触微信这个坑比。OK,不多说,上代码。以下是我的一个controller类,重点在下面

 // 微信交易类型  
    private static final String TRADETYPE = "APP"; 
    // 微信统一下单接口路径  
    private static final String UNIFORMORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 
    // 微信回调地址  
    private static final String NOTIFYURL = "*****";  
    // 微信商户号:*****  
    private static final String MCHID = "******"; 
    //微信APIKEY  
    private static final String APIKEY ="*******";  
    //微信APPID  
    private static final String APPID ="*******";
    
/**
* @Description: Ajax生成微信预支付ID
* @param entity
* @return JsonResult
* @throws JSONException 
* @throws UnsupportedEncodingException 
*/
@RequestMapping(value = "/getPrepayId",method = RequestMethod.POST)
@ResponseBody
public String getPrepayId(HttpServletRequest request) throws UnsupportedEncodingException {  
        ResultObject result = new ResultObject(false);// 返回数据结果集合  
        request.setCharacterEncoding("UTF-8");    
        try {  
            // 订单编号  
            String out_trade_no = request.getParameter("out_trade_no") == null ? null : request.getParameter("out_trade_no").trim();// 订单编号  
            // 消费金额  
            String money = request.getParameter("money") == null ? null : request.getParameter("money").trim();// 消费金额  
            // 消费主题  
            String subject = request.getParameter("subject") == null ? null : request.getParameter("subject").trim();// 消费主体  
            
            if(StringUtils.isEmpty(out_trade_no)){  
                result.setMsg("参数:out_trade_no 为空");  
                result.setResultCode("-1");  
                return JSON.toJSONString(result);  
            }  
            if(StringUtils.isEmpty(money)){  
                result.setMsg("参数:money 为空");  
                result.setResultCode("-1");  
                return JSON.toJSONString(result);  
            }  
            if(StringUtils.isEmpty(subject)){  
                result.setMsg("参数:subject 为空");  
                result.setResultCode("-1");  
                return JSON.toJSONString(result);  
            } 
            String total_fee = "";
            total_fee = String.valueOf((new BigDecimal(money).multiply(new BigDecimal(100))).longValue());
            SortedMap<Object,Object> parame = new TreeMap<Object,Object>();  
            parame.put("appid", APPID);  
            parame.put("mch_id", MCHID);// 商家账号。  
            String randomStr = getRandomString(18).toUpperCase(); 
            parame.put("nonce_str", randomStr);// 随机字符串       
            parame.put("body", subject);// 商品描述  
            parame.put("out_trade_no", out_trade_no);// 商户订单编号  
            parame.put("fee_type", "CNY");//币种
            parame.put("total_fee", total_fee);// 消费金额  
            String ip = getIpAddr(request);
            if (StringUtils.isEmpty(ip)) {  
                parame.put("spbill_create_ip", "127.0.0.1");// 消费IP地址  
            } else {  
                parame.put("spbill_create_ip", ip);// 消费IP地址  
            }  
            parame.put("notify_url", NOTIFYURL);// 回调地址  
            parame.put("trade_type", TRADETYPE);// 交易类型APP  
            String sign =createSign(parame);   
            parame.put("sign", sign);// 数字签证  
            String xml = getRequestXML(parame);   
  
            String content = HttpUtil.sendPost(UNIFORMORDER, xml);    
            System.out.println(content);  
            JSONObject jsonObject = JSONObject.parseObject(XmltoJsonUtil.xml2JSON(content));  
            JSONObject result_xml = jsonObject.getJSONObject("xml");  
            JSONArray result_code =  result_xml.getJSONArray("result_code");              
            String code = (String)result_code.get(0);  
            
            if(code.equalsIgnoreCase("FAIL")){  
                result.setMsg("微信统一订单下单失败");  
                result.setResultCode("-1");  
            }else if(code.equalsIgnoreCase("SUCCESS")){  
            result.setFlag(true);
                JSONArray prepay_id = result_xml.getJSONArray("prepay_id");               
                String prepayId = (String)prepay_id.get(0); 
                String timestamp = String.valueOf(System.currentTimeMillis()/1000);
                SortedMap<Object,Object> signParams = new TreeMap<Object,Object>();
        signParams.put("appid", APPID);
        signParams.put("noncestr", randomStr);
        signParams.put("prepayid", prepayId);
        signParams.put("package", "Sign=WXPay");
        signParams.put("partnerid", MCHID);
        signParams.put("timestamp", timestamp);
        String sign2 = createSign(signParams).toUpperCase();
        SortedMap<Object, Object> parameterMap2 = new TreeMap<Object, Object>();  
        parameterMap2.put("appid", APPID);  
                parameterMap2.put("partnerid", MCHID);  
                parameterMap2.put("prepayid", prepayId);  
                parameterMap2.put("package", "Sign=WXPay");  
                parameterMap2.put("noncestr", randomStr);  
                //本来生成的时间戳是13位,但是ios必须是10位,所以截取了一下
               // parameterMap2.put("timestamp", String.valueOf(System.currentTimeMillis()).toString().substring(0,10));  
                parameterMap2.put("timestamp", timestamp);  
                parameterMap2.put("sign", sign2);  
                result.setMsg("微信统一订单下单成功");  
                result.setResultCode("1");  
                result.setData(parameterMap2);  
            }             
            return JSON.toJSONString(result);  
          
        } catch (Exception e) {  
            result.setMsg(e.getMessage());  
            result.setResultCode("-1");  
            return JSON.toJSONString(result);  
        }  
}


// 返回用IP地址  
    public String getIpAddr(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.getRemoteAddr();  
        }  
        return ip;  
    }  
  
    // 随机字符串生成  
    public static String getRandomString(int length) { // length表示生成字符串的长度  
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
        Random random = new Random();  
        StringBuffer sb = new StringBuffer();  
        for (int i = 0; i < length; i++) {  
            int number = random.nextInt(base.length());  
            sb.append(base.charAt(number));  
        }  
        return sb.toString();  
    }  
      
      
    //拼接xml 请求路径  
    public static String getRequestXML(SortedMap<Object, Object> parame){  
        StringBuffer buffer = new StringBuffer();  
        buffer.append("<xml>");  
        Set set = parame.entrySet();  
        Iterator iterator = set.iterator();  
        while(iterator.hasNext()){  
            Map.Entry entry = (Map.Entry) iterator.next();  
            String key = (String)entry.getKey();  
            String value = (String)entry.getValue();  
            //过滤相关字段sign  
            if("sign".equalsIgnoreCase(key)){  
                buffer.append("<"+key+">"+"<![CDATA["+value+"]]>"+"</"+key+">");  
            }else{  
                buffer.append("<"+key+">"+value+"</"+key+">");  
            }             
        }  
        buffer.append("</xml>");  
        return buffer.toString();  
    }  
      
    //创建md5 数字签证  
    public static String createSign(SortedMap<Object, Object> parame){  
        StringBuffer buffer = new StringBuffer();  
        Set set = parame.entrySet();  
        Iterator iterator = set.iterator();  
        while(iterator.hasNext()){  
            Map.Entry entry = (Map.Entry) iterator.next();  
            String key = (String)entry.getKey();  
            Object value = (String)entry.getValue();  
            if(null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)){  
                buffer.append(key+"="+value+"&");  
            }             
        }  
        buffer.append("key="+APIKEY);  
        System.out.println(buffer.toString());
        String sign = MD5.getMessageDigest(buffer.toString().getBytes()).toUpperCase();
        System.out.println("签名参数:"+sign);  
        return sign;  
    }


第一步,生成prepayid,这一步,只要你的appid,mch_id,key没写错,那么99%以上都能获取到prepayid,如果失败,那肯定是几个ID和key有问题,仔细检查,包括编码等,仔细仔细仔细检查。

问题来了,第二步,对获取到的prepayid进行二次签名,官方文档的坑来了,官方并没有详细说明这一步骤,一切的一切只能靠自己摸索,爬坑。

首先第一坑:参数顺序,我这里用了SortedMap,自动对参数进行asc编码顺序,一劳永逸,当然,也可以用其他map,但一定要注意参数顺序,必须是asc编码顺序。

第二坑:参数package的Sign=WXPay中=的编码问题,转码即可,小坑。

第三坑:苹果系统的timestamp位数,统一成10位即可,小坑。

第四坑:次级大坑,注意,官方文档说到的参与二次签名的参数,prepayId,appId,timeStamp等,如果你用他们的驼峰进行大写,那么你就完了。一定要小写,小写,小写。

第五坑:最大坑,一样,官方文档并没有对于二次签名有过多赘述,如果你上面几个坑完美出坑,那么,你获取到的签名sign跟官方验证的sign绝对是一样的,然而,将这些玩意丢回给APP,APP调起支付,大大的几个字出现了,验证签名失败!WTF!不要急,我已折腾了好几天,终于发现坑在哪里,那就是noncestr随机字符串,参与二次签名的随机字符串不能再次生成,注意,不能再次生成,一定要用第一步中获取prepayid时的那串字符串,一定要用第一步中获取prepayid时的那串字符串,一定要用第一步中获取prepayid时的那串字符串。

第六坑:经历了上述5坑,相信你已经有想干死人的冲动,那么你以为这就结束了吗,还有最后一坑,那就是APP签名已经包名,一定要与开放平台中的一致,然而,即使一致了你以为又结束了吗,NO,如果你更改过开放平台中的签名,并且,在更改前调用过APP微信支付,那么一定一定一定一定记得清除微信缓存。

至此,所有坑都成功出坑,终于出现了支付页面,举国欢腾,微信去年买了个表。最后附上MD5签名类

import java.security.MessageDigest;


public class MD5 {


private MD5() {}

public final static String getMessageDigest(byte[] buffer) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(buffer);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
}