一.流程步驟java
本實例是基於springmvc框架編寫git
1.執行流程
當手機端app(就是你公司開發的app)在支付頁面時,調起服務端(後臺第1個建立訂單接口)接口,後臺把須要調起微信支付的參數返回給手機端,手機端拿到
這些參數後,拉起微信支付環境完成支付,完成支付後會調異步通知(第2個接口),此時須要給微信返回成功或者失敗信息,成功後,由app端調用同步通知(第3個接口)
返回支付成功頁面,完成整個支付流程。
2.須要說明的事項web
由於微信支付都是用本身工具類生成加密、解析xml、驗籤等方法,須要寫的類比較多,所以你們在參考此文檔時,直接複製就好了spring
二.配置文件及通用類api
1.配置文件類數組
ConstantUtil(各應應用id的配置,把本身應用對應的找到便可)緩存
1 package com.qtkj.app.weixinpay.util; 2 3 public class ConstantUtil { 4 5 // 微信開發平臺應用ID* 6 public static final String APP_ID=""; 7 8 // 應用對應的憑證 appsecret 9 public static final String APP_SECRET=""; 10 11 // 應用對應的密鑰 appkey 12 public static final String APP_KEY=""; 13 14 //微信支付商戶號 15 public static final String MCH_ID=""; 16 17 //商戶id 18 public static final String PARTNER_ID=""; 19 20 //商品描述 21 public static final String BODY="遊戲幣-帳戶充值"; 22 23 // 獲取預支付id的接口訪問路徑 24 public static String GATEURL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 25 26 // 微信服務器回調通知url 27 public static String NOTIFY_URL=""; 28 }
2.工具類(一共8個,直接複製使用便可) 服務器
TenpayUtil微信
1 package com.qtkj.app.weixinpay.util; 2 3 import java.text.SimpleDateFormat; 4 import java.util.Date; 5 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 9 public class TenpayUtil { 10 11 /** 12 * 把對象轉換成字符串 13 * @param obj 14 * @return String 轉換成字符串,若對象爲null,則返回空字符串. 15 */ 16 public static String toString(Object obj) { 17 if(obj == null) 18 return ""; 19 20 return obj.toString(); 21 } 22 23 /** 24 * 把對象轉換爲int數值. 25 * 26 * @param obj 27 * 包含數字的對象. 28 * @return int 轉換後的數值,對不能轉換的對象返回0。 29 */ 30 public static int toInt(Object obj) { 31 int a = 0; 32 try { 33 if (obj != null) 34 a = Integer.parseInt(obj.toString()); 35 } catch (Exception e) { 36 37 } 38 return a; 39 } 40 41 /** 42 * 獲取當前時間 yyyyMMddHHmmss 43 * @return String 44 */ 45 public static String getCurrTime() { 46 Date now = new Date(); 47 SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss"); 48 String s = outFormat.format(now); 49 return s; 50 } 51 52 /** 53 * 獲取當前日期 yyyyMMdd 54 * @param date 55 * @return String 56 */ 57 public static String formatDate(Date date) { 58 SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); 59 String strDate = formatter.format(date); 60 return strDate; 61 } 62 63 /** 64 * 取出一個指定長度大小的隨機正整數. 65 * 66 * @param length 67 * int 設定所取出隨機數的長度。length小於11 68 * @return int 返回生成的隨機數。 69 */ 70 public static int buildRandom(int length) { 71 int num = 1; 72 double random = Math.random(); 73 if (random < 0.1) { 74 random = random + 0.1; 75 } 76 for (int i = 0; i < length; i++) { 77 num = num * 10; 78 } 79 return (int) ((random * num)); 80 } 81 82 /** 83 * 獲取編碼字符集 84 * @param request 85 * @param response 86 * @return String 87 */ 88 public static String getCharacterEncoding(HttpServletRequest request, 89 HttpServletResponse response) { 90 91 if(null == request || null == response) { 92 return "gbk"; 93 } 94 95 String enc = request.getCharacterEncoding(); 96 if(null == enc || "".equals(enc)) { 97 enc = response.getCharacterEncoding(); 98 } 99 100 if(null == enc || "".equals(enc)) { 101 enc = "gbk"; 102 } 103 104 return enc; 105 } 106 107 /** 108 * 獲取unix時間,從1970-01-01 00:00:00開始的秒數 109 * @param date 110 * @return long 111 */ 112 public static long getUnixTime(Date date) { 113 if( null == date ) { 114 return 0; 115 } 116 117 return date.getTime()/1000; 118 } 119 120 /** 121 * 時間轉換成字符串 122 * @param date 時間 123 * @param formatType 格式化類型 124 * @return String 125 */ 126 public static String date2String(Date date, String formatType) { 127 SimpleDateFormat sdf = new SimpleDateFormat(formatType); 128 return sdf.format(date); 129 } 130 }
PrepayIdRequestHandler微信開發
1 package com.qtkj.app.weixinpay.handler; 2 3 import java.util.Iterator; 4 import java.util.Map; 5 import java.util.Set; 6 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import com.qtkj.app.weixinpay.util.ConstantUtil; 11 import com.qtkj.app.weixinpay.util.MD5Util; 12 import com.qtkj.app.weixinpay.util.XMLUtil; 13 14 public class PrepayIdRequestHandler extends RequestHandler { 15 16 public PrepayIdRequestHandler(HttpServletRequest request, 17 HttpServletResponse response) { 18 super(request, response); 19 } 20 21 public String createMD5Sign() { 22 StringBuffer sb = new StringBuffer(); 23 Set es = super.getAllParameters().entrySet(); 24 Iterator it = es.iterator(); 25 while (it.hasNext()) { 26 Map.Entry entry = (Map.Entry) it.next(); 27 String k = (String) entry.getKey(); 28 String v = (String) entry.getValue(); 29 sb.append(k + "=" + v + "&"); 30 } 31 String params=sb.append("key="+ConstantUtil.APP_KEY).substring(0); 32 33 34 String sign = MD5Util.MD5Encode(params, "utf8"); 35 return sign.toUpperCase(); 36 } 37 38 // 提交預支付 39 public String sendPrepay() throws Exception { 40 String prepayid = ""; 41 Set es=super.getAllParameters().entrySet(); 42 Iterator it=es.iterator(); 43 StringBuffer sb = new StringBuffer("<xml>"); 44 while(it.hasNext()){ 45 Map.Entry entry = (Map.Entry) it.next(); 46 String k = (String) entry.getKey(); 47 String v = (String) entry.getValue(); 48 sb.append("<"+k+">"+v+"</"+k+">"); 49 } 50 sb.append("</xml>"); 51 String params=sb.substring(0); 52 System.out.println("請求參數:"+params); 53 String requestUrl = super.getGateUrl(); 54 System.out.println("請求url:"+requestUrl); 55 TenpayHttpClient httpClient = new TenpayHttpClient(); 56 httpClient.setReqContent(requestUrl); 57 String resContent = ""; 58 if (httpClient.callHttpPost(requestUrl, params)) { 59 resContent = httpClient.getResContent(); 60 System.out.println("獲取prepayid的返回值:"+resContent); 61 Map<String,String> map=XMLUtil.doXMLParse(resContent); 62 if(map.containsKey("prepay_id")) 63 prepayid=map.get("prepay_id"); 64 } 65 return prepayid; 66 } 67 }
RequestHandler
1 package com.qtkj.app.weixinpay.handler; 2 3 import java.io.IOException; 4 import java.io.UnsupportedEncodingException; 5 import java.net.URLEncoder; 6 import java.util.Iterator; 7 import java.util.Map; 8 import java.util.Set; 9 import java.util.SortedMap; 10 import java.util.TreeMap; 11 12 import javax.servlet.http.HttpServletRequest; 13 import javax.servlet.http.HttpServletResponse; 14 15 import com.qtkj.app.weixinpay.util.MD5Util; 16 import com.qtkj.app.weixinpay.util.TenpayUtil; 17 18 /** 19 * 請求處理類 20 * 請求處理類繼承此類,重寫createSign方法便可。 21 * 22 */ 23 public class RequestHandler { 24 25 /** 網關url地址 */ 26 private String gateUrl; 27 28 /** 密鑰 */ 29 private String key; 30 31 /** 請求的參數 */ 32 private SortedMap parameters; 33 34 protected HttpServletRequest request; 35 36 protected HttpServletResponse response; 37 38 /** 39 * 構造函數 40 * @param request 41 * @param response 42 */ 43 public RequestHandler(HttpServletRequest request, HttpServletResponse response) { 44 this.request = request; 45 this.response = response; 46 47 this.gateUrl = "https://gw.tenpay.com/gateway/pay.htm"; 48 this.key = ""; 49 this.parameters = new TreeMap(); 50 } 51 52 /** 53 *初始化函數。 54 */ 55 public void init() { 56 //nothing to do 57 } 58 59 /** 60 *獲取入口地址,不包含參數值 61 */ 62 public String getGateUrl() { 63 return gateUrl; 64 } 65 66 /** 67 *設置入口地址,不包含參數值 68 */ 69 public void setGateUrl(String gateUrl) { 70 this.gateUrl = gateUrl; 71 } 72 73 /** 74 *獲取密鑰 75 */ 76 public String getKey() { 77 return key; 78 } 79 80 /** 81 *設置密鑰 82 */ 83 public void setKey(String key) { 84 this.key = key; 85 } 86 87 /** 88 * 獲取參數值 89 * @param parameter 參數名稱 90 * @return String 91 */ 92 public String getParameter(String parameter) { 93 String s = (String)this.parameters.get(parameter); 94 return (null == s) ? "" : s; 95 } 96 97 /** 98 * 設置參數值 99 * @param parameter 參數名稱 100 * @param parameterValue 參數值 101 */ 102 public void setParameter(String parameter, Object parameterValue) { 103 String v = ""; 104 if(null != parameterValue) { 105 if(parameterValue instanceof String) 106 v = ((String) parameterValue).trim(); 107 } 108 this.parameters.put(parameter, v); 109 } 110 111 /** 112 * 返回全部的參數 113 * @return SortedMap 114 */ 115 public SortedMap getAllParameters() { 116 return this.parameters; 117 } 118 119 /** 120 * 獲取帶參數的請求URL 121 * @return String 122 * @throws UnsupportedEncodingException 123 */ 124 public String getRequestURL() throws UnsupportedEncodingException { 125 126 this.createSign(); 127 128 StringBuffer sb = new StringBuffer(); 129 String enc = TenpayUtil.getCharacterEncoding(this.request, this.response); 130 Set es = this.parameters.entrySet(); 131 Iterator it = es.iterator(); 132 while(it.hasNext()) { 133 Map.Entry entry = (Map.Entry)it.next(); 134 String k = (String)entry.getKey(); 135 String v = (String)entry.getValue(); 136 137 if(!"spbill_create_ip".equals(k)) { 138 sb.append(k + "=" + URLEncoder.encode(v, enc) + "&"); 139 } else { 140 sb.append(k + "=" + v.replace("\\.", "%2E") + "&"); 141 } 142 } 143 144 //去掉最後一個& 145 String reqPars = sb.substring(0, sb.lastIndexOf("&")); 146 147 return this.getGateUrl() + "?" + reqPars; 148 149 } 150 151 public void doSend() throws UnsupportedEncodingException, IOException { 152 this.response.sendRedirect(this.getRequestURL()); 153 } 154 155 /** 156 * 建立md5摘要,規則是:按參數名稱a-z排序,遇到空值的參數不參加簽名。 157 */ 158 protected void createSign() { 159 StringBuffer sb = new StringBuffer(); 160 Set es = this.parameters.entrySet(); 161 Iterator it = es.iterator(); 162 while(it.hasNext()) { 163 Map.Entry entry = (Map.Entry)it.next(); 164 String k = (String)entry.getKey(); 165 String v = (String)entry.getValue(); 166 if(null != v && !"".equals(v) 167 && !"sign".equals(k) && !"key".equals(k)) { 168 sb.append(k + "=" + v + "&"); 169 } 170 } 171 sb.append("key=" + this.getKey()); 172 String enc = TenpayUtil.getCharacterEncoding(this.request, this.response); 173 String sign = MD5Util.MD5Encode(sb.toString(), enc).toUpperCase(); 174 175 this.setParameter("sign", sign); 176 177 } 178 179 protected HttpServletRequest getHttpServletRequest() { 180 return this.request; 181 } 182 183 protected HttpServletResponse getHttpServletResponse() { 184 return this.response; 185 } 186 }
MD5Util
1 /** 2 * 3 */ 4 package com.qtkj.app.weixinpay.util; 5 6 import java.security.MessageDigest; 7 8 /** 9 * @author Zhao 10 * @version 建立時間:2017年10月22日 下午3:24:07 11 * 12 */ 13 /** 14 *<p>Title:MD5Util </p> 15 *<p>Description:</p> 16 *<p>Company:</p> 17 *@author ZHAO 18 *@date 2017年10月22日 19 */ 20 public class MD5Util { 21 /** 22 * MD5加密 23 * @param b 24 * @return 25 */ 26 private static String byteArrayToHexString(byte b[]) { 27 StringBuffer resultSb = new StringBuffer(); 28 for (int i = 0; i < b.length; i++) 29 resultSb.append(byteToHexString(b[i])); 30 31 return resultSb.toString(); 32 } 33 34 private static String byteToHexString(byte b) { 35 int n = b; 36 if (n < 0) 37 n += 256; 38 int d1 = n / 16; 39 int d2 = n % 16; 40 return hexDigits[d1] + hexDigits[d2]; 41 } 42 43 public static String MD5Encode(String origin, String charsetname) { 44 String resultString = null; 45 try { 46 resultString = new String(origin); 47 MessageDigest md = MessageDigest.getInstance("MD5");//MD5加密 48 if (charsetname == null || "".equals(charsetname)){ 49 resultString = byteArrayToHexString(md.digest(resultString.getBytes())); 50 }else{ 51 resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname))); 52 } 53 } catch (Exception exception) { 54 } 55 return resultString; 56 } 57 58 private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", 59 "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; 60 }
TenpayHttpClient
1 package com.qtkj.app.weixinpay.handler; 2 3 import java.io.BufferedOutputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.net.HttpURLConnection; 7 8 import javax.net.ssl.HttpsURLConnection; 9 import javax.net.ssl.SSLContext; 10 import javax.net.ssl.SSLSocketFactory; 11 12 13 import com.qtkj.app.weixinpay.util.HttpClientUtil; 14 15 public class TenpayHttpClient { 16 17 18 19 20 /** 請求內容,不管post和get,都用get方式提供 */ 21 private String reqContent; 22 23 /** 應答內容 */ 24 private String resContent; 25 26 /** 請求方法 */ 27 private String method; 28 29 /** 錯誤信息 */ 30 private String errInfo; 31 32 /** 超時時間,以秒爲單位 */ 33 private int timeOut; 34 35 /** http應答編碼 */ 36 private int responseCode; 37 38 /** 字符編碼 */ 39 private String charset; 40 41 private InputStream inputStream; 42 43 public TenpayHttpClient() { 44 this.reqContent = ""; 45 this.resContent = ""; 46 this.method = "POST"; 47 this.errInfo = ""; 48 this.timeOut = 30;//30秒 49 50 this.responseCode = 0; 51 this.charset = "utf8"; 52 53 this.inputStream = null; 54 } 55 56 /** 57 * 設置請求內容 58 * @param reqContent 表求內容 59 */ 60 public void setReqContent(String reqContent) { 61 this.reqContent = reqContent; 62 } 63 64 /** 65 * 獲取結果內容 66 * @return String 67 * @throws IOException 68 */ 69 public String getResContent() { 70 try { 71 this.doResponse(); 72 } catch (IOException e) { 73 this.errInfo = e.getMessage(); 74 //return ""; 75 } 76 77 return this.resContent; 78 } 79 80 /** 81 * 設置請求方法post或者get 82 * @param method 請求方法post/get 83 */ 84 public void setMethod(String method) { 85 this.method = method; 86 } 87 88 /** 89 * 獲取錯誤信息 90 * @return String 91 */ 92 public String getErrInfo() { 93 return this.errInfo; 94 } 95 96 /** 97 * 設置超時時間,以秒爲單位 98 * @param timeOut 超時時間,以秒爲單位 99 */ 100 public void setTimeOut(int timeOut) { 101 this.timeOut = timeOut; 102 } 103 104 /** 105 * 獲取http狀態碼 106 * @return int 107 */ 108 public int getResponseCode() { 109 return this.responseCode; 110 } 111 112 protected void callHttp() throws IOException { 113 114 if("POST".equals(this.method.toUpperCase())) { 115 String url = HttpClientUtil.getURL(this.reqContent); 116 String queryString = HttpClientUtil.getQueryString(this.reqContent); 117 byte[] postData = queryString.getBytes(this.charset); 118 this.httpPostMethod(url, postData); 119 120 return ; 121 } 122 123 this.httpGetMethod(this.reqContent); 124 125 } 126 127 public boolean callHttpPost(String url, String postdata) { 128 boolean flag = false; 129 byte[] postData; 130 try { 131 postData = postdata.getBytes(this.charset); 132 this.httpPostMethod(url, postData); 133 flag = true; 134 } catch (IOException e1) { 135 e1.printStackTrace(); 136 } 137 return flag; 138 } 139 140 /** 141 * 以http post方式通訊 142 * @param url 143 * @param postData 144 * @throws IOException 145 */ 146 protected void httpPostMethod(String url, byte[] postData) 147 throws IOException { 148 149 HttpURLConnection conn = HttpClientUtil.getHttpURLConnection(url); 150 151 this.doPost(conn, postData); 152 } 153 154 /** 155 * 以http get方式通訊 156 * 157 * @param url 158 * @throws IOException 159 */ 160 protected void httpGetMethod(String url) throws IOException { 161 162 HttpURLConnection httpConnection = 163 HttpClientUtil.getHttpURLConnection(url); 164 165 this.setHttpRequest(httpConnection); 166 167 httpConnection.setRequestMethod("GET"); 168 169 this.responseCode = httpConnection.getResponseCode(); 170 171 this.inputStream = httpConnection.getInputStream(); 172 173 } 174 175 /** 176 * 以https get方式通訊 177 * @param url 178 * @param sslContext 179 * @throws IOException 180 */ 181 protected void httpsGetMethod(String url, SSLContext sslContext) 182 throws IOException { 183 184 SSLSocketFactory sf = sslContext.getSocketFactory(); 185 186 HttpsURLConnection conn = HttpClientUtil.getHttpsURLConnection(url); 187 188 conn.setSSLSocketFactory(sf); 189 190 this.doGet(conn); 191 192 } 193 194 protected void httpsPostMethod(String url, byte[] postData, 195 SSLContext sslContext) throws IOException { 196 197 SSLSocketFactory sf = sslContext.getSocketFactory(); 198 199 HttpsURLConnection conn = HttpClientUtil.getHttpsURLConnection(url); 200 201 conn.setSSLSocketFactory(sf); 202 203 this.doPost(conn, postData); 204 205 } 206 207 /** 208 * 設置http請求默認屬性 209 * @param httpConnection 210 */ 211 protected void setHttpRequest(HttpURLConnection httpConnection) { 212 213 //設置鏈接超時時間 214 httpConnection.setConnectTimeout(this.timeOut * 1000); 215 216 217 //不使用緩存 218 httpConnection.setUseCaches(false); 219 220 //容許輸入輸出 221 httpConnection.setDoInput(true); 222 httpConnection.setDoOutput(true); 223 224 } 225 226 /** 227 * 處理應答 228 * @throws IOException 229 */ 230 protected void doResponse() throws IOException { 231 232 if(null == this.inputStream) { 233 return; 234 } 235 236 //獲取應答內容 237 this.resContent=HttpClientUtil.InputStreamTOString(this.inputStream,this.charset); 238 239 //關閉輸入流 240 this.inputStream.close(); 241 242 } 243 244 /** 245 * post方式處理 246 * @param conn 247 * @param postData 248 * @throws IOException 249 */ 250 protected void doPost(HttpURLConnection conn, byte[] postData) 251 throws IOException { 252 253 // 以post方式通訊 254 conn.setRequestMethod("POST"); 255 256 // 設置請求默認屬性 257 this.setHttpRequest(conn); 258 259 // Content-Type 260 conn.setRequestProperty("Content-Type", 261 "application/x-www-form-urlencoded"); 262 263 BufferedOutputStream out = new BufferedOutputStream(conn 264 .getOutputStream()); 265 266 final int len = 1024; // 1KB 267 HttpClientUtil.doOutput(out, postData, len); 268 269 // 關閉流 270 out.close(); 271 272 // 獲取響應返回狀態碼 273 this.responseCode = conn.getResponseCode(); 274 275 // 獲取應答輸入流 276 this.inputStream = conn.getInputStream(); 277 278 } 279 280 /** 281 * get方式處理 282 * @param conn 283 * @throws IOException 284 */ 285 protected void doGet(HttpURLConnection conn) throws IOException { 286 287 //以GET方式通訊 288 conn.setRequestMethod("GET"); 289 290 //設置請求默認屬性 291 this.setHttpRequest(conn); 292 293 //獲取響應返回狀態碼 294 this.responseCode = conn.getResponseCode(); 295 296 //獲取應答輸入流 297 this.inputStream = conn.getInputStream(); 298 } 299 300 301 }
XMLUtil
1 package com.qtkj.app.weixinpay.util; 2 /** 3 *<p>Title:XMLUtil </p> 4 *<p>Description:</p> 5 *<p>Company:</p> 6 *@author ZHAO 7 *@date 2017年10月22日 8 */ 9 import java.io.IOException; 10 import java.io.InputStream; 11 import java.util.HashMap; 12 import java.util.Iterator; 13 import java.util.List; 14 import java.util.Map; 15 import java.util.Map.Entry; 16 import java.util.Set; 17 18 import org.jdom2.Document; 19 import org.jdom2.Element; 20 import org.jdom2.JDOMException; 21 import org.jdom2.input.SAXBuilder; 22 import java.io.ByteArrayInputStream; 23 public class XMLUtil { 24 /** 25 * 解析xml,返回第一級元素鍵值對。若是第一級元素有子節點,則此節點的值是子節點的xml數據。 26 * @param strxml 27 * @return 28 * @throws JDOMException 29 * @throws IOException 30 */ 31 public static Map doXMLParse(String strxml) throws JDOMException, IOException { 32 strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); 33 if(null == strxml || "".equals(strxml)) { 34 return null; 35 } 36 37 Map m = new HashMap(); 38 39 InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); 40 SAXBuilder builder = new SAXBuilder(); 41 Document doc = builder.build(in); 42 Element root = doc.getRootElement(); 43 List list = root.getChildren(); 44 Iterator it = list.iterator(); 45 while(it.hasNext()) { 46 Element e = (Element) it.next(); 47 String k = e.getName(); 48 String v = ""; 49 List children = e.getChildren(); 50 if(children.isEmpty()) { 51 v = e.getTextNormalize(); 52 } else { 53 v = XMLUtil.getChildrenText(children); 54 } 55 56 m.put(k, v); 57 } 58 59 //關閉流 60 in.close(); 61 62 return m; 63 } 64 65 /** 66 * 獲取子結點的xml 67 * @param children 68 * @return String 69 */ 70 public static String getChildrenText(List children) { 71 StringBuffer sb = new StringBuffer(); 72 if(!children.isEmpty()) { 73 Iterator it = children.iterator(); 74 while(it.hasNext()) { 75 Element e = (Element) it.next(); 76 String name = e.getName(); 77 String value = e.getTextNormalize(); 78 List list = e.getChildren(); 79 sb.append("<" + name + ">"); 80 if(!list.isEmpty()) { 81 sb.append(XMLUtil.getChildrenText(list)); 82 } 83 sb.append(value); 84 sb.append("</" + name + ">"); 85 } 86 } 87 88 return sb.toString(); 89 } 90 91 /** 92 * 獲取xml編碼字符集 93 * @param strxml 94 * @return 95 * @throws IOException 96 * @throws JDOMException 97 */ 98 public static String getXMLEncoding(String strxml) throws JDOMException, IOException { 99 InputStream in = HttpClientUtil.String2Inputstream(strxml); 100 SAXBuilder builder = new SAXBuilder(); 101 Document doc = builder.build(in); 102 in.close(); 103 return (String)doc.getProperty("encoding"); 104 } 105 106 /** 107 * 支付成功,返回微信那服務器 108 * @param return_code 109 * @param return_msg 110 * @return 111 */ 112 public static String setXML(String return_code, String return_msg) { 113 return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; 114 } 115 116 public static String createXML(Map<String,Object> map){ 117 Set<Entry<String,Object>> set=map.entrySet(); 118 set.iterator(); 119 return null; 120 } 121 122 }
WXUtil
1 package com.qtkj.app.weixinpay.util; 2 3 import java.util.Random; 4 5 public class WXUtil { 6 /** 7 * 生成隨機字符串 8 * @return 9 */ 10 public static String getNonceStr() { 11 Random random = new Random(); 12 return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "utf8"); 13 } 14 /** 15 * 獲取時間戳 16 * @return 17 */ 18 public static String getTimeStamp() { 19 return String.valueOf(System.currentTimeMillis() / 1000); 20 } 21 }
HttpClientUtil
1 package com.qtkj.app.weixinpay.util; 2 import java.io.ByteArrayOutputStream; 3 import java.io.BufferedReader; 4 import java.io.ByteArrayInputStream; 5 import java.io.ByteArrayOutputStream; 6 import java.io.FileInputStream; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.io.OutputStream; 10 import java.net.HttpURLConnection; 11 import java.net.URL; 12 import java.security.KeyManagementException; 13 import java.security.KeyStore; 14 import java.security.KeyStoreException; 15 import java.security.NoSuchAlgorithmException; 16 import java.security.SecureRandom; 17 import java.security.UnrecoverableKeyException; 18 import java.security.cert.CertificateException; 19 import java.util.HashMap; 20 import java.util.Map; 21 22 import javax.net.ssl.HttpsURLConnection; 23 import javax.net.ssl.KeyManagerFactory; 24 import javax.net.ssl.SSLContext; 25 import javax.net.ssl.TrustManagerFactory; 26 27 28 public class HttpClientUtil { 29 /** 30 * http客戶端工具類 31 * 32 */ 33 public static final String SunX509 = "SunX509"; 34 public static final String JKS = "JKS"; 35 public static final String PKCS12 = "PKCS12"; 36 public static final String TLS = "TLS"; 37 38 /** 39 * get HttpURLConnection 40 * @param strUrl url地址 41 * @return HttpURLConnection 42 * @throws IOException 43 */ 44 public static HttpURLConnection getHttpURLConnection(String strUrl) 45 throws IOException { 46 URL url = new URL(strUrl); 47 HttpURLConnection httpURLConnection = (HttpURLConnection) url 48 .openConnection(); 49 return httpURLConnection; 50 } 51 52 /** 53 * get HttpsURLConnection 54 * @param strUrl url地址ַ 55 * @return HttpsURLConnection 56 * @throws IOException 57 */ 58 public static HttpsURLConnection getHttpsURLConnection(String strUrl) 59 throws IOException { 60 URL url = new URL(strUrl); 61 HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url 62 .openConnection(); 63 return httpsURLConnection; 64 } 65 66 /** 67 * 獲取不帶查詢串的url 68 * @param strUrl 69 * @return String 70 */ 71 public static String getURL(String strUrl) { 72 73 if(null != strUrl) { 74 int indexOf = strUrl.indexOf("?"); 75 if(-1 != indexOf) { 76 return strUrl.substring(0, indexOf); 77 } 78 79 return strUrl; 80 } 81 82 return strUrl; 83 84 } 85 86 /** 87 * 獲取查詢串 88 * @param strUrl 89 * @return String 90 */ 91 public static String getQueryString(String strUrl) { 92 93 if(null != strUrl) { 94 int indexOf = strUrl.indexOf("?"); 95 if(-1 != indexOf) { 96 return strUrl.substring(indexOf+1, strUrl.length()); 97 } 98 99 return ""; 100 } 101 102 return strUrl; 103 } 104 105 /** 106 * 查詢字符串轉化爲map 107 * name1=key1&name2=key2&... 108 * @param queryString 109 * @return 110 */ 111 public static Map queryString2Map(String queryString) { 112 if(null == queryString || "".equals(queryString)) { 113 return null; 114 } 115 116 Map m = new HashMap(); 117 String[] strArray = queryString.split("&"); 118 for(int index = 0; index < strArray.length; index++) { 119 String pair = strArray[index]; 120 HttpClientUtil.putMapByPair(pair, m); 121 } 122 123 return m; 124 125 } 126 127 /** 128 * 把鍵值添加到map 129 * pair:name=value 130 * @param pair name=value 131 * @param m 132 */ 133 public static void putMapByPair(String pair, Map m) { 134 135 if(null == pair || "".equals(pair)) { 136 return; 137 } 138 139 int indexOf = pair.indexOf("="); 140 if(-1 != indexOf) { 141 String k = pair.substring(0, indexOf); 142 String v = pair.substring(indexOf+1, pair.length()); 143 if(null != k && !"".equals(k)) { 144 m.put(k, v); 145 } 146 } else { 147 m.put(pair, ""); 148 } 149 } 150 /** 151 * BufferedReader轉換成String<br/> 152 * 注意:流關閉須要自行處理 153 * @param reader 154 * @return 155 * @throws IOException 156 */ 157 public static String bufferedReader2String(BufferedReader reader) throws IOException { 158 StringBuffer buf = new StringBuffer(); 159 String line = null; 160 while( (line = reader.readLine()) != null) { 161 buf.append(line); 162 buf.append("\r\n"); 163 } 164 165 return buf.toString(); 166 } 167 /** 168 * 處理輸出<br/> 169 * 注意:流關閉須要自行處理 170 * @param out 171 * @param data 172 * @param len 173 * @throws IOException 174 */ 175 public static void doOutput(OutputStream out, byte[] data, int len) 176 throws IOException { 177 int dataLen = data.length; 178 int off = 0; 179 while (off < data.length) { 180 if (len >= dataLen) { 181 out.write(data, off, dataLen); 182 off += dataLen; 183 } else { 184 out.write(data, off, len); 185 off += len; 186 dataLen -= len; 187 } 188 189 // ˢ�»����� 190 out.flush(); 191 } 192 193 } 194 /** 195 * 獲取SSLContext 196 * @param trustFile 197 * @param trustPasswd 198 * @param keyFile 199 * @param keyPasswd 200 * @return 201 * @throws NoSuchAlgorithmException 202 * @throws KeyStoreException 203 * @throws IOException 204 * @throws CertificateException 205 * @throws UnrecoverableKeyException 206 * @throws KeyManagementException 207 */ 208 public static SSLContext getSSLContext( 209 FileInputStream trustFileInputStream, String trustPasswd, 210 FileInputStream keyFileInputStream, String keyPasswd) 211 throws NoSuchAlgorithmException, KeyStoreException, 212 CertificateException, IOException, UnrecoverableKeyException, 213 KeyManagementException { 214 215 // ca 216 TrustManagerFactory tmf = TrustManagerFactory.getInstance(HttpClientUtil.SunX509); 217 KeyStore trustKeyStore = KeyStore.getInstance(HttpClientUtil.JKS); 218 trustKeyStore.load(trustFileInputStream, HttpClientUtil 219 .str2CharArray(trustPasswd)); 220 tmf.init(trustKeyStore); 221 222 final char[] kp = HttpClientUtil.str2CharArray(keyPasswd); 223 KeyManagerFactory kmf = KeyManagerFactory.getInstance(HttpClientUtil.SunX509); 224 KeyStore ks = KeyStore.getInstance(HttpClientUtil.PKCS12); 225 ks.load(keyFileInputStream, kp); 226 kmf.init(ks, kp); 227 228 SecureRandom rand = new SecureRandom(); 229 SSLContext ctx = SSLContext.getInstance(HttpClientUtil.TLS); 230 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), rand); 231 232 return ctx; 233 } 234 235 /** 236 * 字符串轉換成char數組 237 * @param str 238 * @return char[] 239 */ 240 public static char[] str2CharArray(String str) { 241 if(null == str) return null; 242 243 return str.toCharArray(); 244 } 245 246 public static InputStream String2Inputstream(String str) { 247 return new ByteArrayInputStream(str.getBytes()); 248 } 249 250 /** 251 * InputStream轉換成Byte 252 * 注意:流關閉須要自行處理 253 * @param in 254 * @return byte 255 * @throws Exception 256 */ 257 public static byte[] InputStreamTOByte(InputStream in) throws IOException{ 258 259 int BUFFER_SIZE = 4096; 260 ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 261 byte[] data = new byte[BUFFER_SIZE]; 262 int count = -1; 263 264 while((count = in.read(data,0,BUFFER_SIZE)) != -1) 265 outStream.write(data, 0, count); 266 267 data = null; 268 byte[] outByte = outStream.toByteArray(); 269 outStream.close(); 270 271 return outByte; 272 } 273 274 /** 275 * InputStream轉換成String 276 * 注意:流關閉須要自行處理 277 * @param in 278 * @param encoding 編碼 279 * @return String 280 * @throws Exception 281 */ 282 public static String InputStreamTOString(InputStream in,String encoding) throws IOException{ 283 284 return new String(InputStreamTOByte(in),encoding); 285 286 } 287 288 }
三.控制層的接入(Controller)
1.Controller層的代碼實現過程
1 package com.qtkj.app.weixinpay.controller; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.PrintWriter; 7 import java.io.UnsupportedEncodingException; 8 import java.math.BigDecimal; 9 import java.text.ParseException; 10 import java.text.SimpleDateFormat; 11 import java.util.Date; 12 import java.util.HashMap; 13 import java.util.LinkedHashMap; 14 import java.util.Map; 15 16 import javax.servlet.http.HttpServletRequest; 17 import javax.servlet.http.HttpServletResponse; 18 19 import org.jdom2.JDOMException; 20 import org.springframework.beans.factory.annotation.Autowired; 21 import org.springframework.stereotype.Controller; 22 import org.springframework.ui.Model; 23 import org.springframework.web.bind.annotation.RequestMapping; 24 import org.springframework.web.bind.annotation.RequestMethod; 25 import org.springframework.web.bind.annotation.RequestParam; 26 import org.springframework.web.bind.annotation.ResponseBody; 27 28 import com.qtkj.admin.common.CacheXmlConfig; 29 import com.qtkj.admin.settings.entity.SiteConfig; 30 import com.qtkj.admin.user.entity.User; 31 import com.qtkj.admin.user.service.UserService; 32 import com.qtkj.app.weixinpay.handler.ClientRequestHandler; 33 import com.qtkj.app.weixinpay.handler.PrepayIdRequestHandler; 34 import com.qtkj.app.weixinpay.util.ConstantUtil; 35 import com.qtkj.app.weixinpay.util.MD5Util; 36 import com.qtkj.app.weixinpay.util.TenpayUtil; 37 import com.qtkj.app.weixinpay.util.UUID; 38 import com.qtkj.app.weixinpay.util.WXUtil; 39 import com.qtkj.app.weixinpay.util.XMLUtil; 40 import com.qtkj.user.entity.Trade; 41 import com.qtkj.user.service.TradeService; 42 import com.qtkj.util.NumberUtils; 43 import com.qtkj.util.PageHelper; 44 45 46 /** 47 *<p>Title:WeiXinPayController </p> 48 *<p>Description:</p> 49 *<p>Company:</p> 50 *@author ZHAO 51 *@date 2017年10月27日 52 */ 53 @Controller 54 public class WeiXinPayController { 55 56 @Autowired 57 private TradeService tradeservice; 58 59 @Autowired 60 private TradeService tradeService; 61 62 @Autowired 63 private UserService userService; 64 65 //------------------------------------------------------------------------------------------------------------------------------------------------------------------ 66 67 /** 68 * 9.1.生成訂單 69 *@author Zhao 70 *@date 2017年10月31日 71 *@param request 72 *@param response 73 *@param model 74 *@param legalMoney 充值法幣金額(legalMoney) 75 *@param totalPrice 充值金額(RMB) 76 *@param userId 用戶id 77 *@return 78 *@throws Exception 79 */ 80 @RequestMapping("api/weixin/createOrder") 81 @ResponseBody 82 public Model doWeinXinRequest(HttpServletRequest request,HttpServletResponse response,Model model, 83 @RequestParam("totalPrice") String totalPrice, 84 @RequestParam("legalMoney") String legalMoney, 85 @RequestParam("userId") String userId 86 ) throws Exception { 87 Map<String,Object> resultMap = new LinkedHashMap<>(); 88 try { 89 90 //---------------2 生成訂單號 開始------------------------ 91 //2.1.當前時間 yyyyMMddHHmmss 92 String currTime = TenpayUtil.getCurrTime(); 93 //2.2 8位日期 94 String strTime = currTime.substring(8, currTime.length()); 95 //2.3四位隨機數 96 String strRandom = TenpayUtil.buildRandom(4) + ""; 97 //2.4 10位序列號,能夠自行調整。 98 String strReq = strTime + strRandom; 99 //2.5 訂單號,此處用時間加隨機數生成,商戶根據本身狀況調整,只要保持全局惟一就行 100 String out_trade_no = strReq; 101 //---------------生成訂單號 結束 ------------------------ 102 103 //3.獲取生成預支付訂單的請求類 104 PrepayIdRequestHandler prepayReqHandler = new PrepayIdRequestHandler(request, response); 105 106 //3.1封裝數據 107 String nonce_str = WXUtil.getNonceStr(); //訂單號 108 out_trade_no = String.valueOf(UUID.next()); 109 String timestamp = WXUtil.getTimeStamp(); //超時時間 110 111 //3.2---------------------------------------------- ***** 統一下單開始 ***** ----------------------------------------------------------- 112 prepayReqHandler.setParameter("appid", ConstantUtil.APP_ID); //平臺應用appId 113 prepayReqHandler.setParameter("mch_id", ConstantUtil.MCH_ID); //商戶號 114 prepayReqHandler.setParameter("nonce_str", nonce_str); //隨機字符串 115 prepayReqHandler.setParameter("body", ConstantUtil.BODY); //商品描述 116 prepayReqHandler.setParameter("out_trade_no", out_trade_no); //訂單號 117 prepayReqHandler.setParameter("total_fee",String.valueOf(totalPrice)); //訂單價格 118 prepayReqHandler.setParameter("spbill_create_ip", request.getRemoteAddr()); //獲取客戶端ip 119 prepayReqHandler.setParameter("notify_url", ConstantUtil.NOTIFY_URL); //回調通知 120 prepayReqHandler.setParameter("trade_type", "APP"); //支付類型 121 prepayReqHandler.setParameter("time_start", timestamp); //時間戳 122 prepayReqHandler.setGateUrl(ConstantUtil.GATEURL); //設置預支付id的接口url 123 124 //3.3 注意簽名(sign)的生成方式,具體見官方文檔(傳參都要參與生成簽名,且參數名按照字典序排序,最後接上APP_KEY,轉化成大寫) 125 prepayReqHandler.setParameter("sign", prepayReqHandler.createMD5Sign()); //sign 簽名 126 127 //3.4 提交預支付,獲取prepayid 128 String prepayid = prepayReqHandler.sendPrepay(); 129 //---------------------------------------------- ***** 統一下單 結束 ***** -------------------------------------------------------------- 130 131 //3.5 若獲取prepayid成功,將相關信息返回客戶端 132 if (prepayid != null && !prepayid.equals("")) { 133 134 135 //---------------4.封裝訂單數據開始 ------------------------ 136 此處用於封裝你本身實體類的訂單信息 137 138 例:Trade trade = new Trade(); 139 140 //---------------4.封裝訂單數據開始 ------------------------ 141 142 String signs = 143 "appid=" + ConstantUtil.APP_ID + 144 "&noncestr=" + nonce_str + 145 "&package=Sign=WXPay"+ 146 "&partnerid="+ ConstantUtil.PARTNER_ID + 147 "&prepayid=" + prepayid + 148 "×tamp=" + timestamp+ 149 "&key="+ ConstantUtil.APP_KEY; 150 151 resultMap.put("appid", ConstantUtil.APP_ID); 152 resultMap.put("partnerid", ConstantUtil.PARTNER_ID); //商家id 153 resultMap.put("prepayid", prepayid); //預支付id 154 resultMap.put("package", "Sign=WXPay"); //固定常量 155 resultMap.put("noncestr", nonce_str); //與請求prepayId時值一致 156 resultMap.put("timestamp", timestamp); //等於請求prepayId時的time_start 157 resultMap.put("sign", MD5Util.MD5Encode(signs, "utf8").toUpperCase());//簽名方式與上面相似 158 model.addAttribute("orderNum", out_trade_no); 159 model.addAttribute("resultMap", resultMap); 160 model.addAttribute("msg", "獲取prepayid成功,生成訂單成功"); 161 model.addAttribute("status",0); 162 }else { 163 model.addAttribute("msg", "獲取prepayid失敗"); 164 model.addAttribute("status",1); 165 } 166 } catch (Exception e) { 167 model.addAttribute("msg", "訂單生成失敗"); 168 model.addAttribute("status",2); 169 } 170 return model; 171 } 172 173 /** 174 * 9.2 接收微信支付成功通知 175 * @param request 176 * @param response 177 * @throws IOException 178 * @throws java.io.IOException 179 * @throws ParseException 180 */ 181 @RequestMapping(value = "api/weixin/notify") 182 public void getnotify(HttpServletRequest request, HttpServletResponse response) 183 throws IOException, ParseException { 184 System.err.println("微信支付回調"); 185 System.err.println("微信支付回調"); 186 //1.建立輸入輸出流 187 PrintWriter writer = response.getWriter(); 188 InputStream inStream = request.getInputStream(); 189 ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 190 byte[] buffer = new byte[1024]; 191 int len = 0; 192 while ((len = inStream.read(buffer)) != -1) { 193 outSteam.write(buffer, 0, len); 194 } 195 outSteam.close(); 196 inStream.close(); 197 //2.將結果轉換 198 String result = new String(outSteam.toByteArray(), "utf-8"); 199 System.out.println("微信支付通知結果:" + result); 200 Map<String, String> map = null; 201 try { 202 //3.解析微信通知返回的信息 203 map = XMLUtil.doXMLParse(result); 204 System.err.println(map); 205 } catch (JDOMException e) { 206 e.printStackTrace(); 207 } 208 // 4.若支付成功,則告知微信服務器收到通知 209 if (map.get("return_code").equals("SUCCESS")) { 210 if (map.get("result_code").equals("SUCCESS")) { 211 System.out.println("充值成功!"); 212 //4.1 修改當前訂單狀態爲:付款成功 2 213 System.err.println("交易號:"+Long.valueOf(map.get("out_trade_no"))); 214 // Trade trade = tradeservice.selectByOrderNumber((String)(map.get("out_trade_no"))); 215 Trade trade = tradeservice.selectByTradeNumber((String)(map.get("out_trade_no"))); 216 if(trade !=null){ 217 trade.setTradeType((byte)2); //設置狀態 218 trade.setPaymentTime(new Date()); 219 trade.setPayTime(new Date()); 220 if(tradeservice.updateByPrimaryKeySelective(trade) > 0){ 221 //更新成功 222 System.err.println("通知微信後臺"); 223 String notifyStr = XMLUtil.setXML("SUCCESS", ""); 224 writer.write(notifyStr); 225 writer.flush(); 226 } 227 }else{ 228 String notifyStr = XMLUtil.setXML("ERROR", ""); 229 writer.write(notifyStr); 230 writer.flush(); 231 } 232 } 233 } 234 } 235 236 /** 237 *微信支付成功後.通知頁面 238 *@author Zhao 239 *@date 2017年11月2日 240 *@param request 241 *@return 242 *@throws UnsupportedEncodingException 243 */ 244 @RequestMapping(value="api/weixin/return",method={RequestMethod.POST,RequestMethod.GET}) 245 @ResponseBody 246 public Model returnUrl(@RequestParam("id") String id,HttpServletRequest request,Model model) throws UnsupportedEncodingException { 247 System.err.println("。。。。。。 微信同步通知 。。。。。。"); 248 System.err.println("。。。。。。 微信同步通知 。。。。。。"); 249 System.err.println("。。。。。。 微信同步通知 。。。。。。"); 250 Map returnMap = new HashMap(); 251 try { 252 253 Trade trade = tradeservice.selectByTradeNumber(id); 254 // 返回值Map 255 if(trade !=null && trade.getTradeStatus() == 2){ 256 257 User user = userService.selectByPrimaryKey(trade.gettUserId()); 258 returnMap.put("tradeType", trade.getTradeType()); //支付方式 259 returnMap.put("phoneNum", user.getPhoneNumber()); //支付賬號 260 returnMap.put("tradeMoney", trade.getTradeMoney()+""); //訂單金額 261 }else{ 262 model.addAttribute("msg", "查詢失敗"); 263 model.addAttribute("status", 0); 264 } 265 model.addAttribute("returnMap", returnMap); 266 System.err.println(returnMap); 267 model.addAttribute("msg", "查詢成功"); 268 model.addAttribute("status", 0); 269 } catch (Exception e) { 270 model.addAttribute("msg", "查詢失敗"); 271 model.addAttribute("status", 1); 272 } 273 274 return model; 275 } 276 }
2.微信的支付注意事項
1.在第1個接口中,生成訂單時,是簽名2次,將數據返回給app端的,手機端能夠直接用來向微信發起支付
2.第2個接口是app端支付成功後,微信的服務器須要回調你的這個接口,因此這個接口的地址要公網能夠測,具體見上一篇的支付寶支付中的(nei wang chuan tou),這裏
還可能出現驗籤失敗等狀況,微信特別坑,就返回 -1,而後就沒有提示信息了,因此但願看官不要急,先去查找一下本身的參數有沒有問題,必要時能夠一個一個對一遍,避免因
爲不認真形成失誤。
3.第3個接口是在第2個接口返回給微信服務器信息後(這個信息是確認我確實收到錢了),手機端支付成功後跳轉頁面時所須要的數據,這個接口根據本身的業務須要把
數據返回給手機端便可。
四.測試
微信支付在app端支付成功後,要調用異步通知,微信官方並未提供沙箱環境,所以須要訪問的url必須是外網能夠訪問的,在這裏推薦一款內網穿透工具natapp,須要用身份證號驗證,測試個支付是沒問題的
附:
1. natapp官網: https://natapp.cn
2.natapp 1分鐘新手圖文教程: https://natapp.cn/article/natapp_newbie
因爲本人能力水平有限,理解能爲通常,有不當之處,請名位看官批評指正!!!