首先准备几个工具类:HttpRequest请求工具类,MD5加密工具类,随机数生成工具类,签名生成工具类,xml格式转换类
package net.tiantianup.wap.utils.weixinpay;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.config.RequestConfig;import org.apache.http.client.methods.HttpPost;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.ssl.SSLContexts;import org.apache.http.util.EntityUtils;import .ssl.SSLContext;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.security.*;import java.security.cert.CertificateException;/*** Created by LV on /4/11 0011.* Email:LvLoveYuForever@*/public class HttpRequest {/*** 请求工具类(使用httpclient)* @param requestUrl 请求的API的URL* @param postData 需要发送的内容* @param certLocalPath PKCS12证书地址* @param certPassword 证书密码* @return* @throws CertificateException* @throws NoSuchAlgorithmException* @throws KeyStoreException* @throws IOException* @throws KeyManagementException* @throws UnrecoverableKeyException*/public static String httpRequest(String requestUrl, String postData, String certLocalPath, String certPassword) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException, UnrecoverableKeyException {KeyStore keyStore = KeyStore.getInstance("PKCS12");FileInputStream instream = new FileInputStream(new File(certLocalPath));//加载本地的证书进行https加密传输try {keyStore.load(instream, certPassword.toCharArray());//设置证书密码} catch (CertificateException e) {e.printStackTrace();} finally {instream.close();}// Trust own CA and all self-signed certsSSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, certPassword.toCharArray()).build();// Allow TLSv1 protocol onlySSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null,SSLConnectionSocketFactory.getDefaultHostnameVerifier());CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();HttpPost httpPost=new HttpPost(requestUrl);//得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别StringEntity postEntity = new StringEntity(postData, "UTF-8");httpPost.addHeader("Content-Type", "text/xml");httpPost.setEntity(postEntity);//设置请求器的配置httpPost.setConfig(RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(3000).build());HttpResponse response=httpclient.execute(httpPost);HttpEntity entity=response.getEntity();String result= EntityUtils.toString(entity);return result;}}
package net.tiantianup.wap.utils.weixinpay;import java.security.MessageDigest;/*** MD5加密工具*/public class MD5 {private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7","8", "9", "a", "b", "c", "d", "e", "f"};/*** 转换字节数组为16进制字串* @param b 字节数组* @return 16进制字串*/public static String byteArrayToHexString(byte[] b) {StringBuilder resultSb = new StringBuilder();for (byte aB : b) {resultSb.append(byteToHexString(aB));}return resultSb.toString();}/*** 转换byte到16进制* @param b 要转换的byte* @return 16进制格式*/private static String byteToHexString(byte b) {int n = b;if (n < 0) {n = 256 + n;}int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}/*** MD5编码* @param origin 原始字符串* @return 经过MD5加密之后的结果*/public static String MD5Encode(String origin) {String resultString = null;try {resultString = origin;MessageDigest md = MessageDigest.getInstance("MD5");resultString = byteArrayToHexString(md.digest(resultString.getBytes()));} catch (Exception e) {e.printStackTrace();}return resultString;}}
package net.tiantianup.wap.utils.weixinpay;import java.util.Random;/*** 随机数自动生成器*/public class RandomStringGenerator {/*** 获取一定长度的随机字符串* @param length 指定字符串长度* @return 一定长度的字符串*/public static String getRandomStringByLength(int length) {String base = "abcdefghijklmnopqrstuvwxyz0123456789";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();}}
package net.tiantianup.wap.utils.weixinpay;import org.apache.log4j.Logger;import org.xml.sax.SAXException;import javax.xml.parsers.ParserConfigurationException;import java.io.IOException;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.Arrays;import java.util.Map;/*** Created by LV on /4/7 0007.* Email:LvLoveYuForever@*/public class SignUtil {/*** 签名算法* @param o 要参与签名的数据对象* @return 签名* @throws IllegalAccessException*/public static Logger log=Logger.getLogger(SignUtil.class);public static String getSign(Object o,String key) throws IllegalAccessException {ArrayList<String> list = new ArrayList<String>();Class cls = o.getClass();Field[] fields = cls.getDeclaredFields();for (Field f : fields) {f.setAccessible(true);if (f.get(o) != null && f.get(o) != "") {list.add(f.getName() + "=" + f.get(o) + "&");}}int size = list.size();String [] arrayToSort = list.toArray(new String[size]);Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);StringBuilder sb = new StringBuilder();for(int i = 0; i < size; i ++) {sb.append(arrayToSort[i]);}String result = sb.toString();result += "key=" + key;log.info("Sign Before MD5:" + result);result = MD5.MD5Encode(result).toUpperCase();log.info("Sign Result:" + result);return result;}/*** 将map对象进行签名* @param map* @return*/public static String getSign(Map<String,Object> map,String key){ArrayList<String> list = new ArrayList<String>();for(Map.Entry<String,Object> entry:map.entrySet()){if(entry.getValue()!=""){list.add(entry.getKey() + "=" + entry.getValue() + "&");}}int size = list.size();String [] arrayToSort = list.toArray(new String[size]);Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);StringBuilder sb = new StringBuilder();for(int i = 0; i < size; i ++) {sb.append(arrayToSort[i]);}String result = sb.toString();result += "key=" + key;log.info("Sign Before MD5:" + result);result = MD5.MD5Encode(result).toUpperCase();log.info("Sign Result:" + result);return result;}/*** 从API返回的XML数据里面重新计算一次签名* @param responseString API返回的XML数据* @return*/public static String getSignFromResponseString(String responseString,String key) throws IOException, SAXException, ParserConfigurationException {Map<String,Object> map = XMLParser.getMapFromXML(responseString);//清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名map.put("sign","");//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较return SignUtil.getSign(map,key);}/*** 检验API返回的数据里面的签名是否合法,避免数据在传输的过程中被第三方篡改* @param responseString API返回的XML数据字符串* @return API签名是否合法* @throws ParserConfigurationException* @throws IOException* @throws SAXException*/public static boolean checkIsSignValidFromResponseString(String responseString,String key) throws ParserConfigurationException, IOException, SAXException {Map<String,Object> map = XMLParser.getMapFromXML(responseString);log.info(map.toString());String signFromAPIResponse = map.get("sign").toString();if(signFromAPIResponse=="" || signFromAPIResponse == null){log.info("API返回的数据签名数据不存在,有可能被第三方篡改!!!");return false;}log.info("服务器回包里面的签名是:" + signFromAPIResponse);//清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名map.put("sign","");//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较String signForAPIResponse = SignUtil.getSign(map,key);if(!signForAPIResponse.equals(signFromAPIResponse)){//签名验不过,表示这个API返回的数据有可能已经被篡改了log.info("API返回的数据签名验证不通过,有可能被第三方篡改!!!");return false;}log.info("恭喜,API返回的数据签名验证通过!!!");return true;}}
package net.tiantianup.wap.utils.weixinpay;import org.apache.log4j.Logger;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.SAXException;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import java.io.IOException;import java.io.InputStream;import java.util.HashMap;import java.util.Iterator;import java.util.Map;/*** Created by LV on /4/7 0007.* Email:LvLoveYuForever@*/public class XMLParser {private static Logger log=Logger.getLogger(XMLParser.class);/*** 将返回的xml的数据转换成map* @param xmlString* @return* @throws ParserConfigurationException* @throws IOException* @throws SAXException*/public static Map<String,Object> getMapFromXML(String xmlString) throws ParserConfigurationException, IOException, SAXException {//这里用Dom的方式解析回包的最主要目的是防止API新增回包字段DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();InputStream is = Util.getStringStream(xmlString);Document document = builder.parse(is);//获取到document里面的全部结点NodeList allNodes = document.getFirstChild().getChildNodes();Node node;Map<String, Object> map = new HashMap<String, Object>();int i=0;while (i < allNodes.getLength()) {node = allNodes.item(i);if(node instanceof Element){map.put(node.getNodeName(),node.getTextContent());}i++;}return map;}/*** 商户处理后同步返回给微信参数,转换成xml类型* @param return_code* @param return_msg* @return*/public static String setXML(String return_code,String return_msg) {return "<xml><return_code><![CDATA[" + return_code +"]]></return_code><return_msg><![CDATA[" +return_msg + "]]></return_msg></xml>";}public static String mapToXML(Map<String,Object> map){//将map类型的请求参数装换成XML格式StringBuilder sb=new StringBuilder();sb.append("<xml>");Iterator it=map.entrySet().iterator();while (it.hasNext()){Map.Entry entry= (Map.Entry) it.next();String k= (String) entry.getKey();String v= (String) entry.getValue();sb.append("<"+k+">"+v+"</"+k+">");}sb.append("</xml>");log.info("转换xml格式:"+sb.toString());return sb.toString();}}
然后支付具体业务需按照需求来定制,下面的业务是本人开发的项目需求里的例子:
/*** 异步发起提交订单* @param courseApplyId* @param request* @return*/@ResponseBody@RequestMapping(value = "/course/courseWxPayAjax/{courseApplyId}" ,method = RequestMethod.GET)public String courseWxPayAjax(@PathVariable("courseApplyId") String courseApplyId,HttpServletRequest request) throws IOException, SAXException, ParserConfigurationException, IllegalAccessException {//判断是否是微信客户端String userAgent = request.getHeader("User-Agent");if (userAgent.indexOf("MicroMessenger") > 0){//获取当前用户信息Subject currentUser = SecurityUtils.getSubject();//获取当前用户Session session = currentUser.getSession();CurrentUserModel currentUserModel = JSONObject.parseObject(session.getAttribute("user").toString(),CurrentUserModel.class);//获取申请的课程信息CourseApply courseApply=courseApplyService.getCourseApplyByCourseAppId(courseApplyId);//获取课程信息Course course=courseService.getCourseByCourseId(courseApply.getCourseId());/**微信支付请求参数*/Map<String,Object> requestParm=new TreeMap<>();requestParm.put("appid","wx0071d459ab95ac0f");//公众账号IDrequestParm.put("attach",courseApplyId);//附加数据我们带上支付的课程申请ID,方便我们在微信支付回调后对数据库进行操作requestParm.put("body",course.getCourseName());//商品描述:课程名requestParm.put("mch_id","商户号》填上申请的商户号");//商户号requestParm.put("nonce_str",RandomStringGenerator.getRandomStringByLength(32));//随机字符串requestParm.put("notify_url","/course/paySuccess");//接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。requestParm.put("openid",currentUserModel.getOpenId());//trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识requestParm.put("out_trade_no",RandomStringGenerator.getRandomStringByLength(32));//out_trade_no商户订单号requestParm.put("spbill_create_ip", IPTools.getIpAddr(request));//APP和网页支付提交用户端iprequestParm.put("total_fee",String.valueOf((int)(course.getCoursePrice()*100)));//订单总金额,微信支付是1分钱为单位的requestParm.put("trade_type","JSAPI");//JSAPI--公众号支付String sign= SignUtil.getSign(requestParm,key);//生成签名requestParm.put("sign",sign);/**微信支付请求参数*/log.info("微信支付请求参数:"+requestParm.toString());//将需要发送给API的参数转换成规定的xml类型String requestXml= XMLParser.mapToXML(requestParm);/**请求参数转换成xml格式*/log.info("请求参数转换成xml格式:"+requestXml);/**调用微信请求的API*///证书在服务器上面的路径String certLocalPath="/var/www/wap/WEB-INF/classes/apiclient_cert.p12";//证书在服务器上的地址,此应用部署的服务器上证书的具体地址------------------String responseXml=null;try {responseXml= HttpRequest.httpRequest("https://api.mch./pay/unifiedorder",requestXml,certLocalPath,"证书密码,自己根据具体的密码填写");//返回的xml格式数据log.info("返回的xml格式数据:"+responseXml);} catch (Exception e) {return null;//如果请求失败,说明请求过程发生了错误,我们在返回的页面做处理 }/* //模拟返回的xml数据String responseXml="<xml>" +" <return_code><![CDATA[SUCCESS]]></return_code>" +" <return_msg><![CDATA[OK]]></return_msg>" +" <appid><![CDATA[wx00]]></appid>" +" <mch_id><![CDATA[131]]></mch_id>" +" <nonce_str><![CDATA["+requestParm.get("nonce_str")+"]]></nonce_str>" +" <sign><![CDATA["+sign+"]]></sign>" +" <result_code><![CDATA[SUCCESS]]></result_code>" +" <prepay_id><![CDATA[wx11101639507cbf6ffd8b0779950874]]></prepay_id>" +" <trade_type><![CDATA[JSAPI]]></trade_type>" +"</xml>";*//**将返回的xml转换map格式*/Map<String,Object> responseMap=XMLParser.getMapFromXML(responseXml);/**返回数据转换成map格式*/log.info("返回数据转换成map格式:"+responseMap.toString());/**查看签名是否被修改*/if(SignUtil.checkIsSignValidFromResponseString(responseXml,key)){/**没有修改就做下一步判断*///result_code成功时才会返回prepay_idif(responseMap.get("result_code").toString().equalsIgnoreCase("SUCCESS")){//添加发起时间CourseApply courseApplyTime=new CourseApply();courseApplyTime.setApplyId(courseApplyId);courseApplyTime.setPrePayTime(new Date());courseApplyService.updateCourseApply(courseApplyTime);//给微信JSAPI准备发送数据Map<String,Object> parameters=new TreeMap<>();parameters.put("appId","此处按申请的账号填写");parameters.put("timeStamp",Long.toString(new Date().getTime()));parameters.put("nonceStr", RandomStringGenerator.getRandomStringByLength(32));parameters.put("package","prepay_id="+responseMap.get("prepay_id").toString());parameters.put("signType","MD5");String paySign=SignUtil.getSign(parameters,key);parameters.put("paySign",paySign);//用签名算法算出来的//当前微信浏览器的版本,5.0以上才支持微信支付char agent=userAgent.charAt(userAgent.indexOf("MicroMessenger")+15);parameters.put("agent",new String(new char[]{agent}));// parameters.put("agent","5.5");//测试时写死parameters.put("sendUrl","course/checkPaySuccess");//支付成功的回调地址----我们回调时可以查询一下数据库,是否支付成功,如果没有支付成功,返回到支付页面再支付---TODO/**为支付JSAPI准备的数据*/log.info("为支付JSAPI准备的数据:"+parameters.toString());return JSONObject.toJSONString(parameters);}}}return null;}
代码里的商户号,appid,证书的路径,证书密码都按申请的账号填写,其中的签名key需要在微信商户号里自行设置
回调函数方法:
/*** 微信支付完成后,通知的支付结果notify_url,由微信服务器请求的异步地址(LV)*/@ResponseBody@RequestMapping(value = "/course/paySuccess")public void paySuccess(HttpServletRequest request, HttpServletResponse response) throws IOException, ParserConfigurationException, SAXException {InputStream inputStream=request.getInputStream();ByteArrayOutputStream outStream=new ByteArrayOutputStream();byte[] buffer=new byte[1024];int len;while((len=inputStream.read(buffer))!=-1){outStream.write(buffer,0,len);}outStream.close();inputStream.close();String result=new String(outStream.toByteArray(),"utf-8");/**通知的支付结果notify_url返回的数据*/log.info("notify_url返回的数据:"+result);Map<String,Object> map= XMLParser.getMapFromXML(result);//返回数据转换成maplog.info("notify_url返回的数据转换map格式:"+map.toString());if(map.get("result_code").toString().equalsIgnoreCase("SUCCESS")){//TODO 对数据库进行操作//获取放入attach的课程申请IDString courseApplyId=map.get("attach").toString();CourseApply courseApply=new CourseApply();courseApply.setApplyId(courseApplyId);courseApply.setApplyStatus(1);courseApply.setPayType(0);courseApply.setPayStatus(1);courseApply.setPayTime(new Date());courseApply.setPayment("WXOPENPAY");//微信公众平台支付courseApply.setOutTradeNo(map.get("out_trade_no").toString());courseApply.setTransactionId(map.get("transaction_id").toString());courseApply.setAppid(map.get("appid").toString());courseApply.setMchId(map.get("mch_id").toString());//将课程的支付的几个参数设置成已支付的状态 courseApplyService.updateCourseApply(courseApply);//告诉微信服务器,支付完成response.getWriter().write(XMLParser.setXML("SUCCESS","OK"));}}
jsAPI中支付成功后的检查数据库的动作
/*** sendUrl 在jsAPI中如果支付成功,我们再次验证一下,是否支付成功* @param model* @param courseApplyId* @return*/@RequestMapping(value = "/course/checkPaySuccess/{courseApplyId}",method = RequestMethod.GET)public String checkPaySuccess(Model model,@PathVariable("courseApplyId") String courseApplyId){log.info("支付成功,检验数据库是否插入数据成功,已支付就跳转到支付成功页面");CourseApply courseApply=courseApplyService.getCourseApplyByCourseAppId(courseApplyId);int payStatus=courseApply.getPayStatus();model.addAttribute("courseApply",courseApply);if (payStatus==1){//使用返现码,生成返现记录(add by zzy)//获取当前用户信息Subject currentUser = SecurityUtils.getSubject();//获取当前用户Session session = currentUser.getSession();if(session.getAttribute("user")!=null) {CurrentUserModel currentUserModel = JSONObject.parseObject(session.getAttribute("user").toString(), CurrentUserModel.class);List<UserCoupon> list = userCouponService.getCouponByUserId(currentUserModel.getUserId());if (list != null && list.size() > 0) {UserCoupon coupon = list.get(0);User user = userService.getUserByUserId(currentUserModel.getUserId());Course course = courseService.getCourseByCourseId(courseApply.getCourseId());CashBackRecord cashBackRecord = new CashBackRecord();cashBackRecord.setRecordId(UUID.randomUUID().toString());cashBackRecord.setCouponNum(coupon.getCouponCode());cashBackRecord.setMoney(course.getCoursePrice() / 10);//返现10%cashBackRecord.setCreateTime(new Date());cashBackRecord.setUserName(user.getNickname());cashBackRecord.setUserId(user.getUserId());cashBackRecord.setCourseId(course.getCourseId());coupon.setIsUsed(1);user.setMoney(user.getMoney() + course.getCoursePrice() / 10);//生成返现记录cashBackRecordService.saveCashBackRecord(cashBackRecord);//更新余额userService.updateUser(user);//更改优惠码使用状态userCouponService.updateCoupon(coupon);}}//数据库有支付记录,返回到成功页面return "pay_result";}//数据库没有支付记录,返回错误页面return "pay_error";}
在jsp中用ajax发起支付,页面中要引入jsapi自己的js文件
<script src="https://res./open/js/jweixin-1.0.0.js"></script><script src="http://res./open/js/jweixin-1.0.0.js"></script><script>$(function($) {$("a[name='wxpay']").click(function () {//alert("${ctx}/course/courseWxPayAjax/${courseApplyId}"); $.ajax({type: "GET",url: "${ctx}/course/courseWxPayAjax/${courseApplyId}",dataType: "JSON",success: function(data){//alert(data);//alert("${ctx}/"+data.sendUrl+"/${courseApplyId}");if(parseInt(data.agent)<5){alert("你的微信版本低于5.0,不能使用微信支付");return;}//调用微信支付控件完成支付function onBridgeReady(){WeixinJSBridge.invoke('getBrandWCPayRequest',{"appId":data.appId,//公众号名称,由商户传入"timeStamp":data.timeStamp, //时间戳,自1970年以来的秒数"nonceStr":data.nonceStr, //随机串"package":data.package,"signType":data.signType, //微信签名方式:"paySign":data.paySign //微信签名 },function(res){// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。//alert("支付成功"+res.err_msg);if(res.err_msg == "get_brand_wcpay_request:ok" ) {//alert("判断成功");//跳转到设置的支付成功页面urlwindow.location.href="${ctx}/"+data.sendUrl+"/${courseApplyId}";}else {//alert("fail");// window.location.href="";//跳转到支付页面继续支付 }});}if (typeof WeixinJSBridge == "undefined"){if( document.addEventListener ){document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);}else if (document.attachEvent){document.attachEvent('WeixinJSBridgeReady', onBridgeReady);document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);}}else{onBridgeReady();}}});})})</script>
注意:在微信支付平台中设置支付目录,其中有测试目录和正式目录,如果是测试目录,只有添加进白名单的微信号才能完成支付,使用正式目录,只要是微信用户都可以完成支付。
${ctx}/course/courseWxPayAjax/${courseApplyId}
那么设置的支付目录是:
域名/${ctx}/course