微信app支付java后台流程、原理分析及nei网穿透

一.流程步骤

  本实例是基于springmvc框架编写

  1.执行流程
           当手机端app(就是你公司开发的app)在支付页面时,调起服务端(后台第1个创建订单接口)接口,后台把需要调起微信支付的参数返回给手机端,手机端拿到
         这些参数后,拉起微信支付环境完成支付,完成支付后会调异步通知(第2个接口),此时需要给微信返回成功或者失败信息,成功后,由app端调用同步通知(第3个接口)
         返回支付成功页面,完成整个支付流程。
        
      2.需要说明的事项

        因为微信支付都是用自己工具类生成加密、解析xml、验签等方法,需要写的类比较多,因此大家在参考此文档时,直接复制就行了

二.配置文件及通用类

  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("");
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+"");
49         }
50         sb.append("");
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 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 *

Title:MD5Util

15 *

Description:

16 *

Company:

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 *

Title:XMLUtil

4 *

Description:

5 *

Company:

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(""); 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 ""; 114 } 115 116 public static String createXML(Map map){ 117 Set> 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
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 * 处理输出
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 *

Title:WeiXinPayController

48 *

Description:

49 *

Company:

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 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 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

 

  由于本人能力水平有限,理解能为一般,有不当之处,请名位看官批评指正!!!

  

 

    

转载于:https://www.cnblogs.com/MrRightZhao/p/7930916.html

你可能感兴趣的:(微信app支付java后台流程、原理分析及nei网穿透)