微信支付:模式二(JAVA)
开始
前言:由于微信官方文档非常扯淡,也不够阿里文档详细,集中和简单,并且官方提供demo多样性和复杂性,此次我采用的是模式二,http工具类请求。
-
首先,我们需要在开发平台获取我们需要的参数。
- appid:产品配置中获得app_id
- device_info:WEB(自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB")
- mch_id:商家号
- notify_url:回调路径
- spbill_create_ip:项目ip(可以127.0.0.1,但是不可以localhost)
- trade_type:NATIVE
- key:密钥
- url:https://api.mch.weixin.qq.com/pay/unifiedorder
-
工具类
-
WxConfig.class(微信配置)
public class WxConfig { public static final String appid = ""; //app_id public static final String body = "订单支付"; // 订单内容 public static final String device_info = "WEB"; // 类型:网站 public static final String mch_id = ""; // 商家号 public static final String notify_url = "/wx/paycallback.json"; // 回调路径 public static final String spbill_create_ip = "127.0.0.1"; // 项目ip public static final String trade_type = "NATIVE"; public static final String key = ""; //key public static final String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //请求路径 }
-
SignGenerate.class(生成请求微信参数格式)
public final class SignGenerate { // 生成md5加密后的sign public static String wxSignGenerate(Map<String, String> paramsMap, String key) { StringBuilder builder = new StringBuilder(); for (Map.Entry<String, String> entry : paramsMap.entrySet()) { builder.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); } builder.append("key=").append(key); return DigestUtils.md5Hex(builder.toString()).toUpperCase(); } // 将配置生成微信需要的xml格式 public static String buildWxXmlJson(String productNumber,String amount){ StringBuilder data = new StringBuilder("<xml>"); Map<String, String> paramsMap = new LinkedHashMap<>(); paramsMap.put("appid", WxConfig.appid); paramsMap.put("body", WxConfig.body); paramsMap.put("device_info", WxConfig.device_info); paramsMap.put("mch_id", WxConfig.mch_id); paramsMap.put("nonce_str", SerialGenerator.genSerialCode()); paramsMap.put("notify_url", WxConfig.notify_url); paramsMap.put("out_trade_no", productNumber); paramsMap.put("spbill_create_ip", WxConfig.spbill_create_ip); paramsMap.put("total_fee", amount); paramsMap.put("trade_type", WxConfig.trade_type); String sign = wxSignGenerate(paramsMap, WxConfig.key); paramsMap.put("sign", sign); for (Map.Entry<String, String> entry : paramsMap.entrySet()) { data.append("<").append(entry.getKey()).append(">").append(entry.getValue()).append("</").append(entry.getKey()).append(">"); } data.append("</xml>"); return data.toString(); } }
-
WeixinWebUtil(微信请求工具类)
public class WeixinWebUtil { private static Logger log = LoggerFactory.getLogger(WeixinWebUtil.class); public static String doGet(String url, String charset, int connectTimeout, int readTimeout) { return httpRequest(url, "GET", null, charset, connectTimeout, readTimeout, null); } public static String doGet(String url, String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) { return httpRequest(url, "GET", null, charset, connectTimeout, readTimeout, headerMap); } //url为不带 ? 号的url public static String doGet(String url, Map<String, String> paramMap, String charset, int connectTimeout, int readTimeout) throws IOException { String query = buildQuery(paramMap, charset); return httpRequest(url + "?" + query, "GET", null, charset, connectTimeout, readTimeout, null); } public static String doGet(String url, Map<String, String> paramMap, String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException { String query = buildQuery(paramMap, charset); return httpRequest(url + "?" + query, "GET", null, charset, connectTimeout, readTimeout, headerMap); } public static String doPost(String url, Map<String, String> paramMap, String jsonParam, String charset, int connectTimeout, int readTimeout) throws IOException { String query = buildQuery(paramMap, charset); return httpRequest(url + "?" + query, "POST", jsonParam, charset, connectTimeout, readTimeout, null); } public static String doPost(String url, Map<String, String> paramMap, String jsonParam, String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException { String query = buildQuery(paramMap, charset); return httpRequest(url + "?" + query, "POST", jsonParam, charset, connectTimeout, readTimeout, headerMap); } public static String doPost(String url, String jsonParam, String charset, int connectTimeout, int readTimeout) { return httpRequest(url, "POST", jsonParam, charset, connectTimeout, readTimeout, null); } public static String doPost(String url, String jsonParam, String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) { return httpRequest(url, "POST", jsonParam, charset, connectTimeout, readTimeout, headerMap); } /** * 发起https请求并获取结果 * * @param requestUrl 请求地址 * @param requestMethod 请求方式(GET、POST) * @param outputStr 提交的数据 * @return String */ private static String httpRequest(String requestUrl, String requestMethod, String outputStr, String charset, int connectTimeout, int readTimeout, Map<String, String> headerMap) { StringBuffer buffer = new StringBuffer(); try { // 创建SSLContext对象,并使用我们指定的信任管理器初始化 TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("TLSV1"); //使用TLS协议加密 // SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 从上述SSLContext对象中得到SSLSocketFactory对象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); httpUrlConn.setConnectTimeout(connectTimeout); httpUrlConn.setReadTimeout(readTimeout); // 设置请求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); if(headerMap!=null){ for(Map.Entry<String, String> entry : headerMap.entrySet()){ httpUrlConn.setRequestProperty(entry.getKey(), entry.getValue()); } } if ("GET".equalsIgnoreCase(requestMethod)){ httpUrlConn.connect(); } // 当有数据需要提交时 if (StringUtils.isNotBlank(outputStr)) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 注意编码格式,防止中文乱码 outputStream.write(outputStr.getBytes(charset)); outputStream.close(); } // 将返回的输入流转换成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 释放资源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); } catch (ConnectException ce) { log.error("Weixin server connection timed out."); } catch (Exception e) { log.error("https request error:", e); } return buffer.toString(); } public static String buildQuery(Map<String, String> params, String charset) throws IOException { if (params == null || params.isEmpty()) { return null; } StringBuilder query = new StringBuilder(); Set<Map.Entry<String, String>> entries = params.entrySet(); boolean hasParam = false; for (Map.Entry<String, String> entry : entries) { String name = entry.getKey(); String value = entry.getValue(); // 忽略参数名或参数值为空的参数 if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(value)) { if (hasParam) { query.append("&"); } else { hasParam = true; } query.append(name).append("=").append(URLEncoder.encode(value, charset)); } } return query.toString(); } } }
4.XMLUtil.class(解析WxXML响应,将XML字符串转为Map)
public final class XMLUtil { /** * 将微信xml格式解析成map * @param xmlString * @return * @throws DocumentException */ public static Map<String, String> parseXmlStr(String xmlString) throws DocumentException { //去掉前后的xml标签 xmlString = xmlString.replaceAll("</?xml>", ""); System.out.println(xmlString); //匹配一段一段这样的数据 <attach><![CDATA[支付测试]]></attach> Pattern pattern = Pattern.compile("<.*?/.*?>"); Matcher matcher = pattern.matcher(xmlString); //配置是否包含<![CDATA[CNY]]> CDATA 包裹的数据 Pattern pattern2 = Pattern.compile("!.*]"); Map<String, String> map = new HashMap<>(); while (matcher.find()) { //获取键 String key = matcher.group().replaceAll(".*/", ""); key = key.substring(0, key.length() - 1); Matcher matcher2 = pattern2.matcher(matcher.group()); String value = matcher.group().replaceAll("</?.*?>", ""); //获取值 if (matcher2.find() && !value.equals("DATA")) { value = matcher2.group().replaceAll("!.*\\[", ""); value = value.substring(0, value.length() - 2); } map.put(key, value); } return map; }
}
-
-
业务
String xmlJson = SignGenerate.buildWxXmlJson("商品号", "金额"); String result = WeixinWebUtil.doPost(WxConfig.url, xmlJson, "UTF-8", 3000, 3000); // 发送请求 Map<String, String> resultMap = XMLUtil.parseXmlStr(result); // 解析Wx的相应参数 String code_url resultMap.get("code_url"); // code_url是Wx响应的扫码网页路径 // 生成二维码 ByteArrayOutputStream outputStream = QRCode.from(code_url).to(ImageType.PNG).stream(); httpResponse.setContentType("image/png"); httpResponse.setContentLength(outputStream.size()); OutputStream outStream = httpResponse.getOutputStream(); outStream.write(outputStream.toByteArray()); outStream.flush(); outStream.close();
-
Maven依赖
<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>${xstream.version}</version> </dependency> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>${jettison.version}</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>${zxing.version}</version> </dependency> <dependency> <groupId>net.glxn</groupId> <artifactId>qrgen</artifactId> <version>${qrgen.version}</version> </dependency>