微信公众号支付
最近遇到微信支付问题,所以就记录下
1.pom.xml
<dependency> <groupId>com.ning</groupId> <artifactId>async-http-client</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency> <dependency> <groupId>com.gson</groupId> <artifactId>wechat</artifactId> <version>1.0.8</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.14.4</version> </dependency> <dependency> <groupId>com.squareup.okhttp</groupId> <artifactId>okhttp</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.jdom</groupId> <artifactId>jdom</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version> </dependency>
这里wechat 1.08需要自己导入,https://files.cnblogs.com/files/hz-cww/wechat-1.08.rar
2、工具类
a、MD5Util
import java.security.MessageDigest; public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; }
b、WxPayUtil 微信支付工具类
import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import javax.net.ssl.SSLContext; import java.io.*; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyStore; import java.util.*; /** * Created by Administrator on 2017/1/4. */ public class WxPayUtil { //微信支付key public static String API_KEY="*********"; //公众号appid public static String APPID="********"; //商户号id public static String MCH_ID="*******"; //随机字符串生成 public static String getRandomString(int length) { //length表示生成字符串的长度 String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } //请求xml组装 public static String getRequestXml(SortedMap<String,Object> parameters){ StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String key = (String)entry.getKey(); String value = (String)entry.getValue(); if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) { sb.append("<"+key+">"+"<![CDATA["+value+"]]></"+key+">"); }else { sb.append("<"+key+">"+value+"</"+key+">"); } } sb.append("</xml>"); return sb.toString(); } //生成签名 public static String createSign(String characterEncoding,SortedMap<String,Object> parameters){ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); 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=" + API_KEY); System.out.println(sb.toString()); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } /** * 验证回调签名 * @param map * @return */ public static boolean isTenpaySign(Map<String, String> map) { String charset = "utf-8"; String signFromAPIResponse = map.get("sign"); if (signFromAPIResponse == null || signFromAPIResponse.equals("")) { System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!"); return false; } System.out.println("服务器回包里面的签名是:" + signFromAPIResponse); //过滤空 设置 TreeMap SortedMap<String,String> packageParams = new TreeMap<>(); for (String parameter : map.keySet()) { String parameterValue = map.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if(!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较 //算出签名 String resultSign = ""; String tobesign = sb.toString(); if (null == charset || "".equals(charset)) { resultSign = MD5Util.MD5Encode(tobesign, "utf-8").toUpperCase(); } else { resultSign = MD5Util.MD5Encode(tobesign, "utf-8").toUpperCase(); } String tenpaySign = ((String)packageParams.get("sign")).toUpperCase(); return tenpaySign.equals(resultSign); } //请求方法 public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 设置请求方式(GET/POST) conn.setRequestMethod(requestMethod); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 当outputStr不为null时向输出流写数据 if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意编码格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 从输入流读取返回内容 InputStream inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 释放资源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; conn.disconnect(); return buffer.toString(); } catch (ConnectException ce) { System.out.println("连接超时:{}"+ ce); } catch (Exception e) { System.out.println("https请求异常:{}"+ e); } return null; } //退款的请求方法 public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); StringBuilder res = new StringBuilder(""); FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12")); try { keyStore.load(instream, "".toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, "1313329201".toCharArray()) .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); try { HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund"); httpost.addHeader("Connection", "keep-alive"); httpost.addHeader("Accept", "*/*"); httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); httpost.addHeader("Host", "api.mch.weixin.qq.com"); httpost.addHeader("X-Requested-With", "XMLHttpRequest"); httpost.addHeader("Cache-Control", "max-age=0"); httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); StringEntity entity2 = new StringEntity(outputStr , Consts.UTF_8); httpost.setEntity(entity2); System.out.println("executing request" + httpost.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpost); try { HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); String text = ""; res.append(text); while ((text = bufferedReader.readLine()) != null) { res.append(text); System.out.println(text); } } EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); } return res.toString(); } //xml解析 public static Map doXMLParse(String strxml) throws JDOMException, 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 = builder.build(in); 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; } public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } }
c、PayUtils 微信支付和预支付
import com.jumei.util.wxutils.WxPayUtil; import org.apache.log4j.Logger; import org.jdom.JDOMException; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.math.BigDecimal; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.UUID; /** * * 支付基本信息 * @author zhangli * @date 2016年3月28日 下午2:35:24 * * * 1.需要支付的信息 : * 用户支付 支付相关业务 支付金额 回调业务 * 2.第三方支付需要的信息: * trade_no,subject,body,total_fee,notify_url * */ public class PayUtils { protected static Logger logger = Logger.getLogger(PayUtils.class); public static String wxnotify = "/api/json/money/wxpay/succ"; /** * @param totalAmount 支付金额 * @param description 描述 * @param request * @return */ public static Map<String, Object> toPay(BigDecimal totalAmount,String description,String openId, HttpServletRequest request) { String sym = request.getRequestURL().toString().split("/api/")[0]; String trade_no0 = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase(); String trade_no = trade_no0.substring(0,32); Map<String, String> map = weixinPrePay(trade_no,totalAmount,description,openId,sym,request); SortedMap<String, Object> finalpackage = new TreeMap<String, Object>(); finalpackage.put("appId", WxPayUtil.APPID); finalpackage.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); finalpackage.put("nonceStr", WxPayUtil.getRandomString(32)); finalpackage.put("package", "prepay_id="+map.get("prepay_id")); finalpackage.put("signType", "MD5"); String sign = WxPayUtil.createSign("UTF-8", finalpackage); finalpackage.put("paySign", sign); return finalpackage; } /** * 预支付 * @param trade_no 商户订单号 **必输** <br/> * @param totalAmount 总金额 **必输** 注意 ,为货币最小单位,比如人民币时,单位为分<br/> * @param description 商品描述 **必输** <br/> * @param openid 用户标示 * @param sym 回调地址 * @param request * @return */ @SuppressWarnings("unchecked") public static Map<String, String> weixinPrePay(String trade_no,BigDecimal totalAmount, String description, String openid,String sym, HttpServletRequest request) { SortedMap<String, Object> parameterMap = new TreeMap<String, Object>(); parameterMap.put("appid", WxPayUtil.APPID); parameterMap.put("mch_id", WxPayUtil.MCH_ID); parameterMap.put("nonce_str", WxPayUtil.getRandomString(32)); parameterMap.put("body", description); parameterMap.put("out_trade_no", trade_no); parameterMap.put("fee_type", "CNY"); BigDecimal total = totalAmount.multiply(new BigDecimal(100)); java.text.DecimalFormat df=new java.text.DecimalFormat("0"); parameterMap.put("total_fee", df.format(total)); parameterMap.put("spbill_create_ip", request.getRemoteAddr()); parameterMap.put("notify_url", sym + wxnotify); parameterMap.put("trade_type", "JSAPI"); //trade_type为JSAPI是 openid为必填项 parameterMap.put("openid", openid); System.out.println(""); String sign = WxPayUtil.createSign("UTF-8", parameterMap); System.out.println("jiner2"); parameterMap.put("sign", sign); String requestXML = WxPayUtil.getRequestXml(parameterMap); System.out.println(requestXML); String result = WxPayUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",requestXML); System.out.println(result); Map<String, String> map = null; try { map = WxPayUtil.doXMLParse(result); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return map; } }
3、再控制层里面直接调用支付工具
Map<String,Object>map = PayUtils.toPay(
totalAmount,
description,
openid,
request
);
4、这里map返回给前端,前端直接调用js,注意这里先引入js sdk
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript"></script>
//调用微信JS api 支付 function jsApiCall(){ $.ajax({ url: urlcore+"/money/get/alipay/order/sign?rechargeOrPay=2&orderMoney=0.01&PayType=3&openid=oDqAOw2aXVR4BigutiXjiPXC5QW4", type: "get", dataType: 'jsonp', contentType: "application/json;charset=utf-8", success:function(data){ var d = data.data; WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":d.appId, //公众号名称,由商户传入 "timeStamp":d.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr":d.nonceStr, //随机串 "package":d.package, "signType":"MD5", //微信签名方式: "paySign":d.paySign //微信签名 }, function (res){ WeixinJSBridge.log(res.err_msg); alert(res.err_code + res.err_desc + res.err_msg); } ); }, error:function() { /* Act on the event */ alert("error"); } }); }
ok,微信公众号支付调用成功!当然这里只是公众号方式微信支付,app方式不一样,需要将PayUtils的 weixinPrePay方法的trade_type参数改为app即可,可以参考http://blog.csdn.net/gbguanbo/article/details/50915333