Android支付——微信支付

最近项目中需要支付功能,一个是支付宝,一个是微信。由于之前都没有接触过,所以在网上看了许多文章与文档,逐步了解了这两个支付,项目中配置完成后,也顺便记录一下配置的环节,以便以后查看使用。
说实在的,支付宝的开发文档写的比微信的支付文档 好太多!!(不吹不黑)。
一、参考的文章 1.Android微信支付爬坑 (http://blog.csdn.net/ywl5320/article/details/50856922#reply)
这篇文章是由App客户端完全同微信服务器进行交互,自家的服务器不参与。官方不提倡,但是为了更好地明白原理,还是很好的
2.Android App支付系列(http://blog.csdn.net/xiong_it/article/details/51685033)
这篇文章是由自家的服务器同微信的服务器交互,生成与支付订单,返回给App客户端的,这样一来,比较安全,通知客户端所需要做的事情也少了。
3.微信支付官方文档(https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1)
自己看。。刚开始看的时候一头雾水,需要慢慢有耐心的看。。。
4.微信支付官方demo(https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=11_1)
集成微信支付的时候需要用到其中的类和方法。
二、支付流程(附上官方图) Android支付——微信支付
文章图片
微信支付流程
根据我的理解可以简单的分为以下几步
1.App客户端将简单的订单信息发送给自己的服务器(订单信息跟后台商量,需要传哪些参数);
2.App服务端调用统一下单Api同微信服务端进行交互,微信服务端生成与支付订单(带有prepay_id的订单信息)返回给App服务端。
3.服务端将预支付订单进行加签处理后返回给App客户端,由App客户端进行支付。
4.App客户端收到加签后的预支付订单后,调用微信SDK进行支付,支付结果在WxPayEntryActivity中显示。
三、支付中遇到的问题 1.提交订单时显示签名错误,原因可能是下单中的参数(body)中含有中文,需要进行iso8859-1编码。
2.微信服务端返回prepay_id后,进行支付时返回签名错误,原因可能是没有进行注册,wXapi.registerApp(WxConstans.APP_ID);
3.能够进行支付了,但是支付结果一直显示-1,支付失败,原因可能是
①App打包时必须用你申请支付时的签名文件
②我将微信的缓存清空了,可以进行一次支付,然后就不行了。难道要每次支付都要清空一次缓存么。。这样肯定不行,包名也要与申请支付时的包名一致。
4.官方demo中用到了HttpClient这个类,然而在AS中已经不适用这个类了,需要在module的build.gradle中添加
useLibrary 'org.apache.http.legacy'方可使用HttpClient。
开始进行支付了!!! 【Android支付——微信支付】我将所有的工作全部放在了客户端完成,讲道理加签什么的应该放在服务端的!!)
1.支付准备

  • APP_ID (App_id申请时候给的)
  • MCH_ID (商户号)
  • API_KEY (API秘钥)
  • APP_notify_url (异步通知服务器地址,跟自家服务端商量)
  • 微信的jar包(官方demo中有)
  • dom4j-full.jar (解析xml使用的jar包)
2.构造支付实体类
private String appid; //appid private String body; //商品描述 private String mch_id; //商户ID private String nonce_str; //随机字符串32位 private String notify_url; //微信通知后台支付结果url private String out_trade_no; //我们自己的订单号,由自家服务端返回的唯一订单号 private String spbill_create_ip; //客户端IP(我感觉不写也可以。。) private int total_fee; //总的支付金额(单位是分!!!) private String trade_type; //因为是移动应用 所以是APP(固定值) private String sign; //以上所有参数的MD5签名

3.注册微信!!!!!!!!!!
在你需要进行支付的Activiity中注册微信,一般在onCreate方法中
private IWXAPI wXapi = WXAPIFactory.createWXAPI(this, null); wXapi.registerApp(WxConstans.APP_ID);

4.构造订单的实体类
entity.setAppid(WxConstans.APP_ID); entity.setBody("test"); entity.setMch_id(WxConstans.MCH_ID); entity.setNonce_str(getNonceStr()); //单独一个方法获取32位随机数 entity.setNotify_url(WxConstans.APP_notify_url); entity.setOut_trade_no(getOutTradNo()); //单独方法获取唯一订单号 entity.setTotal_fee(1); //1分钱 entity.setTrade_type("APP"); //固定值 entity.setSpbill_create_ip("192.168.179.2");

//生成随机号,防重发 private String getNonceStr() { Random random = new Random(); return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes()); }

//生成订单号 private String getOutTradNo() { Random random = new Random(); return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());

}
5.参考下单API开始拼单
//构造商品参数集合,因为需要排序,所以用到了SortedMap SortedMap parameters = new TreeMap(); parameters.put("appid", entity.getAppid()); parameters.put("body", entity.getBody()); parameters.put("mch_id", entity.getMch_id()); parameters.put("nonce_str", entity.getNonce_str()); parameters.put("notify_url", entity.getNotify_url()); parameters.put("out_trade_no", entity.getOut_trade_no()); parameters.put("total_fee", entity.getTotal_fee()); parameters.put("trade_type", entity.getTrade_type()); parameters.put("spbill_create_ip", entity.getSpbill_create_ip()); //将订单信息签名后再传入 parameters.put("sign", WxUtils.createSign("UTF-8", parameters, WxConstans.API_KEY));

