-----------------------------------------------------------------------------------------------1.微信 手机网页支付 流程图------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------2.前台页面--------------------------------------------------------------------------------------------------
根据上面的流程,下面一步一步的实现这个微信支付的逻辑。
前台页面 userPayView.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>模拟支付页面--微信支付/支付宝支付</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> </head> <body> <input type="text" value="" name="openID"/> <!-- 微信 所需OpenID --> <input type="text" value="" name="orderID"/><!-- 微信 支付宝[out_trade_no] --> <input type="number" value="" name="money"/><!-- 微信[分为单位,不允许小数] 支付宝[total_amount 元为单位,精确到小数点后2位] 商品价格 --> <input type="text" value="" name="subject"/><!-- 支付宝 商品的标题/交易标题/订单标题/订单关键字等。 --> <input type="text" value="" name="product_code"/><!-- 支付宝 销售产品码,商家和支付宝签约的产品码。该产品请填写固定值:QUICK_WAP_WAY。 --> <input type="text" value="" name="body"/><!-- 支付宝 商品描述 --> <button class="payButton">微信支付</button> <button class="alipayButton">支付宝支付</button> </body> <script type="text/javascript" src="/resources/bootstrap-3.3.5-dist/js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="/wx/pay/pay.js"></script> </html>
微信支付按钮的点击事件 pay.js
/** * 微信支付按钮的点击事件 */ $(".payButton").click(function(){ var openID = $("input[name='openID']").val(); var orderID = $("inpuut[name='orderID']").val(); var money = $("input[name='money']").val(); /** * ①ajax 微信支付按钮点击事件 */ $.ajax({ url: "/wx/PayOrder/unifiedOrder.jhtml", type:"GET", data: {"openId":openID,"orderId":orderID,"money":money }, traditional:true, success: function(response){ if(response.length > 0){ /** * ⑤获取到prepayId 继续ajax请求商户服务器 */ $.ajax({ url: "/wx/PayOrder/createPayConfig.jhtml", type:"GET", data: {"prepayId":response }, traditional:true, success: function(response){ var data = eval('(' + response + ')'); var obj = data.msg; /** * ⑦ 根据支付配置,使用微信浏览器内置对象WeixinJSBridge 调用支付页面,完成支付操作,将支付结果返回给①中配置的回调函数 */ WeixinJSBridge.invoke('getBrandWCPayRequest',{ "appId" : obj.appId, //公众号名称,由商户传入 "timeStamp":obj.timestamp, //时间戳,自 1970 年以来的秒数 "nonceStr" : obj.nonce, //随机串 "package" : obj.packageName, //商品包信息 prepay_Id拼接值 "signType" : obj.signType, //微信签名方式 "paySign" : obj.signature //微信签名 },function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { alert('支付成功'); }else if(res.err_msg == "get_brand_wcpay_request:cancel"){ alert("支付过程中用户取消"); }else if(res.err_msg == "get_brand_wcpay_request:fail"){ alert("支付失败"); } }); } }); } }}); });
----------------------------------------------------------------------------------------------3.获取prepay_id时的配置实体类 也就是统一下单实体类-------------------------------------------------------------------------------------------------
统一下单 入参 参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
package net.shopxx.wx.pay; /** * 微信支付 统一下单 接口提供必填参数的实体类 * @author SXD * */ public class Unifiedorder { /** * 公众账号ID */ private String appid; /** * 商户号 */ private String mch_id; /** * 随机字符串,长度要求在32位以内 */ private String nonce_str; /** * 签名 通过签名算法计算得出的签名值 */ private String sign; /** * 商品描述 */ private String body; /** * 商户系统内部订单号 要求32个字符内 */ private String out_trade_no; /** * 本次支付总金额 单位为分 不能带小数点 */ private Integer total_fee; /** * 用户IP */ private String spbill_create_ip; /** * 通知地址 * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数 */ private String notify_url; /** * 交易类型 * 取值如下:JSAPI,NATIVE,APP等 * JSAPI 公众号支付 * NATIVE 扫描二维码支付 * APP app支付 */ private String trade_type; /** * 用户标识 * trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识 */ private String openid; public Unifiedorder() { this.trade_type = "JSAPI";//公众号支付的方式 } public String getAppid() { return appid; } public void setAppid(String appid) { this.appid = appid; } public String getMch_id() { return mch_id; } public void setMch_id(String mch_id) { this.mch_id = mch_id; } public String getNonce_str() { return nonce_str; } public void setNonce_str(String nonce_str) { this.nonce_str = nonce_str; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getOut_trade_no() { return out_trade_no; } public void setOut_trade_no(String out_trade_no) { this.out_trade_no = out_trade_no; } public Integer getTotal_fee() { return total_fee; } public void setTotal_fee(Integer total_fee) { this.total_fee = total_fee; } public String getSpbill_create_ip() { return spbill_create_ip; } public void setSpbill_create_ip(String spbill_create_ip) { this.spbill_create_ip = spbill_create_ip; } public String getNotify_url() { return notify_url; } public void setNotify_url(String notify_url) { this.notify_url = notify_url; } public String getTrade_type() { return trade_type; } public void setTrade_type(String trade_type) { this.trade_type = trade_type; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } }
----------------------------------------------------------------------------------------------4.微信支付的实体类 也就是JS-SDK使用的配置信息实体类----------------------------------------------------------------------------------------------
微信内H5调起支付 配置信息 入参 参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
package net.shopxx.wx.pay; /** * 微信支付 * JS-SDK使用的配置信息 实体 * @author SXD * */ public class JsAPIConfig { /** * 公众号id */ private String appId; /** * 时间戳 */ private String timeStamp; /** * 随机字符串 */ private String nonceStr; /** * 订单详情扩展字符串 prepay_id=*** */ private String packageStr; /** * 签名方式 暂支持MD5 */ private String signType; /** * 签名 * * 签名算法 * 第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。 特别注意以下重要规则: ◆ 参数名ASCII码从小到大排序(字典序); ◆ 如果参数的值为空不参与签名; ◆ 参数名区分大小写; ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。 ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段 第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 * */ private String paySign; public JsAPIConfig(){ signType = "MD5"; } public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getPackageStr() { return packageStr; } public void setPackageStr(String packageStr) { this.packageStr = packageStr; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } public String getPaySign() { return paySign; } public void setPaySign(String paySign) { this.paySign = paySign; } }
----------------------------------------------------------------------------------------------5.商户处理后同步返回给微信 订单成功的 实体类--------------------------------------------------------------------------------------------------------
返回信息实体 参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 返回结果
package net.shopxx.wx.pay; /** * 微信支付 商户处理后同步返回给微信 订单成功的 实体 * @author SXD * */ public class PayCallback { private String return_code; private String return_msg; public PayCallback() { this.return_code = "SUCCESS"; this.return_msg = "OK"; } public String getReturn_code() { return return_code; } public void setReturn_code(String return_code) { this.return_code = return_code; } public String getReturn_msg() { return return_msg; } public void setReturn_msg(String return_msg) { this.return_msg = return_msg; } }
----------------------------------------------------------------------------------------------6.HTTP通信类 在后台访问微信服务器的 通信工具类----------------------------------------------------------------------------------------------------
package net.shopxx.wx.pay; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.Iterator; import java.util.Map; import javax.activation.MimetypesFileTypeMap; import org.springframework.stereotype.Component; /** * 微信支付 * HTTP通信类 * */ @Component public class HttpConnection { /** * 向指定URL发送GET方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public String get(String url, String param) throws Exception { String urlName = url + "?" + param; return get(urlName); } /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @return URL所代表远程资源的响应 */ public String get(String url) throws Exception { String result = ""; BufferedReader in = null; URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8"); conn.setRequestProperty("Content-Type", "text/xml;charset=utf-8"); conn.setRequestProperty("connection", "keep-alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 建立实际的连接 conn.connect(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line; while ((line = in.readLine()) != null) { result += line; } in.close(); return result; } /** * 向指定URL发送POST方法的请求 * @param url 发送请求的URL * @param content 内容 * @return URL所代表远程资源的响应 * @throws Exception */ public String post(String url,String content) throws Exception{ String result = ""; URL postUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) postUrl .openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestMethod("POST"); connection.setUseCaches(false); connection.setInstanceFollowRedirects(true); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.connect(); DataOutputStream out = new DataOutputStream(connection .getOutputStream()); // out.writeBytes(content); out.write(content.getBytes("UTF-8")); out.flush(); out.close(); // flush and close BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"utf-8"));//设置编码,否则中文乱码 String line=""; while ((line = reader.readLine()) != null){ result += line; } reader.close(); connection.disconnect(); return result; } /** * 向指定URL发送POST方法的请求 * @Title: post * @Description: TODO * @param @param url * @param @param textMap * @param @return * @return String * @throws */ public String post(String url, Map<String, String> textMap){ String res = ""; HttpURLConnection conn = null; String BOUNDARY = "---------------------------123821742118716"; //boundary就是request头和上传文件内容的分隔符 try { URL postUrl = new URL(url); conn = (HttpURLConnection) postUrl.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); OutputStream out = new DataOutputStream(conn.getOutputStream()); // text if (textMap != null) { StringBuffer strBuf = new StringBuffer(); Iterator iter = textMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } strBuf.append("\r\n").append("--").append(BOUNDARY).append( "\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); } out.write(strBuf.toString().getBytes()); } byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes(); out.write(endData); out.flush(); out.close(); // 读取返回数据 StringBuffer strBuf = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader( conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { strBuf.append(line).append("\n"); } res = strBuf.toString(); reader.close(); reader = null; } catch (Exception e) { System.out.println("发送POST请求出错。" + url); e.printStackTrace(); } finally { if (conn != null) { conn.disconnect(); conn = null; } } return res; } /** * 向指定URL发送POST方法的请求 (带文件) * @param url 发送请求的URL * @param textMap 文本参数键值 * @param fileMap 文件键值 * @return URL所代表远程资源的响应 * @throws Exception */ public String filePost(String url, Map<String, String> textMap, Map<String, String> fileMap) { String res = ""; HttpURLConnection conn = null; String BOUNDARY = "---------------------------123821742118716"; //boundary就是request头和上传文件内容的分隔符 try { URL postUrl = new URL(url); conn = (HttpURLConnection) postUrl.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(30000); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); conn.setRequestProperty("Connection", "Keep-Alive"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)"); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); OutputStream out = new DataOutputStream(conn.getOutputStream()); // text if (textMap != null) { StringBuffer strBuf = new StringBuffer(); Iterator iter = textMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } strBuf.append("\r\n").append("--").append(BOUNDARY).append( "\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n"); strBuf.append(inputValue); } out.write(strBuf.toString().getBytes()); } // file if (fileMap != null) { Iterator iter = fileMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String inputName = (String) entry.getKey(); String inputValue = (String) entry.getValue(); if (inputValue == null) { continue; } File file = new File(inputValue); String filename = file.getName(); String contentType = new MimetypesFileTypeMap() .getContentType(file); if (filename.endsWith(".png")) { contentType = "image/png"; } if (contentType == null || contentType.equals("")) { contentType = "application/octet-stream"; } StringBuffer strBuf = new StringBuffer(); strBuf.append("\r\n").append("--").append(BOUNDARY).append( "\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n"); strBuf.append("Content-Type:" + contentType + "\r\n\r\n"); out.write(strBuf.toString().getBytes()); DataInputStream in = new DataInputStream( new FileInputStream(file)); int bytes = 0; byte[] bufferOut = new byte[1024]; while ((bytes = in.read(bufferOut)) != -1) { out.write(bufferOut, 0, bytes); } in.close(); } } byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes(); out.write(endData); out.flush(); out.close(); // 读取返回数据 StringBuffer strBuf = new StringBuffer(); BufferedReader reader = new BufferedReader(new InputStreamReader( conn.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { strBuf.append(line).append("\n"); } res = strBuf.toString(); reader.close(); reader = null; } catch (Exception e) { System.out.println("发送POST请求出错。" + url); e.printStackTrace(); } finally { if (conn != null) { conn.disconnect(); conn = null; } } return res; } }
----------------------------------------------------------------------------------------------7.微信 签名 规则 工具类-------------------------------------------------------------------------------------------------------------------------------------------
签名算法 规则:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
package net.shopxx.wx.pay; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import javax.net.ssl.SSLContext; import javax.security.cert.CertificateException; import javax.servlet.http.HttpServletRequest; import org.apache.commons.codec.digest.DigestUtils; import org.apache.http.ssl.SSLContexts; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; /** * 微信支付 微信公众号发红包 * 工具类 * @author SXD * */ public class WeXinUtil { /** * 获取用户IP * @param request * @return */ public static String getIp(HttpServletRequest request){ String ipAddress = null; if (request.getHeader("x-forwarded-for") == null) { ipAddress = request.getRemoteAddr(); }else{ if(request.getHeader("x-forwarded-for").length() > 15){ String [] aStr = request.getHeader("x-forwarded-for").split(","); ipAddress = aStr[0]; } else{ ipAddress = request.getHeader("x-forwarded-for"); } } return ipAddress; } /** * 签名算法,生成统一下单中 必填项签名 * @param unifiedOrder 1.将统一下单实体中各个字段拼接 2.MD5加密 3.全部转化为大写 * @return 返回经过签名算法生成的签名 sign * 第一步的规则 * ◆ 参数名ASCII码从小到大排序(字典序); * ◆ 如果参数的值为空不参与签名; * ◆ 参数名区分大小写; * ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。 * ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段 */ /* 手动拼接方式 public String createUnifiedOrderSign(Unifiedorder unifiedOrder){ StringBuffer sign = new StringBuffer(); sign.append("appid=").append(unifiedOrder.getAppid()); sign.append("&body=").append(unifiedOrder.getBody()); sign.append("&mch_id=").append(unifiedOrder.getMch_id()); sign.append("&nonce_str=").append(unifiedOrder.getNonce_str()); sign.append("¬ify_url=").append(unifiedOrder.getNotify_url()); sign.append("&openid=").append(unifiedOrder.getOpenid()); sign.append("&out_trade_no=").append(unifiedOrder.getOut_trade_no()); sign.append("&spbill_create_ip=").append(unifiedOrder.getSpbill_create_ip()); sign.append("&total_fee=").append(unifiedOrder.getTotal_fee()); sign.append("&trade_type=").append(unifiedOrder.getTrade_type()); sign.append("&key=").append(KEY); return DigestUtils.md5Hex(sign.toString()).toUpperCase(); } */ /** * 拼接生成sign 签名 * @param unifiedOrder * @param KEY * @return * @throws Exception */ public static String createUnifiedOrderSign(Object object,String KEY) throws Exception{ StringBuffer sign = new StringBuffer(); Map<String, String> map = getSortMap(object); boolean isNotFirst = false; for (Map.Entry<String, String> entry : map.entrySet()) { if(isNotFirst == true){ sign.append("&"); }else{ isNotFirst = true; } sign.append(entry.getKey()).append("=").append(entry.getValue()); } sign.append("&key=").append(KEY); return DigestUtils.md5Hex(sign.toString()).toUpperCase(); } /** * 使用java反射机制,动态获取对象的属性和参数值,排除值为null的情况,并按字典序排序 * @param object * @return * @throws Exception */ private static Map<String, String> getSortMap(Object object) throws Exception{ Field[] fields = object.getClass().getDeclaredFields(); Map<String, String> map = new HashMap<String, String>(); for(Field field : fields){ String name = field.getName(); String methodName = "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1) .toUpperCase()); // 调用getter方法获取属性值 // Method getter = object.getClass().getMethod(methodName); // String value = getter.invoke(object)+""; field.setAccessible(true); Object value = field.get(object); if (value != null){ map.put(name, value.toString()); } } Map<String, String> sortMap = new TreeMap<String, String>( new Comparator<String>() { @Override public int compare(String arg0, String arg1) { return arg0.compareTo(arg1); } }); sortMap.putAll(map); return sortMap; } public static SSLContext getSSL() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, java.security.cert.CertificateException { KeyStore keyStore = KeyStore.getInstance("PKCS12"); //证书位置 放在自己的项目下面 Resource resource = new ClassPathResource("apiclient_cert.p12"); InputStream instream = resource.getInputStream(); try { keyStore.load(instream, "填写证书密码,默认为商户号".toCharArray()); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, "填写证书密码,默认为商户号".toCharArray()) .build(); return sslcontext; } }
----------------------------------------------------------------------------------------------8.封装/解析xml消息的工具类-------------------------------------------------------------------------------------------------------------------------------------
package net.shopxx.wx.pay; import java.io.Writer; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.naming.NoNameCoder; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; /** * 微信支付 微信公众号发红包 * 封装/解析xml消息的工具类 * @author SXD * */ public class XmlUtil { public XStream getXstreamInclueUnderline(){ XStream stream = new XStream(new XppDriver(new NoNameCoder()) { @Override public PrettyPrintWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 对所有xml节点的转换都增加CDATA标记 boolean cdata = true; @Override @SuppressWarnings("rawtypes") public void startNode(String name, Class clazz) { super.startNode(name, clazz); } @Override public String encodeNode(String name) { return name; } @Override protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { writer.write(text); } } }; } }); return stream; } /** * 根据字符串 解析XML map集合 * @param xml * @return * @throws DocumentException */ public Map<String, String> parseXML(String xml) throws DocumentException{ Document document = DocumentHelper.parseText(xml); Element element =document.getRootElement(); List<Element> childElements = element.elements(); Map<String,String> map = new HashMap<String, String>(); map = getAllElements(childElements,map); map.forEach((k,v)->{ System.out.println(k+">>>>"+v); }); return map; } /** * 获取 子节点的被迭代方法 * @param childElements * @param mapEle * @return */ private Map<String, String> getAllElements(List<Element> childElements,Map<String,String> mapEle) { for (Element ele : childElements) { if(ele.elements().size()>0){ mapEle = getAllElements(ele.elements(), mapEle); }else{ mapEle.put(ele.getName(), ele.getText()); } } return mapEle; } }
----------------------------------------------------------------------------------------------9.微信支付 商户服务器 逻辑处理中心------------------------------------------------------------------------------------------------------------------------
package net.shopxx.wx.pay; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.thoughtworks.xstream.XStream; /** * 微信支付 逻辑处理 * @author SXD * */ @Controller @RequestMapping("/wx/PayOrder") public class PayOrderController { /** * 公众账号ID */ @Value("${member.appid}") private String APPID; /** * 商户号 暂未配置 */ private String MCHID; /** * key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 * 暂未配置 */ private String KEY; /** * 统一下单 URL */ @Value("${unifiedOrderUrl}") private String unifiedOrderUrl; private XmlUtil xmlUtil = new XmlUtil(); private HttpConnection httpConnection = new HttpConnection(); /** * ② 用户支付按钮后,先进入统一下单这个方法获取prepay_id *进行统一下单 ,获取预支付订单的prepay_id * @param request * * @param openId * @param orderId * @param money * @return * @throws Exception */ @ResponseBody @RequestMapping("/unifiedOrder") public String unifiedOrder(HttpServletRequest request,String openId,String orderId,int money) throws Exception{ Unifiedorder unifiedOrder = new Unifiedorder(); unifiedOrder.setAppid(APPID); unifiedOrder.setMch_id(MCHID); String nonce = UUID.randomUUID().toString().replaceAll("-", ""); unifiedOrder.setNonce_str(nonce); unifiedOrder.setBody("商品的描述"); unifiedOrder.setOut_trade_no(orderId); unifiedOrder.setTotal_fee(money); unifiedOrder.setSpbill_create_ip(WeXinUtil.getIp(request)); unifiedOrder.setNotify_url("http://weixin.myagen.com.cn/wx/PayOrder/wechatPayNotify"); unifiedOrder.setOpenid(openId); String sign = WeXinUtil.createUnifiedOrderSign(unifiedOrder,KEY); unifiedOrder.setSign(sign); /** * 转成XML格式 微信可接受的格式 */ xmlUtil.getXstreamInclueUnderline().alias("xml", unifiedOrder.getClass()); String xml = xmlUtil.getXstreamInclueUnderline().toXML(unifiedOrder); /** * ③请求微信服务器 返回结果 获取prepay_id */ String response = httpConnection.post(unifiedOrderUrl, xml); System.out.println(response); Map<String, String> responseMap = xmlUtil.parseXML(response); /** * ④商户服务器将prepay_id返回给前台ajax */ return responseMap.get("prepay_id"); } /** * ⑥根据获取到的prepay_Id ,组装支付需要的相关配置参数,返回给前台网页 * 获取支付 配置 * @param prepayId * @return * @throws Exception */ @ResponseBody @RequestMapping("/createPayConfig") public JsAPIConfig createPayConfig(String prepayId) throws Exception { JsAPIConfig config = new JsAPIConfig(); String nonce = UUID.randomUUID().toString().replaceAll("-", ""); String timestamp = Long.toString(System.currentTimeMillis() / 1000); String packageName = "prepay_id="+prepayId; /** * 生成签名 */ StringBuffer sign = new StringBuffer(); sign.append("appId=").append(APPID); sign.append("&nonceStr=").append(nonce); sign.append("&package=").append(packageName); sign.append("&signType=").append(config.getSignType()); sign.append("&timeStamp=").append(timestamp); sign.append("&key=").append(KEY); String signature = DigestUtils.md5Hex(sign.toString()).toUpperCase(); config.setAppId(APPID); config.setNonceStr(nonce); config.setPackageStr(packageName); config.setTimeStamp(timestamp); config.setPaySign(signature); return config; } /** * ⑧ ⑨回调方法 接收到支付完成后的相关信息,根据是否支付成功 进行商户服务器端的业务逻辑操作,并在最后返回给微信服务器 已经确认成功的信息 * 回调方法 * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数 * @param request * @return */ @ResponseBody @RequestMapping("/wechatPayNotify") public String wechatPayNotify (HttpServletRequest request){ try { Map<String, String> map = getCallbackParams(request); if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) { //这里写成功后的业务逻辑 // String orderId = map.get("out_trade_no"); // orderService.updateConfirm(orderId); } }catch(Exception e){ System.out.println(e); } /** * ⑩返回给微信服务器 确认交易完成 */ return getPayCallback(); } /** * 接收支付完成后 微信服务器返回的request,解析返回字符串为键值对 * @param request * @return * @throws Exception */ public Map<String, String> getCallbackParams(HttpServletRequest request) throws Exception { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } System.out.println("~~~~~~~付款成功~~~~~~~~~"); outSteam.close(); inStream.close(); String result = new String(outSteam.toByteArray(), "utf-8"); return xmlUtil.parseXML(result); } /** * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 * 商户处理后同步返回给微信参数 * @return */ public String getPayCallback(){ PayCallback callback = new PayCallback(); XStream stream = new XStream(); stream.alias("xml",callback.getClass() ); String xml = stream.toXML(callback); return xml; } }
--------------------------------------------------------------------------------------------------------------如上,整个的微信 手机网页内支付-------------------------------------------------------------------------------------------------