Android微信支付集成研究(android端和Java服务端)

先来一张微信支付官方的的流程图

这里写图片描述

官网在该图的最上方有这么一句话:统一下单API、支付结果通知API和查询订单API等都涉及签名过程,调用都必须在商户服务器端完成,这句话比较重要,接下来我将严格按照这个规则展开说明,不太严谨的有把上面的过程放在客户端的,这可能会有一定的安全隐患。

第一步:商户后端统一下单API(对应于该流程图的第4,5,6步骤)
①商户后端请求微信支付后端:对应第4步
参数详情如下:

字段名变量名必填类型示例值描述
应用IDappidString(32)wxd678efh567hg6787微信开放平台审核通过的应用APPID
商户号mch_idString(32)1230000109微信支付分配的商户号
商品描述bodyString(128)腾讯充值中心-QQ会员充值商品描述交易字段格式根据不同的应用场景按照以下格式:APP——需传入应用市场上的APP名字-实际商品名称,天天爱消除-游戏充值。
商户号mch_idString(32)1230000109微信支付分配的商户号
随机字符串nonce_strString(32)5K8264ILTKCH16CQ2502SI8ZNMTM67VS随机字符串,不长于32位。推荐随机数生成算法
商户订单号out_trade_noString(32)20150806125346商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
总金额total_feeInt888订单总金额,单位为分,详见支付金额
终端IPspbill_create_ipString(16)123.12.12.123用户端实际ip
通知地址notify_urlString(256)http://www.weixin.qq.com/wxpay/pay.php接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
交易类型trade_typeString(16)APP支付类型
签名signString(32)C380BEC2BFD727A4B6845133519F3AD6签名,详见签名生成算法

以上为必须发送给微信服务端的参数,会由StortedMap进行排序(主要是为了接下来的sign生成),其中,APP_body、total_fee、spbill_create_ip、为APP端要传过来的参数,用于生成订单信息,其他参数都是在后台的,一般都为固定的常数或者可以在服务端生成。

   SortedMap<String, Object> oparams = new SortedMap<String, Object>();
private void createSingParams(String ip, String orderId,
        int price) {

            oparams .setParameter("appid", ConfigUtil.APPID)//应用号;
            oparams .setParameter("body", WeixinConstant.PRODUCT_BODY)// 商品描述;
            oparams .setParameter("mch_id", ConfigUtil.MCH_ID)// 商户号;
            oparams .setParameter("nonce_str", PayCommonUtil.CreateNoncestr())// 16随机字符串(大小写字母加数字)
            oparams .setParameter("out_trade_no", orderId)// 商户订单号;
            oparams .setParameter("total_fee", "1")// 银行币种支付的钱钱啦;
            oparams .setParameter("spbill_create_ip", ip)// IP地址;
            oparams .setParameter("notify_url", ConfigUtil.NOTIFY_URL) // 微信回调地址;
            oparams .setParameter("trade_type", ConfigUtil.TRADE_TYPE)// 支付类型 APP;

}
public void setParameter(String parameter, String parameterValue) {
    String v = "";
    if(null != parameterValue) {
        v = parameterValue.trim();
    }
    this.params.put(parameter, v);
}

可以看到上边必需传递的10个参数还有一个sign没有,这个sign是要根据前面的参数来生成的,当然了,如果你需要给微信传递其他的参数可以,可以参考标准预订单请求参数

   String signStr = createSign(signParams);
    //这个是加签的

   protected void createSign(Map signParams) {
    StringBuffer sb = new StringBuffer();
    Set es = signParams.entrySet();
    Iterator it = es.iterator();
    while(it.hasNext()) {
        Map.Entry entry = (Map.Entry)it.next();
        String k = (String)entry.getKey();
        String v = (String)entry.getValue();
        if(null != v && !"".equals(v) 
                && !"sign".equals(k) && !"key".equals(k)) {
            sb.append(k + "=" + v + "&");
        }
    }
    sb.append("key=" + this.getKey());
    String sign = MD5Util.MD5Encode(sb.toString(), enc).toUpperCase();

    return sign;
    }

public class MD5Util {

private static String byteArrayToHexString(byte b[]) {
    StringBuffer resultSb = new StringBuffer();
    for (int i = 0; i < b.length; i++)
        resultSb.append(byteToHexString(b[i]));

    return resultSb.toString();
}

private static String byteToHexString(byte b) {
    int n = b;
    if (n < 0)
        n += 256;
    int d1 = n / 16;
    int d2 = n % 16;
    return hexDigits[d1] + hexDigits[d2];
}

public static String MD5Encode(String origin, String charsetname) {
    String resultString = null;
    try {
        resultString = new String(origin);
        MessageDigest md = MessageDigest.getInstance("MD5");
        if (charsetname == null || "".equals(charsetname))
            resultString = byteArrayToHexString(md.digest(resultString
                    .getBytes()));
        else
            resultString = byteArrayToHexString(md.digest(resultString
                    .getBytes(charsetname)));
    } catch (Exception exception) {
    }
    return resultString;
}

private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
        "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

}

大概意思就是:将非空参数按照ASCII从小到大,以键值对进行拼接,然后最后拼接上key(应用对应的密钥)生成了str1,然后对str1进行MD5加密,并且转化为大写,就得到了sign签名的值。

可能有几点注意事项请参考:签名算法
params.setParameter(“sign”, signStr );
然后将params转化为xml格式,如下:

<xml>
   <appid>wx2421b1c4370ec43b</appid>
   <attach>支付测试</attach>
   <body>APP支付测试</body>
   <mch_id>10000100</mch_id>
   <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
   <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
   <out_trade_no>1415659990</out_trade_no>
   <spbill_create_ip>14.23.150.211</spbill_create_ip>
   <total_fee>1</total_fee>
   <trade_type>APP</trade_type>
   <sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>

