开始写代码前,前往微信支付商户平台官网完成账号注册,API安全配置。
以下案例,已接入app支付为例
微信APP支付接入文档
1.统一下单
商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再在APP里面调起支付。
String random = ActionUtils.getRandom(16);//1.1生成随机数TreeMap<String,Object> paramsMap = new TreeMap<String,Object>();//参数map 按字典序排序paramsMap.put("appid", Constants.WXPayappid);//APPID ,接入时生成paramsMap.put("mch_id", Constants.WXPayMchid);//商户ID,接入时生成paramsMap.put("nonce_str", random);paramsMap.put("body", body);paramsMap.put("detail", detail);paramsMap.put("attach", attach);paramsMap.put("out_trade_no", orderId);paramsMap.put("total_fee", amount);paramsMap.put("spbill_create_ip", Constants.WXPayClientIP);paramsMap.put("notify_url", Constants.WXPayNotifyURL);//接受支付结果回调的地址,步骤2中会重点说这个paramsMap.put("trade_type", Constants.WXPayTradeType);//支付类型,“APP”String signValue = ActionUtils.WXPOSTBodyStrGenerate(paramsMap);//1.2计算签名if (!StringUtils.hasText(signValue) ) {msg = "微信下单签名失败";msgLog = "微信下单签名失败";logger.info(msgLog);}paramsMap.put("sign", signValue);JSONObject wxPayRes;try {wxPayRes = wechatService.httpswxPayRequest(paramsMap, Constants.WXPayUnifiedorder);//1.3请求微信生成订单,Constants.WXPayUnifiedorder:微信提供的下单地址} catch (Exception e) {e.printStackTrace();msg = "微信下单异常";msgLog ="微信下单异常"+e.toString();logger.info(msgLog);}
1.1生成随机数的工具类,仅供参考
public static String getRandom(int len) {String[] tempRandom = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",};Random a = new Random();int nextInt = a.nextInt(len);nextInt +=16;StringBuffer stringBuffer = new StringBuffer();for (int i = 0; i < nextInt; i++) {int nextInt2 = a.nextInt(36);stringBuffer.append(tempRandom[nextInt2]);}String currentTimeMillis = System.currentTimeMillis()+"";int length = stringBuffer.length();if(length<32) {if ((currentTimeMillis.length()-32)>0) {stringBuffer.append(currentTimeMillis.substring(currentTimeMillis.length()-32+length));}}String stringrandom = stringBuffer.toString();System.out.println("stringrandom:"+stringrandom);return stringrandom;}
1.2计算签名
public static String WXPOSTBodyStrGenerate(TreeMap<String, Object> paramsMap){StringBuffer stringBuffer = new StringBuffer();Iterator<Map.Entry<String, Object>> it = paramsMap.entrySet().iterator();while(it.hasNext()){Map.Entry<String, Object> entry = it.next();stringBuffer.append(entry.getKey()).append("=").append(entry.getValue()).append("&");}String requestParamString = stringBuffer.substring(0,stringBuffer.length()-1);//待加密的String部分 需要和key连接起来String encryptStr = requestParamString + "&key="+Constants.WXAPI_PlatForm_appSecret;//1.2.1 Constants.WXAPI_PlatForm_appSecret:接入时生成的32位密钥logger.info("wxpayencryptStr:"+encryptStr+";length:"+encryptStr.length());//计算签名--对待签名信息进行MD5计算String signValue = SignComputeUtil.StringMD5Compute(encryptStr).toUpperCase();//attention:32位大写logger.info("signValue: "+signValue);return signValue;}
1.3发起请求
public JSONObject httpswxPayRequest(TreeMap<String, Object> paramsMap, String requestUrl) {TreeMap<String,String> paramDataMap = new TreeMap<String,String>();//请求时携带参数的mapString postBodyStr = ActionUtils.Format_WXPay_Xml(paramsMap);//1.3.1生成xml请求体String WXPayReturnInfoStr = HttpRequestUtil.HttpDefaultExecute("POST", requestUrl, paramDataMap, postBodyStr);//这个地儿paramDataMap不起作用,1.3.2发出请求logger.info("WXPayReturnInfoStr:"+WXPayReturnInfoStr);ReceiveXmlPrePayEntity msgEntity = ActionUtils.getMsgEntity(WXPayReturnInfoStr);//1.3.3解析微信返回的xmllogger.info(msgEntity.toString());return JSONObject.parseObject(JSONObject.toJSON(msgEntity).toString());}
1.3.1生成请求体
public static String Format_WXPay_Xml(TreeMap<String, Object> paramsMap){StringBuffer sb = new StringBuffer();sb.append("<xml><appid>");sb.append(paramsMap.get("appid"));sb.append("</appid><attach>");sb.append(paramsMap.get("attach"));sb.append("</attach><body>");sb.append(paramsMap.get("body"));sb.append("</body><mch_id>");sb.append(paramsMap.get("mch_id"));sb.append("</mch_id><nonce_str>");sb.append(paramsMap.get("nonce_str"));sb.append("</nonce_str><detail>");sb.append(paramsMap.get("detail"));sb.append("</detail><notify_url>");sb.append(paramsMap.get("notify_url"));sb.append("</notify_url><out_trade_no>");sb.append(paramsMap.get("out_trade_no"));sb.append("</out_trade_no><spbill_create_ip>");sb.append(paramsMap.get("spbill_create_ip"));sb.append("</spbill_create_ip><total_fee>");sb.append(paramsMap.get("total_fee"));sb.append("</total_fee><trade_type>");sb.append(paramsMap.get("trade_type"));sb.append("</trade_type><sign>");sb.append(paramsMap.get("sign"));sb.append("</sign></xml>");return sb.toString();}
1.3.2发起网络请求
1.3.3解析微信返回的xml
public static ReceiveXmlPrePayEntity getMsgEntity(String strXml){ReceiveXmlPrePayEntity msg = null;if(strXml.length() <= 0 || strXml == null){return null;}try {Document document = DocumentHelper.parseText(strXml);Element root = document.getRootElement();Iterator<?> iter = root.elementIterator();//利用反射机制,调用set方法//获取该实体的元类型Class<?> c = ReceiveXmlPrePayEntity.class;msg = (ReceiveXmlPrePayEntity)c.newInstance();//创建这个实体的对象while(iter.hasNext()){//对xml文件进行解析Element ele = (Element)iter.next();Field field = c.getDeclaredField(ele.getName());//获取set方法,field.getType())获取它的参数数据类型String name = ele.getName().substring(0,1).toUpperCase()+ele.getName().substring(1);System.out.println("name:"+name);Method method = c.getDeclaredMethod("set"+name,field.getType());//调用set方法method.invoke(msg,ele.getText());}} catch (DocumentException e) {e.printStackTrace();}catch (IllegalAccessException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (SecurityException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}return msg;}
public class ReceiveXmlPrePayEntity {
/* //设备号private String device_info;//业务结果private String result_code;//错误代码private String err_code;//错误代码描述private String err_code_des;*///业务结果private String result_code;//返回状态码private String return_code;//返回信息private String return_msg;//应用APPIDprivate String appid;//商户号private String mch_id;//随机字符串private String nonce_str;//签名private String sign;//交易类型private String trade_type;//预支付交易会话标识private String prepay_id;
//set and get。。。。。。。。。。
}
到这里已经完成了,下单操作,将解析xml的到entity返回客户端,客户端即可发起支付。
客户端支付
2.接受微信支付结果回调通知
这个接口是微信调起,是在下单接口中配置的。这一步是重点,因为这是钱来钱往。所以下面的每一步,都必要。
支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
在通知一直不成功的情况下,微信总共会发起多次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。
2.1接受通知数据流
ReceiveWXPayResourceAction(HttpServletRequest req,HttpServletResponse res) {logger.info("WXPayResBack");//1.接受数据流logger.info("1.接受数据流");ServletInputStream ips = null;String xmlString = "";try {ips = req.getInputStream();xmlString = HttpRequestUtil.inputToStr(ips);//2.1.1接受数据流logger.info("xmlString:"+xmlString);} catch (Exception e) {e.printStackTrace();String return_code ="FAIL";//SUCCESS/FAIL String return_msg = "未知错误";logger.error("WXPayResBack: "+return_msg+e.toString());String formatXmlAnswer_wx = ActionUtils.formatXmlAnswer_wx(return_code, return_msg);//2.1.2返回微信结果return formatXmlAnswer_wx;}finally {if (ips!=null) {try {ips.close();ips = null;} catch (IOException e) {e.printStackTrace();}}}
}
2.1.1接受数据流
Roland大佬写的好多工具类,很好用,记住一定是“utf-8”接受。
public static String inputToStr(InputStream is){String result = ""; if (is != null) { StringBuilder builder=new StringBuilder();BufferedReader reader=null;try {reader = new BufferedReader(new InputStreamReader(is,DEFAULT_CHARSET));String line;while((line=reader.readLine())!=null){builder.append(line+"\n");}} catch (Exception e) {e.printStackTrace();}finally{try {reader.close();} catch (IOException e) {e.printStackTrace();}}return builder.toString(); }return result; }
2.1.2返回微信数据,通知成功、失败
public static String formatXmlAnswer_wx(String return_code, String return_msg){StringBuffer sb = new StringBuffer();sb.append("<xml><return_code><![CDATA[");sb.append(return_code);sb.append("]]></return_code><return_msg><![CDATA[");sb.append(return_msg);sb.append("]]></return_msg></xml>");return sb.toString();}
2.2必要参数校验,这几个笔者认为必要,其他没有具体校验
WXPayReceiveWXResultEntity wxResultEntity = ActionUtils.getwxPayResultMsgEntity(xmlString);//解析xml,在步骤1中已经贴过代码,不再赘述if (!StringUtils.hasText(wxResultEntity.getResult_code()) || !StringUtils.hasText(wxResultEntity.getReturn_code()) || !(wxResultEntity.getResult_code()).equals("SUCCESS")|| !(wxResultEntity.getReturn_code()).equals("SUCCESS")|| !StringUtils.hasText(wxResultEntity.getTransaction_id()) ) {String return_code ="FAIL";//SUCCESS/FAIL String return_msg = "参数格式校验错误";logger.error("WXPayResBack: "+return_msg);String formatXmlAnswer_wx = ActionUtils.formatXmlAnswer_wx(return_code, return_msg);return formatXmlAnswer_wx;//如果校验失败,通知微信失败了}
2.3校验签名
必须做,没得商量
注意一个小点sign不参与签名计算
logger.info("3.验证签名");TreeMap<String, Object> paramsMap = new TreeMap<>();paramsMap.put("appid", Constants.WXPayappid);paramsMap.put("attach", wxResultEntity.getAttach());paramsMap.put("bank_type", wxResultEntity.getBank_type());paramsMap.put("cash_fee", wxResultEntity.getCash_fee());paramsMap.put("fee_type", wxResultEntity.getFee_type());paramsMap.put("is_subscribe", wxResultEntity.getIs_subscribe());paramsMap.put("mch_id", wxResultEntity.getMch_id());paramsMap.put("nonce_str", wxResultEntity.getNonce_str());paramsMap.put("openid", wxResultEntity.getOpenid());paramsMap.put("out_trade_no", wxResultEntity.getOut_trade_no());paramsMap.put("result_code", wxResultEntity.getResult_code());paramsMap.put("return_code", wxResultEntity.getReturn_code());paramsMap.put("time_end", wxResultEntity.getTime_end());paramsMap.put("total_fee", wxResultEntity.getTotal_fee());paramsMap.put("trade_type", wxResultEntity.getTrade_type());paramsMap.put("transaction_id", wxResultEntity.getTransaction_id());String mSign = ActionUtils.WXPOSTBodyStrGenerate(paramsMap);//步骤1中,已贴代码,不再赘述System.out.println("mSign:"+mSign);System.out.println("wxResultEntitySign:"+wxResultEntity.getSign());if (!StringUtils.hasText(wxResultEntity.getSign()) || !StringUtils.hasText(mSign) || !(wxResultEntity.getSign()).equals(mSign)) {String return_code ="FAIL";//SUCCESS/FAIL String return_msg = "签名失败";logger.error("WXPayResBack: "+return_msg);String formatXmlAnswer_wx = ActionUtils.formatXmlAnswer_wx(return_code, return_msg);return formatXmlAnswer_wx;//如果自己算的签名和微信返回的不一致,通知微信失败}
到这里接受通知结果已经完成了,根据自己业务完成对应逻辑。
2.4通知微信成功
String return_code ="SUCCESS";//SUCCESS/FAIL
String return_msg = "OK";String formatXmlAnswer_wx = ActionUtils.formatXmlAnswer_wx(return_code, return_msg);return formatXmlAnswer_wx;
3.主动查询微信支付结果
万一步骤2不好用了呢,或者客户端业务的某些需求,还是掌握主动权比较好。
String random = ActionUtils.getRandom(16);//生成随机数,已贴过代码,不赘述TreeMap<String,Object> paramsMap = new TreeMap<String,Object>();//参数map 按字典序排序paramsMap.put("appid", Constants.WXPayappid);paramsMap.put("mch_id", Constants.WXPayMchid);paramsMap.put("nonce_str", random);paramsMap.put("out_trade_no", out_trade_no);//此处可以是商户生成的orderID,也可以是微信支付成功的orderidString signValue = ActionUtils.WXPOSTBodyStrGenerate(paramsMap);//生成请求中的body体paramsMap.put("sign", signValue);InitiatQueryWXPayResultEntity queryWXPayResultEntity = wechatService.httpswxPayQueryRequest(paramsMap, Constants.WXPayOrderquery);//发起请求微信,下单步骤一帖代码,Constants.WXPayOrderquery:请求地址,详见官网
3.1结果参数校验
笔者认为几个必要校验参数,供参考
if (!StringUtils.hasText(queryWXPayResultEntity.getResult_code()) || !StringUtils.hasText(queryWXPayResultEntity.getReturn_code()) || !StringUtils.hasText(queryWXPayResultEntity.getTrade_state_desc())|| !(queryWXPayResultEntity.getResult_code()).equals("SUCCESS")|| !(queryWXPayResultEntity.getReturn_code()).equals("SUCCESS")|| !(queryWXPayResultEntity.getTrade_state_desc()).equals("支付成功")|| !StringUtils.hasText(queryWXPayResultEntity.getTransaction_id()) ) {//业务逻辑。。。}
3.2验证签名
同下单接口
3.3完事,收工。
支付是相对慎重的逻辑,上述流程仅供君参考。