微信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("<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
由于本人能力水平有限,理解能为一般,有不当之处,请名位看官批评指正!!!