把生成的xml字符串作为实体以post方式发送给微信服务端接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder

②微信支付后端的回复:对应于步骤5
具体返回格式参考:返回结果
提取回复中的 prepayid:回复的字符串为xml的,需要进行解析,然后取出来然后进行签名的验证,验证通过即可以取出来prepayId。

 Map<String,String>  resParams  = WCPayUtils.getParamsMapFromXml(xmlstrRes);  //将xml解析为map
  resParams .put("key",pContect.getWcPayKey());  
 if(resParams .containsKey("sign")&& resParams .get("prepay_id") != null &&  
                                     !"".equals(resParams .get("prepay_id"))&& !"null".equals(resParams .get("prepay_id"))){  
       if(WCPayUtils.checkSign(resParams )){//签名认证成功  
            for(Map.Entry<String, String> param : resParams .entrySet()) {  
                   if("appid".equals(resParams .getKey()))  
                   System.out.println("prepayid="+resParams .getValue());                          
            }
        }
}

/** 
 * 从xml字符串中解析参数 
 * @param xml 
 * @return 
 * @throws Exception 
 */  
public static Map<String, String> getParamsMapFromXml(InputStream xml) throws Exception {  
    Map<String, String> params = new HashMap<String, String>(0);  
    SAXReader saxReader = new SAXReader();  
    Document read = saxReader.read(xml);  
    Element node = read.getRootElement();  
    listNodes(node, params);  
    return params;  
}  

 @SuppressWarnings({ "unchecked" })  
public static void listNodes(Element node, Map<String, String> params) {  
    // 获取当前节点的所有属性节点  
    List<Attribute> list = node.attributes();  
    // 遍历属性节点  
    if ((list == null || list.size() == 0) && !(node.getTextTrim().equals(""))) {  
        if(node.getTextTrim().contains("<![CDATA[")){  
            String[] split = node.getTextTrim().split("<![CDATA[");  
            split[1].replaceAll("]]>", "");  
            params.put(node.getName(), split[1]);  
        }else{  
            params.put(node.getName(),node.getTextTrim());  
        }  
    }  
    // 当前节点下面子节点迭代器  
    Iterator<Element> it = node.elementIterator();  
    // 遍历  
    while (it.hasNext()) {  
        // 获取某个子节点对象  
        Element e = it.next();  
        // 对子节点进行遍历  
        listNodes(e, params);  
    }  

 /** 
 * 签名认证 
 * @param paramsMap 
 * @return 
 * @throws Exception 
 */  
public static <T> boolean checkSign(Map<String, T> paramsMap) throws Exception {  
        String sign = getSignFromParamMap(paramsMap);  
        return paramsMap.get("sign").equals(sign);  
}  

/** 
 * 从map中获取签名sign 
 * @param paramsMap 
 * @return 
 * @throws Exception 
 */  
public static <T> String getSignFromParamMap(Map<String, T> paramsMap) throws Exception{  
    if (paramsMap != null && paramsMap.size() > 0) {  
        Map<String, T> params = new TreeMap<String, T>(new Comparator<String>() {  
            public int compare(String s1, String s2) {  
                return s1.compareTo(s2);  
            }  

        });  
        params.putAll(paramsMap);  
        StringBuffer tempStr = new StringBuffer();  
        for (Entry<String, T> param : params.entrySet()) {  
            if (!"sign".equals(param.getKey()) && !"key".equals(param.getKey())   
                    && !"".equals(param.getValue()) && param.getValue() != null) {  
                tempStr.append(param.getKey() + "=" + param.getValue() + "&");  
            }  
        }  
        String temp = tempStr.toString().concat("key="+params.get("key"));  
        return MD5Utils.getMD5(temp).toUpperCase();  
    }  
    return null;  
}  

③商家服务端将订单信息返回给App端支付信息:对应于步骤6、7
从上面resParams 中解析出如下参数,然后再次再次签名(同上),最后返回给App端。

调起支付参数
请求参数

字段名变量名类型必填示例值描述
应用IDappidString(32)wx8888888888888888微信开放平台审核通过的应用APPID
商户号partneridString(32)1900000109微信支付分配的商户号
预支付交易会话IDprepayidString(32)WX1217752501201407033233368018微信返回的支付交易会话ID
扩展字段packageString(128)Sign=WXPay暂填写固定值Sign=WXPay
随机字符串noncestrString(32)5K8264ILTKCH16CQ2502SI8ZNMTM67VS随机字符串,不长于32位。推荐随机数生成算法
时间戳timestampString(10)1412000000时间戳,请见接口规则-参数规定
签名signString(32)C380BEC2BFD727A4B6845133519F3AD6签名,详见签名生成算法

第二步APP端调起支付
关键代码如下:

IWXAPI api;
PayReq request = new PayReq();
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "7FFECB600D7157C5AA49810D2D8F28BC2811827B";
api.sendReq(request);

支付回调参考:支付结果回调

第三步:商户服务端接受微信服务端的异步支付状态消息并处理相应业务逻辑。官方文档:支付结果通用通知

当app客户端向微信发起支付请求,并付款成功后,微信会向异步通知URL也就是notify_url上面传递支付接口信息,也是xml字符串。我们需要将之解析完成后,并再次生成sign,然后将生成的sign与传来的sign进行比对认证,认证成功则说明是微信发来的信息。然后从里面拿到result_code,如果result_code是“SUCCESS”说明支付成功。处理相应业务逻辑。

到这里基本上交互就完成了。

本文多处参考如下博客,写的很好,可以参考:第三方APP微信支付Java服务端构建步骤
最后就是提醒后来者在查android微信支付资料的时候最好过滤一下,最近一年的最好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值