首先準備幾個工具類: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 javax.net.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 2016/4/11 0011. * Email:LvLoveYuForever@gmail.com */ 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 certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, certPassword.toCharArray()) .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory 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 2016/4/7 0007. * Email:LvLoveYuForever@gmail.com */ 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 2016/4/7 0007. * Email:LvLoveYuForever@gmail.com */ 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(); } }
而後支付具體業務需按照需求來定製,下面的業務是本人開發的項目需求裏的例子:java
/** * 異步發起提交訂單 * @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");//公衆帳號ID requestParm.put("attach",courseApplyId);//附加數據咱們帶上支付的課程申請ID,方便咱們在微信支付回調後對數據庫進行操做 requestParm.put("body",course.getCourseName());//商品描述:課程名 requestParm.put("mch_id","商戶號》填上申請的商戶號");//商戶號 requestParm.put("nonce_str",RandomStringGenerator.getRandomStringByLength(32));//隨機字符串 requestParm.put("notify_url","http://wap.zhaorenxue.com/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和網頁支付提交用戶端ip requestParm.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.weixin.qq.com/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[wx201411101639507cbf6ffd8b0779950874]]></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_id if(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須要在微信商戶號裏自行設置node
回調函數方法:git
/** * 微信支付完成後,通知的支付結果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);//返回數據轉換成map log.info("notify_url返回的數據轉換map格式:"+map.toString()); if(map.get("result_code").toString().equalsIgnoreCase("SUCCESS")){ //TODO 對數據庫進行操做 //獲取放入attach的課程申請ID String 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中支付成功後的檢查數據庫的動做ajax
/** * 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.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script src="http://res.wx.qq.com/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("判斷成功"); //跳轉到設置的支付成功頁面url window.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