一、java后台
1、maven引入
<!--微信支付-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>2.0.2</version>
</dependency>
2、java代码
WxUtils类
import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClients; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @Component public class WxUtils { /** * 执行 POST 方法的 HTTP 请求 * * @param url * @param parameters * @return * @throws IOException */ public String executeHttpPost(String url, SortedMap<String, Object> parameters) throws IOException { HttpClient client = HttpClients.createDefault(); HttpPost request = new HttpPost(url); request.setHeader("Content-type", "application/xml"); request.setHeader("Accept", "application/xml"); request.setEntity(new StringEntity(transferMapToXml(parameters), "UTF-8")); HttpResponse response = client.execute(request); return readResponse(response); } /** * 第一次签名 * * @param parameters 数据为服务器生成,下单时必须的字段排序签名 * @param key * @return */ public String createSign(SortedMap<String, Object> parameters, String key) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序) Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key); return encodeMD5(sb.toString()); } /** * 第二次签名 * * @param result 数据为微信返回给服务器的数据(XML 的 String),再次签名后传回给客户端(APP)使用 * @param key 密钥 * @return * @throws IOException */ public Map createSign2(String result, String key) throws IOException { SortedMap<String, Object> map = new TreeMap<>(transferXmlToMap(result)); Map app = new HashMap(); System.out.println(map); app.put("appId", map.get("appid")); app.put("nonceStr", map.get("nonce_str")); app.put("package", "prepay_id="+map.get("prepay_id")); app.put("signType", "MD5"); app.put("timeStamp",String.valueOf(new Date().getTime() / 1000) ); // 时间为秒,JDK 生成的是毫秒,故除以 1000 app.put("sign", createSign(new TreeMap<>(app), key)); return app; } /** * 验证签名是否正确 * * @return boolean * @throws Exception */ public boolean checkSign(SortedMap<String, Object> parameters, String key) throws Exception { String signWx = parameters.get("sign").toString(); if (signWx == null) return false; parameters.remove("sign"); // 需要去掉原 map 中包含的 sign 字段再进行签名 String signMe = createSign(parameters, key); return signWx.equals(signMe); } /** * 读取 request body 内容作为字符串 * * @param request * @return * @throws IOException */ public String readRequest(HttpServletRequest request) throws IOException { InputStream inputStream; StringBuffer sb = new StringBuffer(); inputStream = request.getInputStream(); String str; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((str = in.readLine()) != null) { sb.append(str); } in.close(); inputStream.close(); return sb.toString(); } /** * 读取 response body 内容为字符串 */ public String readResponse(HttpResponse response) throws IOException { BufferedReader in = new BufferedReader( new InputStreamReader(response.getEntity().getContent())); String result = new String(); String line; while ((line = in.readLine()) != null) { result += line; } return result; } /** * 将 Map 转化为 XML * * @param map * @return */ public String transferMapToXml(SortedMap<String, Object> map) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); for (String key : map.keySet()) { sb.append("<").append(key).append(">") .append(map.get(key)) .append("</").append(key).append(">"); } return sb.append("</xml>").toString(); } /** * 将 XML 转化为 map * * @param strxml * @return * @throws org.jdom2.JDOMException * @throws IOException */ public Map transferXmlToMap(String strxml) throws IOException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if (null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = null; try { doc = builder.build(in); } catch (JDOMException e) { throw new IOException(e.getMessage()); // 统一转化为 IO 异常输出 } // 解析 DOM Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } // 辅助 transferXmlToMap 方法递归提取子节点数据 private String getChildrenText(List<Element> children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator<Element> it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List<Element> list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 生成 32 位随机字符串,包含:数字、字母大小写 * * @return */ public String gen32RandomString() { char[] dict = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; StringBuffer sb = new StringBuffer(); Random random = new Random(); for (int i = 0; i < 32; i++) { sb.append(String.valueOf(dict[(int) (Math.random() * 36)])); } return sb.toString(); } /** * MD5 签名 * * @param str * @return 签名后的字符串信息 */ public String encodeMD5(String str) { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); byte[] inputByteArray = (str).getBytes(); messageDigest.update(inputByteArray); byte[] resultByteArray = messageDigest.digest(); return byteArrayToHex(resultByteArray); } catch (NoSuchAlgorithmException e) { return null; } } // 辅助 encodeMD5 方法实现 private String byteArrayToHex(byte[] byteArray) { char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] resultCharArray = new char[byteArray.length * 2]; int index = 0; for (byte b : byteArray) { resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; resultCharArray[index++] = hexDigits[b & 0xf]; } // 字符数组组合成字符串返回 return new String(resultCharArray); } }
统一下单接口
//统一下单接口 public Map createOrder(@PathVariable String ids, @CurrentPerson CurPersonInfo currentPerson) throws IOException { SortedMap<String, Object> parameters = new TreeMap<String, Object>(); parameters.put("appid", APPID); parameters.put("mch_id", MCH_ID); parameters.put("device_info", "WEB"); // 默认 "WEB" parameters.put("body", body); parameters.put("nonce_str", wxUtils.gen32RandomString()); // 32 位随机字符串 parameters.put("notify_url", notifyUrl); parameters.put("out_trade_no", ids); parameters.put("sign_type", "MD5"); parameters.put("total_fee", 1); // 测试时,将支付金额设置为 1 分钱 parameters.put("spbill_create_ip", "127.0.0.1"); parameters.put("trade_type", "JSAPI"); System.out.println(currentPerson.getOpenId()); parameters.put("openid", currentPerson.getOpenId()); parameters.put("sign", wxUtils.createSign(parameters, KEY)); // sign 必须在最后 System.out.println(parameters); String result = wxUtils.executeHttpPost(placeUrl, parameters); // 执行 HTTP 请求,获取接收的字符串(一段 XML) Map resultInfo=wxUtils.createSign2(result, KEY); System.out.println(resultInfo); return resultInfo; }
回调接口
@RequestMapping(value = "/callBack") public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception { // 预先设定返回的 response 类型为 xml response.setHeader("Content-type", "application/xml"); // 读取参数,解析Xml为map Map<String, String> map = wxUtils.transferXmlToMap(wxUtils.readRequest(request)); // 转换为有序 map,判断签名是否正确 boolean isSignSuccess = wxUtils.checkSign(new TreeMap<String, Object>(map), KEY); if (isSignSuccess) { // 签名校验成功,说明是微信服务器发出的数据 业务代码 return success(); } else { // 签名校验失败(可能不是微信服务器发出的数据) return fail(); } } String fail() { return "<xml>\n" + " <return_code><![CDATA[FAIL]]></return_code>\n" + " <return_msg><![CDATA[]]></return_msg>\n" + "</xml>"; } String success() { return "<xml>\n" + " <return_code><![CDATA[SUCCESS]]></return_code>\n" + " <return_msg><![CDATA[OK]]></return_msg>\n" + "</xml>"; }
二、小程序调起支付代码
返回的参数为统一下单接口返回的
wx.requestPayment( { 'timeStamp': data.timeStamp, 'nonceStr': data.nonceStr, 'package': data.package, 'signType': 'MD5', 'paySign': data.sign, 'success':function(res){ console.log(res) }, 'fail':function(res){ console.log(res) } })