遵循:BY-SA
署名-相同方式共享 4.0协议
作者:谭东
时间:2016年10月28日
环境:Windows 7
Android版微信支付官方文档和Demo问题很多,官方也没有及时更新和细化开发集成文档。
这里分享我集成Android客户端微信支付的思路和部分代码。希望对大家有帮助。
遇到的问题无非以下几种:
1、提示签名不对;
2、打包签名后的APK无法调起微信支付客户端,直接返回回调页;
3、不支持中文的Body;
4、支付的钱倍数不对,因为微信的单位是分;
... ...
首先,本文针对的是最新版微信Android支付SDK3.1.1写的,大家可以放心使用。
首先,把WXPayEntryActivity.java复制到我们的包名下的.wxapi目录下,要一致。
<activity android:name=".wxapi.WXPayEntryActivity" android:exported="true" android:launchMode="singleTop" android:screenOrientation="portrait" />WXPayEntryActivity.java官方Demo里有,我这里也复制一份我的,仅供参考。
public class WXPayEntryActivity extends BaseActivity implements IWXAPIEventHandler { private IWXAPI api; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); api = WXAPIFactory.createWXAPI(this, Conf.APP_ID); api.handleIntent(getIntent(), this); getSupportActionBar().setIcon(R.drawable.logo_gray); getSupportActionBar().setTitle("购买积分"); getSupportActionBar().setDisplayHomeAsUpEnabled(true); Drawable dw = getResources().getDrawable(R.drawable.color_bg); getSupportActionBar().setBackgroundDrawable(dw); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { this.finish(); } return super.onOptionsItemSelected(item); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); api.handleIntent(intent, this); } @Override public void onReq(BaseReq baseReq) { } @Override public void onResp(BaseResp baseResp) { if (baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) { Log.d("info", "onPayFinish,errCode=" + baseResp.errCode); if (baseResp.errCode == 0) { ToastUtil.showToast(this, "支付成功"); Intent intent = new Intent("ACTION_PAY"); sendBroadcast(intent); this.finish(); } else if (baseResp.errCode == -1) { ToastUtil.showToast(this, "配置错误"); this.finish(); } else if (baseResp.errCode == -2) { ToastUtil.showToast(this, "用户取消"); this.finish(); } } else { ToastUtil.showToast(this, baseResp.errStr); } } }
这里说一下Android微信支付流程,这里本地客户端都处理了所有的流程。
客户端把所有需要的信息拼接为XML后,统一下单,发送给微信服务器API请求,获取订单id,也就是prepayId。获取到这个prepayId后,我们再把必要的数据字段sign签名MD5后,调起微信支付客户端就可以了。比支付宝麻烦很多。
1、在程序的启动页,例如欢迎页就可以注册微信API的ID了。
IWXAPI msgApi = WXAPIFactory.createWXAPI(this, Conf.APP_ID); msgApi.registerApp(Conf.APP_ID);
2、接下来处理微信POST请求的必要参数的拼接和加密等处理。写PayActivity.java
统一下单获取prepayId的接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
微信文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
把必填的参数处理下:
nonce_str:随机字符串,不长于32位。
out_trade_no:商户系统内部的订单号,32个字符内、可包含字母。
spbill_create_ip:用户端实际ip,16位,可写固定值。
notify_url:暂时写成固定的,http://www.weixin.qq.com/wxpay/pay.php。
最麻烦的,sign:32位签名,参照签名生成算法。
以上这些算法我都会写成工具类,供大家参考。
签名算法:
附上整个支付代码:
PayActivity.java里关于支付的代码:
private IWXAPI api; private String stringA; private String noneString; private String out_trade_no; private String sign; private String prepayId; private String ip; private String body = "商品-积分充值"; private static final String order_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//POST请求统一下单接口地址 private String notify_url = "http://www.weixin.qq.com/wxpay/pay.php"; private String entity;//XML形式的post请求实体 private void payWeixin(int money) { api = WXAPIFactory.createWXAPI(this, Conf.APP_ID); try { body = new String(body.getBytes(), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } noneString = Utils.createRandomString(true, 32);//32位随机字符串 out_trade_no = Utils.createRandomString(true, 20) + Utils.timeStamp();//30位内部随机订单号 ip = "123.123.123.123"; stringA = "appid=" + Conf.APP_ID + "&body=" + body + "&mch_id=" + Conf.MCH_ID + "&nonce_str=" + noneString + "¬ify_url=" + notify_url + "&out_trade_no=" + out_trade_no + "&spbill_create_ip=" + ip + "&" + "total_fee=" + money + "&trade_type=APP"; String stringSignTemp = stringA + "&key=" + Conf.KEY; sign = Utils.getMD5(stringSignTemp).toUpperCase(Locale.getDefault()); entity = "<xml><appid>" + Conf.APP_ID + "</appid><mch_id>" + Conf.MCH_ID + "</mch_id><nonce_str>" + noneString + "</nonce_str><sign>" + sign + "</sign><body>" + body + "</body><out_trade_no>" + out_trade_no + "</out_trade_no><total_fee>" + money + "</total_fee><spbill_create_ip>" + ip + "</spbill_create_ip><notify_url>" + notify_url + "</notify_url><trade_type>APP</trade_type></xml>"; try { entity = new String(entity.getBytes(), "ISO8859-1");//想要支持中文的Boby,那就要把XML转码为ISO8859-1即可 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } new Thread(new Runnable() { @Override public void run() { try { byte[] buf = Utils.httpPost(order_url, entity); Message message = new Message(); message.what = 0; message.obj = buf; handler.sendMessage(message); } catch (Exception e) { Log.e("info", "异常:" + e.getMessage()); } } }).start(); } Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: byte[] buf = (byte[]) msg.obj; if (buf != null && buf.length > 0) { String content = new String(buf); OrderResult orderResult = Tools.parseXml(new ByteArrayInputStream(content.getBytes())); if (!TextUtils.equals(orderResult.getReturnCode(), "SUCCESS")) { Toast.makeText(PayActivity.this, orderResult.getReturnMsg(), Toast.LENGTH_SHORT).show(); return; } if (!TextUtils.equals(orderResult.getResultCode(), "SUCCESS")) { Toast.makeText(PayActivity.this, orderResult.getErrorDesc(), Toast.LENGTH_SHORT).show(); return; } PayReq req = new PayReq(); req.appId = Conf.APP_ID; req.partnerId = Conf.MCH_ID; req.prepayId = orderResult.getPrepayId(); req.packageValue = "Sign=WXPay"; req.nonceStr = noneString; String timeStamp = Utils.timeStamp(); req.timeStamp = timeStamp; req.sign = Utils.getMD5("appid=" + Conf.APP_ID + "&noncestr=" + noneString + "&package=Sign=WXPay" + "&partnerid=" + Conf.MCH_ID + "&prepayid=" + orderResult.getPrepayId() + "×tamp=" + timeStamp + "&key=" + Conf.KEY).toUpperCase(Locale.getDefault()); api.sendReq(req); } else { Log.d("PAY_GET", "服务器请求错误"); Toast.makeText(PayActivity.this, "服务器请求错误", Toast.LENGTH_SHORT).show(); } break; } } };
public class Utils { /** * 产生随机字符串 * * @param numberFlag 是否允许有字母 * @param length 随机字符串长度 * @return 随机字符串 */ public static String createRandomString(boolean numberFlag, int length) { String retStr = ""; String strTable = numberFlag ? "1234567890" : "1234567890abcdefghijkmnpqrstuvwxyz"; int len = strTable.length(); boolean bDone = true; do { retStr = ""; int count = 0; for (int i = 0; i < length; i++) { double dblR = Math.random() * len; int intR = (int) Math.floor(dblR); char c = strTable.charAt(intR); if (('0' <= c) && (c <= '9')) { count++; } retStr += strTable.charAt(intR); } if (count >= 2) { bDone = false; } } while (bDone); return retStr; } /** * 获取MD5加密后的字符串 * * @param val 待加密字符串 * @return 加密后字符串 */ public static String getMD5(String val) { byte[] m = new byte[0]; try { MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(val.getBytes()); m = md5.digest(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return getString(m); } private static String getString(byte[] b) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < b.length; i++) { sb.append(b[i]); } return sb.toString(); } /** * 获取10位长度的时间戳 * * @return 10位时间戳 */ public static String timeStamp() { String time = String.valueOf(System.currentTimeMillis()); return time.substring(0, 10); } public static byte[] httpGet(final String url) { if (url == null || url.length() == 0) { return null; } HttpClient httpClient = getNewHttpClient(); HttpGet httpGet = new HttpGet(url); try { HttpResponse resp = httpClient.execute(httpGet); if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { return null; } return EntityUtils.toByteArray(resp.getEntity()); } catch (Exception e) { e.printStackTrace(); return null; } } public static byte[] httpPost(String url, String entity) { if (url == null || url.length() == 0) { return null; } HttpClient httpClient = getNewHttpClient(); HttpPost httpPost = new HttpPost(url); try { httpPost.setEntity(new StringEntity(entity)); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Content-type", "application/json"); HttpResponse resp = httpClient.execute(httpPost); if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { return null; } return EntityUtils.toByteArray(resp.getEntity()); } catch (Exception e) { e.printStackTrace(); return null; } } private static HttpClient getNewHttpClient() { try { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore); sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); HttpParams params = new BasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", sf, 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); return new DefaultHttpClient(ccm, params); } catch (Exception e) { return new DefaultHttpClient(); } } public static OrderResult parseXml(InputStream is) { XmlPullParser parser = Xml.newPullParser(); OrderResult orderResult = null; try { parser.setInput(is, "UTF-8"); int type = parser.getEventType(); while (type != XmlPullParser.END_DOCUMENT) { switch (type) { case XmlPullParser.START_DOCUMENT: break; case XmlPullParser.START_TAG: if (parser.getName().equals("xml")) { orderResult = new OrderResult(); } else if (parser.getName().equals("return_code")) { orderResult.setReturnCode(parser.nextText()); } else if (parser.getName().equals("return_msg")) { orderResult.setReturnMsg(parser.nextText()); } else if (parser.getName().equals("result_code")) { orderResult.setResultCode(parser.nextText()); } else if (parser.getName().equals("err_code_des")) { orderResult.setErrorDesc(parser.nextText()); } else if (parser.getName().equals("prepay_id")) { orderResult.setPrepayId(parser.nextText()); } else if (parser.getName().equals("sign")) { orderResult.setSign(parser.nextText()); } break; case XmlPullParser.END_TAG: break; } type = parser.next(); } } catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return orderResult; } }