提及來.微信支付真是一堆坑. 竟然官網都沒有java版本的完整代碼. 就算是php版本的.還都有錯誤.且先後各類版本.各類文檔一大堆....不停的誤導開發人員.javascript
花了一天半時間.總算實現了微信公衆號支付.和pc端的微信掃碼支付.其餘不說了.直接給思路
php
本人作的是微信V3版本的微信支付.微信的官方文檔中.提供的demo 只有一些工具類.這些類仍是頗有做用的.java
https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course3_tmpl&lang=zh_CN 能夠在這個鏈接中找到相應的java類.jquery
這裏必定要注意.在官網填寫的受權目錄必定要寫到三級目錄.如:
git
個人回調地址是:http://111.111.111.111:1111/control/weixinPay_notify web
那麼,官網填寫都受權目錄就是:http://111.111.111.111:1111/control/api
我試過.受權目錄寫到2級.是沒用的.此處差評,第一個坑.數組
首先,定義各類微信支付所須要的參數
緩存
GzhConfig.java服務器
public static String APPID = "XXXXXXXXXXXX"; //受理商ID,身份標識 public static String MCHID = "XXXXXXXXXXXXxx"; //商戶支付密鑰Key。審覈經過後,在微信發送的郵件中查看 public static String KEY = "XXXXXXXXXXXXXXXXX"; //JSAPI接口中獲取openid,審覈後在公衆平臺開啓開發模式後可查看 public static String APPSECRET = "xxxxxxxxxxxxxx"; //重定向地址 public static String REDIRECT_URL = "http://XXXXXXXXXXXXXXXXXXX/callWeiXinPay"; //異步回調地址 public static String NOTIFY_URL = "http://XXXXXXXXXXXXXXXXXXXXXX/weixinPay_notify"; //web回調地址 public static String WEB_NOTIFY_URL = "http://XXXXXXXXXXXXXXXXXXXXXXXXX/weixinPay_notify";
而後.就是正式的開始代碼了:
1.使用Oauth2.0受權.進行頁面跳轉,獲取code .(code關係着後面獲取openid.)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=123456789&redirect_uri=http://111.111.111.111:1111/control/orderPay&response_type=code&scope=snsapi_base&state=456123456#wechat_redirect
此處.appid 這個在微信官網能夠獲取. 重定向地址. 就是獲取code 後.跳轉指向你的地址.這裏能夠是你的訂單結算頁面.response_type=code和scope=snsapi_base 都是固定格式. state 是傳入傳出.這個參數用戶自定義爲任何均可以,好比說訂單id. 而後會和code 一塊兒傳遞到你的重定向地址,如我上面寫的重定向地址就是 orderPay連接.
2.在重定向到頁面(http://111.111.111.111:1111/control/orderPay)的時候中間執行java方法(如獲取openid 如執行微信統一下單接口,獲取預支付ID.).處理各類參數.下面貼具體代碼作說明.
GzhService.java
String code = request.getParameter("code"); String state = request.getParameter("state"); Debug.log("code-======"+code+"===========state======"+state); String noncestr = Sha1Util.getNonceStr();//生成隨機字符串 String timestamp = Sha1Util.getTimeStamp();//生成1970年到如今的秒數. //state 能夠傳遞你的訂單號.而後根據訂單號 查詢付款金額.我就不詳細寫了. String out_trade_no = state;//訂單號 GenericValue orderHeader = delegator.findOne("OrderHeader", UtilMisc.toMap("orderId", out_trade_no),false); String total_fee = String.valueOf(orderHeader.getBigDecimal("grandTotal").doubleValue()*100); String order_price = total_fee.substring(0, total_fee.indexOf("."));//訂單金額 //微信金額 以分爲單位.這是第二坑.若是不注意.頁面的報錯.你基本看不出來.由於他提示系統升級,正在維護.扯淡..... String product_name=out_trade_no;//訂單名稱 //獲取jsapi_ticket.此參數是爲了生成 js api 加載時候的簽名用.必須.jsapi_ticket只會存在7200秒.而且有時間限制,(好像一年還只能調用兩萬次.因此必定要緩存.)這是第三坑. //能夠在java中模擬url請求.就能獲取access_token 而後根據access_token 取得 jsapi_ticket,但必定要緩存起來..這個代碼.我只提供獲取.緩存大家本身處理. //SendGET方法我會在後面放出 String tokenParam = "grant_type=client_credential&appid="+GzhConfig.APPID+"&secret="+GzhConfig.APPSECRET; String tokenJsonStr = SendGET("https://api.weixin.qq.com/cgi-bin/token", tokenParam); Map tokenMap = JSON.parseObject(tokenJsonStr); //獲取access_token String access_token = (String)tokenMap.get("access_token"); String ticketParam = "access_token="+access_token+"&type=jsapi"; String ticketJsonStr = SendGET("https://api.weixin.qq.com/cgi-bin/ticket/getticket", ticketParam); Map ticketMap = JSON.parseObject(ticketJsonStr); //獲取jsapi_ticket String ticket = (String)ticketMap.get("ticket"); //下面就到了獲取openid,這個表明用戶id. //獲取openID String openParam = "appid="+GzhConfig.APPID+"&secret="+GzhConfig.APPSECRET+"&code="+code+"&grant_type=authorization_code"; String openJsonStr = SendGET("https://api.weixin.qq.com/sns/oauth2/access_token", openParam); Map openMap = JSON.parseObject(openJsonStr); String openid = (String) openMap.get("openid"); RequestHandler reqHandler = new RequestHandler(request, response); //初始化 RequestHandler 類能夠在微信的文檔中找到.還有相關工具類 reqHandler.init(); reqHandler.init(GzhConfig.APPID, GzhConfig.APPSECRET, GzhConfig.KEY, ""); //執行統一下單接口 得到預支付id reqHandler.setParameter("appid",GzhConfig.APPID); reqHandler.setParameter("mch_id", GzhConfig.MCHID); //商戶號 reqHandler.setParameter("nonce_str", noncestr); //隨機字符串 reqHandler.setParameter("body", product_name); //商品描述(必填.若是不填.也會提示系統升級.正在維護我艹.) reqHandler.setParameter("out_trade_no", out_trade_no); //商家訂單號 reqHandler.setParameter("total_fee", order_price); //商品金額,以分爲單位 reqHandler.setParameter("spbill_create_ip",request.getRemoteAddr()); //用戶的公網ip IpAddressUtil.getIpAddr(request) //下面的notify_url是用戶支付成功後爲微信調用的action 異步回調. reqHandler.setParameter("notify_url", GzhConfig.NOTIFY_URL); reqHandler.setParameter("trade_type", "JSAPI"); //------------須要進行用戶受權獲取用戶openid------------- reqHandler.setParameter("openid", openid); //這個必填. //這裏只是在組裝數據.還沒到執行到統一下單接口.由於統一下單接口的數據傳遞格式是xml的.因此才須要組裝. String requestUrl = reqHandler.getRequestURL(); requestUrl 例子: /* <xml><appid>wx2421b1c4370ec43b</appid><attach>支付測試</attach><body>JSAPI支付測試</body><mch_id>10000100</mch_id><nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str><notify_url>http://wxpay.weixin.qq.com/pub_v2/pay/notify.v2.php</notify_url><openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid><out_trade_no>1415659990</out_trade_no><spbill_create_ip>14.23.150.211</spbill_create_ip><total_fee>1</total_fee><trade_type>JSAPI</trade_type><sign>0CB01533B8C1EF103065174F50BCA001</sign></xml> */ Debug.log("requestUrl==================="+requestUrl); //統一下單接口提交 xml格式 URL orderUrl = new URL("https://api.mch.weixin.qq.com/pay/unifiedorder"); HttpURLConnection conn = (HttpURLConnection) orderUrl.openConnection(); conn.setConnectTimeout(30000); // 設置鏈接主機超時(單位:毫秒) conn.setReadTimeout(30000); // 設置從主機讀取數據超時(單位:毫秒) conn.setDoOutput(true); // post請求參數要放在http正文內,顧設置成true,默認是false conn.setDoInput(true); // 設置是否從httpUrlConnection讀入,默認狀況下是true conn.setUseCaches(false); // Post 請求不能使用緩存 // 設定傳送的內容類型是可序列化的java對象(若是不設此項,在傳送序列化對象時,當WEB服務默認的不是這種類型時可能拋java.io.EOFException) conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); conn.setRequestMethod("POST");// 設定請求的方法爲"POST",默認是GET conn.setRequestProperty("Content-Length",requestUrl.length()+""); String encode = "utf-8"; OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(), encode); out.write(requestUrl.toString()); out.flush(); out.close(); String result = getOut(conn); Debug.log("result=========返回的xml============="+result); Map<String, String> orderMap = XMLUtil.doXMLParse(result); Debug.log("orderMap==========================="+orderMap); //獲得的預支付id String prepay_id = orderMap.get("prepay_id"); SortedMap<String,String> params = new TreeMap<String,String>(); params.put("appId", GzhConfig.APPID); params.put("timeStamp",timestamp); params.put("nonceStr", noncestr); params.put("package", "prepay_id="+prepay_id); params.put("signType", "MD5"); //生成支付簽名,這個簽名 給 微信支付的調用使用 String paySign = reqHandler.createSign(params); request.setAttribute("paySign", paySign); request.setAttribute("appId", GzhConfig.APPID); request.setAttribute("timeStamp", timestamp); //時間戳 request.setAttribute("nonceStr", noncestr); //隨機字符串 request.setAttribute("signType", "MD5"); //加密格式 request.setAttribute("out_trade_no", out_trade_no); //訂單號 request.setAttribute("package", "prepay_id="+prepay_id);//預支付id ,就這樣的格式. String url = "http://xxxxxxxxxx/control/wxPayment"; String signValue = "jsapi_ticket="+ticket+"&noncestr="+noncestr+"×tamp="+timestamp+"&url="+url; Debug.log("url====="+signValue); //這個簽名.主要是給加載微信js使用.別和上面的搞混了. String signature = Sha1Util.getSha1((signValue)); request.setAttribute("signature", signature);
//此頁面的一些其餘方法
public static String getOut(HttpURLConnection conn) throws IOException{ if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { return null; } // 獲取響應內容體 BufferedReader in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "UTF-8")); String line = ""; StringBuffer strBuf = new StringBuffer(); while ((line = in.readLine()) != null) { strBuf.append(line).append("\n"); } in.close(); return strBuf.toString().trim(); }
public static String SendGET(String url,String param){ String result="";//訪問返回結果 BufferedReader read=null;//讀取訪問結果 try { //建立url URL realurl=new URL(url+"?"+param); //打開鏈接 URLConnection connection=realurl.openConnection(); // 設置通用的請求屬性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); //創建鏈接 connection.connect(); // 獲取全部響應頭字段 // Map<String, List<String>> map = connection.getHeaderFields(); // 遍歷全部的響應頭字段,獲取到cookies等 // for (String key : map.keySet()) { // System.out.println(key + "--->" + map.get(key)); // } // 定義 BufferedReader輸入流來讀取URL的響應 read = new BufferedReader(new InputStreamReader( connection.getInputStream(),"UTF-8")); String line;//循環讀取 while ((line = read.readLine()) != null) { result += line; } } catch (IOException e) { e.printStackTrace(); }finally{ if(read!=null){//關閉流 try { read.close(); } catch (IOException e) { e.printStackTrace(); } } } return result; }
其餘相關類的方法:
/*
'============================================================================
'api說明:
'createSHA1Sign建立簽名SHA1
'getSha1()Sha1簽名
'============================================================================
'*/
public class Sha1Util { public static String getNonceStr() { Random random = new Random(); return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8"); } public static String getTimeStamp() { return String.valueOf(System.currentTimeMillis() / 1000); } //建立簽名SHA1 public static String createSHA1Sign(SortedMap<String, String> signParams) throws Exception { StringBuffer sb = new StringBuffer(); Set es = signParams.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(); sb.append(k + "=" + v + "&"); //要採用URLENCODER的原始值! } String params = sb.substring(0, sb.lastIndexOf("&")); System.out.println("sha1 sb:" + params); return getSha1(params); } //Sha1簽名 public static String getSha1(String str) { if (str == null || str.length() == 0) { return null; } char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; try { MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); mdTemp.update(str.getBytes("GBK")); byte[] md = mdTemp.digest(); int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; buf[k++] = hexDigits[byte0 & 0xf]; } return new String(buf); } catch (Exception e) { return null; } } }
/**
* xml工具類
* @author miklchen
*
*/
public class XMLUtil { /** * 解析xml,返回第一級元素鍵值對。若是第一級元素有子節點,則此節點的值是子節點的xml數據。 * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map<String,String> doXMLParse(String strxml) throws JDOMException, IOException { if(null == strxml || "".equals(strxml)) { return null; } Map<String,String> m = new HashMap<String,String>(); InputStream in = HttpClientUtil.String2Inputstream(strxml); 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 = XMLUtil.getChildrenText(children); } m.put(k, v); } //關閉流 in.close(); return m; } /** * 獲取子結點的xml * @param children * @return String */ 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(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 獲取xml編碼字符集 * @param strxml * @return * @throws IOException * @throws JDOMException */ public static String getXMLEncoding(String strxml) throws JDOMException, IOException { InputStream in = HttpClientUtil.String2Inputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); in.close(); return (String)doc.getProperty("encoding"); } }
/** * Http客戶端工具類<br/> * 這是內部調用類,請不要在外部調用。 * @author miklchen * */
import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.HashMap; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; public class HttpClientUtil { public static final String SunX509 = "SunX509"; public static final String JKS = "JKS"; public static final String PKCS12 = "PKCS12"; public static final String TLS = "TLS"; /** * get HttpURLConnection * @param strUrl url地址 * @return HttpURLConnection * @throws IOException */ public static HttpURLConnection getHttpURLConnection(String strUrl) throws IOException { URL url = new URL(strUrl); HttpURLConnection httpURLConnection = (HttpURLConnection) url .openConnection(); return httpURLConnection; } /** * get HttpsURLConnection * @param strUrl url地址 * @return HttpsURLConnection * @throws IOException */ public static HttpsURLConnection getHttpsURLConnection(String strUrl) throws IOException { URL url = new URL(strUrl); HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url .openConnection(); return httpsURLConnection; } /** * 獲取不帶查詢串的url * @param strUrl * @return String */ public static String getURL(String strUrl) { if(null != strUrl) { int indexOf = strUrl.indexOf("?"); if(-1 != indexOf) { return strUrl.substring(0, indexOf); } return strUrl; } return strUrl; } /** * 獲取查詢串 * @param strUrl * @return String */ public static String getQueryString(String strUrl) { if(null != strUrl) { int indexOf = strUrl.indexOf("?"); if(-1 != indexOf) { return strUrl.substring(indexOf+1, strUrl.length()); } return ""; } return strUrl; } /** * 查詢字符串轉換成Map<br/> * name1=key1&name2=key2&... * @param queryString * @return */ public static Map queryString2Map(String queryString) { if(null == queryString || "".equals(queryString)) { return null; } Map m = new HashMap(); String[] strArray = queryString.split("&"); for(int index = 0; index < strArray.length; index++) { String pair = strArray[index]; HttpClientUtil.putMapByPair(pair, m); } return m; } /** * 把鍵值添加至Map<br/> * pair:name=value * @param pair name=value * @param m */ public static void putMapByPair(String pair, Map m) { if(null == pair || "".equals(pair)) { return; } int indexOf = pair.indexOf("="); if(-1 != indexOf) { String k = pair.substring(0, indexOf); String v = pair.substring(indexOf+1, pair.length()); if(null != k && !"".equals(k)) { m.put(k, v); } } else { m.put(pair, ""); } } /** * BufferedReader轉換成String<br/> * 注意:流關閉須要自行處理 * @param reader * @return String * @throws IOException */ public static String bufferedReader2String(BufferedReader reader) throws IOException { StringBuffer buf = new StringBuffer(); String line = null; while( (line = reader.readLine()) != null) { buf.append(line); buf.append("\r\n"); } return buf.toString(); } /** * 處理輸出<br/> * 注意:流關閉須要自行處理 * @param out * @param data * @param len * @throws IOException */ public static void doOutput(OutputStream out, byte[] data, int len) throws IOException { int dataLen = data.length; int off = 0; while(off < dataLen) { if(len >= dataLen) { out.write(data, off, dataLen); } else { out.write(data, off, len); } //刷新緩衝區 out.flush(); off += len; dataLen -= len; } } /** * 獲取SSLContext * @param trustFile * @param trustPasswd * @param keyFile * @param keyPasswd * @return * @throws NoSuchAlgorithmException * @throws KeyStoreException * @throws IOException * @throws CertificateException * @throws UnrecoverableKeyException * @throws KeyManagementException */ public static SSLContext getSSLContext( FileInputStream trustFileInputStream, String trustPasswd, FileInputStream keyFileInputStream, String keyPasswd) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { // ca TrustManagerFactory tmf = TrustManagerFactory.getInstance(HttpClientUtil.SunX509); KeyStore trustKeyStore = KeyStore.getInstance(HttpClientUtil.JKS); trustKeyStore.load(trustFileInputStream, HttpClientUtil .str2CharArray(trustPasswd)); tmf.init(trustKeyStore); final char[] kp = HttpClientUtil.str2CharArray(keyPasswd); KeyManagerFactory kmf = KeyManagerFactory.getInstance(HttpClientUtil.SunX509); KeyStore ks = KeyStore.getInstance(HttpClientUtil.PKCS12); ks.load(keyFileInputStream, kp); kmf.init(ks, kp); SecureRandom rand = new SecureRandom(); SSLContext ctx = SSLContext.getInstance(HttpClientUtil.TLS); ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), rand); return ctx; } /** * 獲取CA證書信息 * @param cafile CA證書文件 * @return Certificate * @throws CertificateException * @throws IOException */ public static Certificate getCertificate(File cafile) throws CertificateException, IOException { CertificateFactory cf = CertificateFactory.getInstance("X.509"); FileInputStream in = new FileInputStream(cafile); Certificate cert = cf.generateCertificate(in); in.close(); return cert; } /** * 字符串轉換成char數組 * @param str * @return char[] */ public static char[] str2CharArray(String str) { if(null == str) return null; return str.toCharArray(); } /** * 存儲ca證書成JKS格式 * @param cert * @param alias * @param password * @param out * @throws KeyStoreException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws IOException */ public static void storeCACert(Certificate cert, String alias, String password, OutputStream out) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); ks.setCertificateEntry(alias, cert); // store keystore ks.store(out, HttpClientUtil.str2CharArray(password)); } public static InputStream String2Inputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } }
RequestHandler.java
import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /* '微信支付服務器簽名支付請求請求類 '============================================================================ 'api說明: 'init(app_id, app_secret, partner_key, app_key); '初始化函數,默認給一些參數賦值,如cmdno,date等。 'setKey(key_)'設置商戶密鑰 'getLasterrCode(),獲取最後錯誤號 'GetToken();獲取Token 'getTokenReal();Token過時後實時獲取Token 'createMd5Sign(signParams);生成Md5簽名 'genPackage(packageParams);獲取package包 'createSHA1Sign(signParams);建立簽名SHA1 'sendPrepay(packageParams);提交預支付 'getDebugInfo(),獲取debug信息 '============================================================================ '*/ public class RequestHandler { /** Token獲取網關地址地址 */ private String tokenUrl; /** 預支付網關url地址 */ private String gateUrl; /** 查詢支付通知網關URL */ private String notifyUrl; /** 商戶參數 */ private String appid; private String appkey; private String partnerkey; private String appsecret; private String key; /** 請求的參數 */ private SortedMap parameters; /** Token */ private String Token; private String charset; /** debug信息 */ private String debugInfo; private String last_errcode; private HttpServletRequest request; private HttpServletResponse response; /** * 初始構造函數。 * * @return */ public RequestHandler(HttpServletRequest request, HttpServletResponse response) { this.last_errcode = "0"; this.request = request; this.response = response; this.charset = "GBK"; this.parameters = new TreeMap(); // 驗證notify支付訂單網關 notifyUrl = "https://gw.tenpay.com/gateway/simpleverifynotifyid.xml"; } /** * 初始化函數。 */ public void init(String app_id, String app_secret, String app_key, String partner_key) { this.last_errcode = "0"; this.Token = "token_"; this.debugInfo = ""; this.appkey = app_key; this.appid = app_id; this.partnerkey = partner_key; this.appsecret = app_secret; } public void init() { } /** * 獲取最後錯誤號 */ public String getLasterrCode() { return last_errcode; } /** *獲取入口地址,不包含參數值 */ public String getGateUrl() { return gateUrl; } /** * 獲取參數值 * * @param parameter * 參數名稱 * @return String */ public String getParameter(String parameter) { String s = (String) this.parameters.get(parameter); return (null == s) ? "" : s; } /** * 設置參數值 * @param parameter 參數名稱 * @param parameterValue 參數值 */ public void setParameter(String parameter, String parameterValue) { String v = ""; if(null != parameterValue) { v = parameterValue.trim(); } this.parameters.put(parameter, v); } //設置密鑰 public void setKey(String key) { this.partnerkey = key; } //設置微信密鑰 public void setAppKey(String key){ this.appkey = key; } // 特殊字符處理 public String UrlEncode(String src) throws UnsupportedEncodingException { return URLEncoder.encode(src, this.charset).replace("+", "%20"); } // 獲取package的簽名包 public String genPackage(SortedMap<String, String> packageParams) throws UnsupportedEncodingException { String sign = createSign(packageParams); 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(); sb.append(k + "=" + UrlEncode(v) + "&"); } // 去掉最後一個& String packageValue = sb.append("sign=" + sign).toString(); System.out.println("packageValue=" + packageValue); return packageValue; } /** * 建立md5摘要,規則是:按參數名稱a-z排序,遇到空值的參數不參加簽名。 */ public String createSign(SortedMap<String, String> packageParams) { 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 (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + GzhConfig.KEY); System.out.println("md5 sb:" + sb); String sign = MD5Util.MD5Encode(sb.toString(), this.charset) .toUpperCase(); return sign; } /** * 建立package簽名 */ public boolean createMd5Sign(String signParams) { StringBuffer sb = new StringBuffer(); Set es = this.parameters.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 + "&"); } } // 算出摘要 String enc = TenpayUtil.getCharacterEncoding(this.request, this.response); String sign = MD5Util.MD5Encode(sb.toString(), enc).toLowerCase(); String tenpaySign = this.getParameter("sign").toLowerCase(); // debug信息 this.setDebugInfo(sb.toString() + " => sign:" + sign + " tenpaySign:" + tenpaySign); return tenpaySign.equals(sign); } public String getRequestURL() throws UnsupportedEncodingException { this.createSign(); StringBuffer sb = new StringBuffer(); sb.append("<xml>"); String enc = TenpayUtil.getCharacterEncoding(this.request, this.response); Set es = this.parameters.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 ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) { sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">"); }else { sb.append("<"+k+">"+v+"</"+k+">"); } } sb.append("</xml>"); return sb.toString(); } protected void createSign() { StringBuffer sb = new StringBuffer(); Set es = this.parameters.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(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + GzhConfig.KEY); //本身的API密鑰 String enc = TenpayUtil.getCharacterEncoding(this.request, this.response); String sign = MD5Util.MD5Encode(sb.toString(), enc).toUpperCase(); this.setParameter("sign", sign); } //輸出XML public String parseXML() { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = this.parameters.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(null != v && !"".equals(v) && !"appkey".equals(k)) { sb.append("<" + k +">" + getParameter(k) + "</" + k + ">\n"); } } sb.append("</xml>"); return sb.toString(); } /** * 設置debug信息 */ protected void setDebugInfo(String debugInfo) { this.debugInfo = debugInfo; } public void setPartnerkey(String partnerkey) { this.partnerkey = partnerkey; } public String getDebugInfo() { return debugInfo; } public String getKey() { return key; } 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>"; } }
TenpayUtil
import java.text.SimpleDateFormat; import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class TenpayUtil { private static Object Server; private static String QRfromGoogle; /** * 把對象轉換成字符串 * @param obj * @return String 轉換成字符串,若對象爲null,則返回空字符串. */ public static String toString(Object obj) { if(obj == null) return ""; return obj.toString(); } /** * 把對象轉換爲int數值. * * @param obj * 包含數字的對象. * @return int 轉換後的數值,對不能轉換的對象返回0。 */ public static int toInt(Object obj) { int a = 0; try { if (obj != null) a = Integer.parseInt(obj.toString()); } catch (Exception e) { } return a; } /** * 獲取當前時間 yyyyMMddHHmmss * @return String */ public static String getCurrTime() { Date now = new Date(); SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss"); String s = outFormat.format(now); return s; } /** * 獲取當前日期 yyyyMMdd * @param date * @return String */ public static String formatDate(Date date) { SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); String strDate = formatter.format(date); return strDate; } /** * 取出一個指定長度大小的隨機正整數. * * @param length * int 設定所取出隨機數的長度。length小於11 * @return int 返回生成的隨機數。 */ public static int buildRandom(int length) { int num = 1; double random = Math.random(); if (random < 0.1) { random = random + 0.1; } for (int i = 0; i < length; i++) { num = num * 10; } return (int) ((random * num)); } /** * 獲取編碼字符集 * @param request * @param response * @return String */ public static String getCharacterEncoding(HttpServletRequest request, HttpServletResponse response) { if(null == request || null == response) { return "gbk"; } String enc = request.getCharacterEncoding(); if(null == enc || "".equals(enc)) { enc = response.getCharacterEncoding(); } if(null == enc || "".equals(enc)) { enc = "gbk"; } return enc; } public static String URLencode(String content){ String URLencode; URLencode= replace(Server.equals(content), "+", "%20"); return URLencode; } private static String replace(boolean equals, String string, String string2) { return null; } /** * 獲取unix時間,從1970-01-01 00:00:00開始的秒數 * @param date * @return long */ public static long getUnixTime(Date date) { if( null == date ) { return 0; } return date.getTime()/1000; } public static String QRfromGoogle(String chl) { int widhtHeight = 300; String EC_level = "L"; int margin = 0; String QRfromGoogle; chl = URLencode(chl); QRfromGoogle = "http://chart.apis.google.com/chart?chs=" + widhtHeight + "x" + widhtHeight + "&cht=qr&chld=" + EC_level + "|" + margin + "&chl=" + chl; return QRfromGoogle; } /** * 時間轉換成字符串 * @param date 時間 * @param formatType 格式化類型 * @return String */ public static String date2String(Date date, String formatType) { SimpleDateFormat sdf = new SimpleDateFormat(formatType); return sdf.format(date); } }
MD5Util
public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; }
下面纔到了頁面代碼:jsp 頁面
<script type="text/javascript" src="/xxxx/jquery-1.6.2.min.js"></script> <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script language="javascript"> //加載 wx.config({ debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。 appId: '${StringUtil.wrapString(requestAttributes.appId)!}', // 必填,公衆號的惟一標識 timestamp: ${StringUtil.wrapString(requestAttributes.timeStamp)?default(0)!}, // 必填,生成簽名的時間戳 nonceStr: '${StringUtil.wrapString(requestAttributes.nonceStr)!}', // 必填,生成簽名的隨機串 signature: '${StringUtil.wrapString(requestAttributes.signature)!}',// 必填,簽名,見附錄1 jsApiList: [ 'checkJsApi', 'chooseWXPay'] // 必填,須要使用的JS接口列表,全部JS接口列表見附錄2 }); wx.ready(function(){ //支付 wx.chooseWXPay({ timestamp: ${StringUtil.wrapString(requestAttributes.timeStamp)?default(0)!}, // 支付簽名時間戳,注意微信jssdk中的全部使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符 nonceStr: '${StringUtil.wrapString(requestAttributes.nonceStr)!}', // 支付簽名隨機串,不長於 32 位 package: '${StringUtil.wrapString(requestAttributes.package)!}', // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=***) signType: '${StringUtil.wrapString(requestAttributes.signType)!}', // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5' paySign: '${StringUtil.wrapString(requestAttributes.paySign)!}', // 支付簽名 success: function (res) { // 支付成功後的回調函數 WeixinJSBridge.log(res.err_msg); //alert("支付接口:"+res.err_code + res.err_desc + res.err_msg); if(!res.err_msg){ //支付完後.跳轉到成功頁面. location.href="orderconfirm?orderId=${StringUtil.wrapString(requestAttributes.out_trade_no)!}"; } } }); // config信息驗證後會執行ready方法,全部接口調用都必須在config接口得到結果以後,config是一個客戶端的異步操做,因此若是須要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則能夠直接調用,不須要放在ready函數中。 }); wx.error(function(res){ // config信息驗證失敗會執行error函數,如簽名過時致使驗證失敗,具體錯誤信息能夠打開config的debug模式查看,也能夠在返回的res參數中查看,對於SPA能夠在這裏更新簽名。 }); wx.checkJsApi({ jsApiList: ['chooseWXPay'], // 須要檢測的JS接口列表,全部JS接口列表見附錄2, success: function(res) { //alert("檢測接口:"+res.err_msg); } }); </script>
下面是後臺異步回調代碼:
/** * 異步返回 */ @SuppressWarnings("deprecation") public static String weixinNotify(HttpServletRequest request, HttpServletResponse response){ try { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); String resultStr = new String(outSteam.toByteArray(),"utf-8"); Map<String, String> resultMap = XMLUtil.doXMLParse(resultStr); String result_code = resultMap.get("result_code"); String is_subscribe = resultMap.get("is_subscribe"); String out_trade_no = resultMap.get("out_trade_no"); String transaction_id = resultMap.get("transaction_id"); String sign = resultMap.get("sign"); String time_end = resultMap.get("time_end"); String bank_type = resultMap.get("bank_type"); String return_code = resultMap.get("return_code"); //簽名驗證 GenericValue userLogin =delegator.findOne("UserLogin", UtilMisc.toMap("userLoginId","admin"),false); if(return_code.equals("SUCCESS")){ //此處就是你的邏輯代碼 } request.setAttribute("out_trade_no", out_trade_no); //通知微信.異步確認成功.必寫.否則會一直通知後臺.八次以後就認爲交易失敗了. response.getWriter().write(RequestHandler.setXML("SUCCESS", "")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JDOMException e) { e.printStackTrace(); } return "success"; }
代碼中,刪除了一些和公司相關的代碼.因此若是直接複製進去.確定是要作大量修改的.見諒.