微信支付接入总结
Android Studio,集成微信支付通道
一、支付说明:
1.引用微信支付官方交互时序图,统一下单API、支付结果通知API和查询订单API等都涉及签名过程,调用都必须在商户服务器端完成。
商户系统和微信支付系统主要交互说明:
步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。
步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式为Sign=WXPay
二、APP端集成
步骤:
1:集成环境(已改用gradle形式,发布到jcenter(http://jcenter.bintray.com/,在build.gradle文件中,添加如下依赖即可)
compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
2:清单文件
(1)权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
(2)回调(要在自己包名目录下新建目录wxapi,然后新建WXPayEntryActivity,下面会贴出实现类,注意这个类和微信登录回调的是不一样的,红色的部分)
<activity android:name=".wxapi.WXPayEntryActivity" android:exported="true" android:launchMode="singleTop" android:screenOrientation="portrait" />
3:微信注册(在调用支付之前一定要进行注册)
IWXAPI mWxApi = WXAPIFactory.createWXAPI(this, ShareKey.WX_ID, false); // 将该app注册到微信 mWxApi.registerApp(ShareKey.WX_ID);4:调起支付
/** * 微信支付 * @param infodata * @param checkmannery */ public void doWeChatPayment(PaymentPlanAggBean infodata, String checkmannery, String checkdueperiod) {
//首先在调用之前,需要先在代码中进行微信API注册,并将该app注册到微信 api = WXAPIFactory.createWXAPI(activity, AppConstant.WX_APP_ID, false); //查询后台得到对应用户的缴费计划 GenWXOrderInfoRequest orderRequest = new GenWXOrderInfoRequest(activity, new RequestListener() { @Override public void successBack(Object object) { UIUtil.dismissProgressDialog(); if (null == object) { showErrorMsg("警告", "获取支付订单信息错误"); return; } else { WeChatResultBean wxbean = (WeChatResultBean) object; corderid = wxbean.getCorderid(); try { PayReq req = new PayReq(); req.appId = AppConstant.WX_APP_ID; req.partnerId = wxbean.getPartnerid();//商户号 req.prepayId = wxbean.getPrepayid();//预支付交易会话ID req.nonceStr = wxbean.getNoncestr();//随机字符串随机字符串,不长于32位 req.timeStamp = wxbean.getTimestamp();//时间戳 req.packageValue = wxbean.getPackagevalue();//扩展字段暂填写固定值Sign=WXPay req.sign = wxbean.getSign(); req.extData = "app data"; // optional // 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信 boolean flag = api.sendReq(req); Log.e("PAY_GET", "异常:" +flag); } catch (Exception e) { Log.e("PAY_GET", "异常:" + e.getMessage()); } } } @Override public void failBack(Object object) { UIUtil.dismissProgressDialog(); } }); //根据界面信息生成预订单数据对象 String strjson = chgPlanToOrder(infodata, checkmannery, checkdueperiod); //支付前需要 orderRequest.setJsondata(strjson); orderRequest.startRequest(); }5:支付回调
/** * 微信支付回调页面 */ public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler { private static final String TAG = "MicroMsg.SDKSample.WXPayEntryActivity"; private IWXAPI api; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pay_result); //回调的关键,不写的话可能回调失败 api = WXAPIFactory.createWXAPI(this, AppConstant.WX_APP_ID); api.handleIntent(getIntent(), this); finish(); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); setIntent(intent); api.handleIntent(intent, this); } @Override public void onReq(BaseReq req) { } @SuppressLint("LongLogTag") @Override public void onResp(BaseResp resp) { Log.d(TAG, "onPayFinish, errCode = " + resp.errCode); if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) { int code = resp.errCode; switch (code) { case 0://支付成功后的界面 break; case -1: UIUtils.showToast(getString(R.string.pay_result_callback_msg, String.valueOf(resp.errCode)) + "签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、您的微信账号异常等。"); break; case -2://用户取消支付后的界面 break; } AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.app_tip); builder.setMessage(getString(R.string.pay_result_callback_msg, String.valueOf(resp.errCode))); builder.show(); } //微信支付后续操作,失败,成功,取消 } }三、后台集成
微信支付功能除了前台的微信调用还需要后台相关代码功能的支持,光有APP端功能是跑不起来的。
1、统一下单接口集成。
微信支付需要手动调用微信提供的统一下单接口生成支付预订单,并根据返回的预订单id标识进行后续的操作,关键工具类代码:
package com.tenpay.util; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import com.bean.wechatBean.WeChatPreOrderBean; import nc.util.iface.pub.XStreamUtil; public class PayCommonUtil { //微信参数配置 public static String APPID = "wx8dd63af172e40469"; //注意这个key必须从https://pay.weixin.qq.com/index.php/core/cert/api_cert,重新生成,一次生成后不宜修改 public static String API_KEY = "6E0BB9100C9AF7384EB0280EB56F84AE"; //商户号微信注册的时候会发送到账户 public static String MCH_ID = "1494998152"; @SuppressWarnings("unchecked") public static Map<String, String> weixinPrePay(String orderno,String totalAmount, String description, InetAddress request) { SortedMap<String, Object> parameterMap = new TreeMap<String, Object>(); parameterMap.put("appid", PayCommonUtil.APPID);//应用ID parameterMap.put("mch_id", PayCommonUtil.MCH_ID);//商户号 parameterMap.put("nonce_str", PayCommonUtil.getRandomString(32));//随机字符串 parameterMap.put("body", description);//商品描述 parameterMap.put("out_trade_no", orderno); //商户订单号 parameterMap.put("fee_type", "CNY");//币种 parameterMap.put("total_fee", totalAmount); //总金额 parameterMap.put("spbill_create_ip", request.getHostAddress()); //终端IP parameterMap.put("notify_url", "http://xxx.com"); //通知地址 parameterMap.put("trade_type", "APP");//交易类型 String sign = PayCommonUtil.createSign("UTF-8", parameterMap,API_KEY); parameterMap.put("sign", sign); //签名 String requestXML = PayCommonUtil.getRequestXml(parameterMap); System.out.println(requestXML); String result = PayCommonUtil.httpsRequest( "https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", requestXML); System.out.println(result); Map<String, String> map = null; try { map = PayCommonUtil.doXMLParse(result); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return map; } /** * 随机字符串生成 * @param length * @return */ public static String getRandomString(int length) { //length表示生成字符串的长度 String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 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组装 * @param parameters * @return */ public static String getRequestXml(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) { sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">"); } else { sb.append("<" + key + ">" + value + "</" + key + ">"); } } sb.append("</xml>"); return sb.toString(); } /** * 生成签名 * @param characterEncoding * @param parameters * @param apiKey * @return */ public static String createSign(String characterEncoding, SortedMap<String, Object> parameters, String apiKey) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); 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=" + apiKey); System.out.println(sb.toString()); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * 验证回调签名 * @param packageParams * @param key * @param charset * @return */ public static boolean isTenpaySign(Map<String, String> map, String apiKey) throws UnsupportedEncodingException { String charset = "utf-8"; String signFromAPIResponse = map.get("sign"); if (signFromAPIResponse == null || signFromAPIResponse.equals("")) { System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!"); return false; } System.out.println("服务器回包里面的签名是:" + signFromAPIResponse); //过滤空 设置 TreeMap SortedMap<String, String> packageParams = new TreeMap<>(); for (String parameter : map.keySet()) { String parameterValue = map.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } StringBuffer sb = new StringBuffer(); Set es = packageParams.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 (!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + apiKey); //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较 //算出签名 String resultSign = ""; String tobesign = sb.toString(); if (null == charset || "".equals(charset)) { resultSign = MD5Util.MD5Encode(tobesign, charset).toUpperCase(); } else { resultSign = MD5Util.MD5Encode(tobesign, charset).toUpperCase(); } String tenpaySign = packageParams.get("sign").toUpperCase(); return tenpaySign.equals(resultSign); } /** * 请求方法 * @param requestUrl * @param requestMethod * @param outputStr * @return */ public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 设置请求方式(GET/POST) conn.setRequestMethod(requestMethod); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 当outputStr不为null时向输出流写数据 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意编码格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 从输入流读取返回内容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 释放资源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); return buffer.toString(); } catch (ConnectException ce) { System.out.println("连接超时:{}" + ce); } catch (Exception e) { System.out.println("https请求异常:{}" + e); } return null; } // //退款的请求方法 // public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception { // KeyStore keyStore = KeyStore.getInstance("PKCS12"); // StringBuilder res = new StringBuilder(""); // FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12")); // try { // keyStore.load(instream, "".toCharArray()); // } finally { // instream.close(); // } // // // Trust own CA and all self-signed certs // SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, "1313329201".toCharArray()).build(); // // Allow TLSv1 protocol only // SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); // CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); // try { // // HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund"); // httpost.addHeader("Connection", "keep-alive"); // httpost.addHeader("Accept", "*/*"); // httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); // httpost.addHeader("Host", "api.mch.weixin.qq.com"); // httpost.addHeader("X-Requested-With", "XMLHttpRequest"); // httpost.addHeader("Cache-Control", "max-age=0"); // httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); // StringEntity entity2 = new StringEntity(outputStr, Consts.UTF_8); // httpost.setEntity(entity2); // System.out.println("executing request" + httpost.getRequestLine()); // // CloseableHttpResponse response = httpclient.execute(httpost); // // try { // HttpEntity entity = response.getEntity(); // // System.out.println("----------------------------------------"); // System.out.println(response.getStatusLine()); // if (entity != null) { // System.out.println("Response content length: " + entity.getContentLength()); // BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); // String text = ""; // res.append(text); // while ((text = bufferedReader.readLine()) != null) { // res.append(text); // System.out.println(text); // } // // } // EntityUtils.consume(entity); // } finally { // response.close(); // } // } finally { // httpclient.close(); // } // return res.toString(); // // } /** * xml解析 * @param strxml * @return * @throws IOException * @throws JDOMException */ public static Map doXMLParse(String strxml) throws IOException, JDOMException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if (null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } System.out.println(k+"-"+v); m.put(k, v); } //关闭流 in.close(); return m; } public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } }
2、根据下单返回单据,生成支付命令
/** * 创建微信预订单信息,并返回 * * @param aggvo:APP端返回的预订单数据VO * @return * @throws UnknownHostException */ private WeChatResultBean getOrderInfo(OrderDetailAggBean aggvo) throws UnknownHostException { OrderDetailHeaderBean header = aggvo.getHeader(); InetAddress address = InetAddress.getLocalHost(); String orderno = getOutTradeNo(); String ordername = "党费缴纳";// 订单名称-固定党费缴纳 String orderinfo = ordername + "-支付人:" + header.getUsername();// 订单详情 //微信接口要求金额转换成分进行传递 double dorderprice =Double.parseDouble(header.getNpaymentprice())*100;// 订单支付金额 String orderprice = (int)dorderprice+""; //调用微信生成预订单:订单号,价格,网络端口号 Map<String,String> maps =PayCommonUtil.weixinPrePay(orderno, orderprice, orderinfo, address ); //将返回值封装成json对象 WeChatResultBean bean = new WeChatResultBean(); bean.setReturn_code(maps.get("return_code"));// 返回状态码SUCCESS/FAIL bean.setReturn_msg(maps.get("return_msg"));// 返回信息,如非空,为错误原因 bean.setAppid(maps.get("appid"));// 调用接口提交的应用ID bean.setPartnerid(maps.get("mch_id"));// 调用接口提交的商户号 bean.setNoncestr(maps.get("nonce_str"));// 微信返回的随机字符串 bean.setPreordersign(maps.get("sign"));// 微信返回的签名 bean.setResult_code(maps.get("result_code"));// 业务返回结果 bean.setPrepayid(maps.get("prepay_id"));// 预支付交易会话标识 bean.setTrade_type(maps.get("trade_type"));// 交易类型:调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP bean.setErr_code(maps.get("err_code"));// 错误代码 bean.setErr_code_des(maps.get("err_code_des"));// 错误代码描述 bean.setTimestamp(System.currentTimeMillis()+"");//时间戳(毫秒) //重新生成微信APP支付使用的签名 //(微信支付用的签名跟预订单不是一回事,需要重新按下面的格式进行生成,否则在前台支付会报-1错误) SortedMap<String,Object> signmaps = new TreeMap<String, Object>(); signmaps.put("appid", bean.getAppid()); signmaps.put("noncestr", bean.getNoncestr()); signmaps.put("package", bean.getPackagevalue()); signmaps.put("partnerid", bean.getPartnerid()); signmaps.put("prepayid", bean.getPrepayid()); signmaps.put("timestamp",bean.getTimestamp() ); String sign = PayCommonUtil.createSign("UTF-8", signmaps,PayCommonUtil.API_KEY); //签名 bean.setSign(sign); return bean; }四、支付错误分析及解决,参照下列文章