/** * 微信支付签名算法sign * @param characterEncoding 签名编码(UTF-8) * @param parameters 要签名的参数的集合 * @param key 商户自己设置的key */ public static String createSign(String characterEncoding, SortedMap parameters, String key){ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); //所有参与传参的参数按照accsii排序(升序) Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); Object v = entry.getValue(); if(null != v && !"".equals(v)&& !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key); System.out.println(sb.toString()); String sign = WxMd5.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); System.out.println(sign); return sign; }

public class WxMd5 { 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("utf-8"))); } 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"}; }

6.将订单信息拼成xml格式
由于微信服务器只接收xml格式的数据,需要将订单信息拼成xml格式
//3.因为统一下单接口需要以xml格式post发送给微信,所以我们先拼接xml格式的参数: StringBuilder xmlBuilder = new StringBuilder(); xmlBuilder.append(""); Set es = parameters.entrySet(); //所有参与传参的参数按照accsii排序(升序) Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v1 = entry.getValue(); xmlBuilder.append("<").append(k).append(">"); xmlBuilder.append(v1); xmlBuilder.append(""); } xmlBuilder.append(""); System.out.println(xmlBuilder.toString()); Log.d("tag", "拼装的xml信息" + xmlBuilder.toString()); try { //异步线程获取微信服务器返回的信息 new GetPrepayId(new String(xmlBuilder.toString().getBytes(), "ISO8859-1")).execute(); //这一步非常重要,不这样转换编码的话,传递中文就会报“签名错误”,这是很多人都会遇到的错误。} catch (Exception e) { e.printStackTrace(); }

//异步线程请求统一下单接口: public class GetPrepayId extends AsyncTask { String str; public GetPrepayId(String str) { this.str = str; } @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Object doInBackground(Object[] params) { //微信给的下单接口 String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //Util类为微信demo中自带的一个类,可以复制出来,自行使用 byte[] buf = Util.httpPost(url, str); String content = new String(buf); Log.d("tag", "微信服务器返回的xml为: " + content); //将xml转为map Map map = xmlToMap(content); Log.d("tag", "xml转为map: " + map.toString()); String nonceStr = getNonceStr(); //获取时间戳 String timeStamp = String.valueOf(getTimeStamp()); //开始准备付款 PayReq request = new PayReq(); request.appId = WxConstans.APP_ID; request.partnerId = WxConstans.MCH_ID; request.prepayId = map.get("prepay_id"); request.packageValue = "https://www.it610.com/article/Sign=WXPay"; request.nonceStr = nonceStr; request.timeStamp = timeStamp; //再签名 SortedMap parameters = new TreeMap(); parameters.put("appid", request.appId); parameters.put("partnerid",request.partnerId); parameters.put("prepayid", request.prepayId); parameters.put("package", request.packageValue); parameters.put("noncestr", request.nonceStr); parameters.put("timestamp", request.timeStamp); request.sign = WxUtils.createSign("UTF-8", parameters, WxConstans.API_KEY); Log.d("tag", "request.sign" + request.sign); //真开始支付了,支付结果在WXPayEntryActivity中显示 wXapi.sendReq(request); return content; }}

xml转map,需要用到dom4j-full.jar 包
//将xml转为map,相信都能看懂,不做注释。。。 public Map xmlToMap(String xmlstr) { Map map = new HashMap<>(); try { SAXReader reader = new SAXReader(); InputStream ins = new ByteArrayInputStream(xmlstr.getBytes("UTF-8")); Document doc = reader.read(ins); Element root = doc.getRootElement(); List list = root.elements(); for (Element e : list) { map.put(e.getName(), e.getText()); } ins.close(); } catch (Exception e) { e.printStackTrace(); } return map; }

获取时间戳
public static long getTimeStamp() {return System.currentTimeMillis() / 1000; }

需要注意
WXPayEntryActivity必须在当前包名.wxapi包下。
xml中声明

基本完事!!!!

    推荐阅读