JAVA微信支付——微信公众号内支付 代码
官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
微信PC二维码支付方式参考:https://www.cnblogs.com/pxblog/p/10542917.html
沙箱环境调试:https://www.cnblogs.com/pxblog/p/13844925.html
本地开发环境支付回调调试方法可以参考:https://www.cnblogs.com/pxblog/p/11623053.html
引入jdom-1.1.3.jar包
HttpClientUtil.java
package weixinpay; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.HttpResponseException; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class HttpClientUtil { private static class SingletonHolder{ private final static HttpClientUtil INSTANCE=new HttpClientUtil(); } private HttpClientUtil(){} public static HttpClientUtil getInstance(){ return SingletonHolder.INSTANCE; } public String get(String url){ CharsetHandler handler = new CharsetHandler("UTF-8"); CloseableHttpClient client = null; try { HttpGet httpget = new HttpGet(new URI(url)); HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); client= httpClientBuilder.build(); client = (CloseableHttpClient) wrapClient(client); return client.execute(httpget, handler); } catch (Exception e) { //e.printStackTrace(); return ""; }finally { try { if(client!=null){ client.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static String post(String url, String params,String contentType) { //创建HttpClientBuilder HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); //HttpClient CloseableHttpClient client = httpClientBuilder.build(); client = (CloseableHttpClient) wrapClient(client); HttpPost post = new HttpPost(url); CloseableHttpResponse res = null; try { StringEntity s = new StringEntity(params,"UTF-8"); if(StringUtils.isBlank(contentType)){ s.setContentType("application/json"); } s.setContentType(contentType); s.setContentEncoding("utf-8"); post.setEntity(s); res = client.execute(post); HttpEntity entity = res.getEntity(); return EntityUtils.toString(entity, "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { res.close(); client.close(); } catch (IOException e) { e.printStackTrace(); } } return ""; } public static String post(String urlStr,String xmlInfo) { String line1 = ""; try { URL url = new URL(urlStr); URLConnection con = url.openConnection(); con.setDoOutput(true); //con.setRequestProperty("Pragma:", "no-cache"); con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Content-Type", "text/xml"); OutputStreamWriter out = new OutputStreamWriter(con .getOutputStream()); out.write(new String(xmlInfo.getBytes("utf-8"))); out.flush(); out.close(); BufferedReader br = new BufferedReader(new InputStreamReader(con .getInputStream())); String line = ""; for (line = br.readLine(); line != null; line = br.readLine()) { line1+=line; } return new String(line1.getBytes(),"utf-8"); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } private class CharsetHandler implements ResponseHandler<String> { private String charset; public CharsetHandler(String charset) { this.charset = charset; } public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException { StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); } HttpEntity entity = response.getEntity(); if (entity != null) { if (!StringUtils.isBlank(charset)) { return EntityUtils.toString(entity, charset); } else { return EntityUtils.toString(entity); } } else { return null; } } } private static HttpClient wrapClient(HttpClient base) { try { SSLContext ctx = SSLContext.getInstance("TLSv1"); X509TrustManager tm = new X509TrustManager() { public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }; ctx.init(null, new TrustManager[] { tm }, null); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(ctx, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); return httpclient; } catch (Exception ex) { return null; } } }
Num62.java
package weixinpay; /** * 62进制数字 */ public class Num62 { /** * 62个字母和数字,含大小写 */ public static final char[] N62_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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' }; /** * 36个小写字母和数字 */ public static final char[] N36_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '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' }; /** * 10 个数字 */ public static final char[] N10_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; /** * 长整型用N36表示的最大长度 */ public static final int LONG_N36_LEN = 13; /** * 长整型用N62表示的最大长度 */ public static final int LONG_N62_LEN = 11; /** * 长整型转换成字符串 * * @param l * @param chars * @return */ private static StringBuilder longToNBuf(long l, char[] chars) { int upgrade = chars.length; StringBuilder result = new StringBuilder(); int last; while (l > 0) { last = (int) (l % upgrade); result.append(chars[last]); l /= upgrade; } return result; } /** * 长整数转换成N62 * * @param l * @return */ public static String longToN62(long l) { return longToNBuf(l, N62_CHARS).reverse().toString(); } /** * 长整型转换成N36 * * @param l * @return */ public static String longToN36(long l) { return longToNBuf(l, N36_CHARS).reverse().toString(); } /** * 长整数转换成N62 * * @param l * @param length * 如不足length长度,则补足0。 * @return */ public static String longToN62(long l, int length) { StringBuilder sb = longToNBuf(l, N62_CHARS); for (int i = sb.length(); i < length; i++) { sb.append('0'); } return sb.reverse().toString(); } /** * 长整型转换成N36 * * @param l * @param length * 如不足length长度,则补足0。 * @return */ public static String longToN36(long l, int length) { StringBuilder sb = longToNBuf(l, N36_CHARS); for (int i = sb.length(); i < length; i++) { sb.append('0'); } return sb.reverse().toString(); } /** * N62转换成整数 * * @param n62 * @return */ public static long n62ToLong(String n62) { return nToLong(n62, N62_CHARS); } /** * N36转换成整数 * * @param n36 * @return */ public static long n36ToLong(String n36) { return nToLong(n36, N36_CHARS); } private static long nToLong(String s, char[] chars) { char[] nc = s.toCharArray(); long result = 0; long pow = 1; for (int i = nc.length - 1; i >= 0; i--, pow *= chars.length) { int n = findNIndex(nc[i], chars); result += n * pow; } return result; } private static int findNIndex(char c, char[] chars) { for (int i = 0; i < chars.length; i++) { if (c == chars[i]) { return i; } } throw new RuntimeException("N62(N36)非法字符:" + c); } public static void main(String[] args) { System.out.println(longToN62(Long.MAX_VALUE)); } }
微信支付支付参数配置类
PaymentConfig.java
package weixinpay; public class PaymentConfig { /*******微信支付参数*********/ //公众账号ID public static final String appid="wxd"; //密钥 public static final String appKey="h"; //商户号 public static final String mch_id="1584"; //接口地址 public static final String pay_url="https://api2.mch.weixin.qq.com/pay/unifiedorder"; //交易场景信息 public static final String scene_info = "{\"store_info\" : {\"id\": \"zxbm\",\"name\": \"支付\",\"area_code\": \"430000\",\"address\": \"中国\" }}"; }
PayUtil.java
package weixinpay; import org.apache.commons.lang.StringUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import java.io.*; import java.security.MessageDigest; import java.util.*; public class PayUtil { /** * 将需要传递给微信的参数转成xml格式 * @param parameters * @return */ public static String assembParamToXml(Map<String,String> parameters){ StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set<String> es = parameters.keySet(); List<Object> list=new ArrayList<Object>(es); Object[] ary =list.toArray(); Arrays.sort(ary); list=Arrays.asList(ary); Iterator<Object> it = list.iterator(); while(it.hasNext()) { String key = (String) it.next(); String val=(String) parameters.get(key); if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) { sb.append("<"+key+">"+"<![CDATA["+val+"]]></"+key+">"); }else { sb.append("<"+key+">"+val+"</"+key+">"); } } sb.append("</xml>"); return sb.toString(); } /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */ public static Map parseXMLToMap(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 =PayUtil.getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * @param children * @return String */ 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(); } /** * 微信支付签名sign * @param param * @param key * @return */ @SuppressWarnings("unchecked") public static String createSign(Map<String, String> param,String key){ //签名步骤一:按字典排序参数 List list=new ArrayList(param.keySet()); Object[] ary =list.toArray(); Arrays.sort(ary); list=Arrays.asList(ary); String str=""; for(int i=0;i<list.size();i++){ str+=list.get(i)+"="+param.get(list.get(i)+"")+"&"; } //签名步骤二:加上key str+="key="+key; //步骤三:加密并大写 str=PayUtil.MD5Encode(str,"utf-8").toUpperCase(); return str; } public static String MD5Encode(String origin,String charsetName){ String resultString=null; try{ resultString=new String(origin); MessageDigest md=MessageDigest.getInstance("MD5"); if(StringUtils.isBlank(charsetName)){ resultString=byteArrayToHexString(md.digest(resultString.getBytes())); }else{ resultString=byteArrayToHexString(md.digest(resultString.getBytes(charsetName))); } }catch(Exception e){ } return resultString; } public static String byteArrayToHexString(byte b[]){ StringBuffer resultSb=new StringBuffer(); for(int i=0;i<b.length;i++){ resultSb.append(PayUtil.byteToHexString(b[i])); } return resultSb.toString(); } public static String byteToHexString(byte b){ int n=b; if(n<0){ n+=256; } int d1=n/16; int d2=n%16; return PayUtil.hexDigits[d1]+PayUtil.hexDigits[d2]; } public static final String hexDigits[]={ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; /** * 元转换为分 * @param amount */ public static String changeY2F(Double amount){ String currency = amount.toString(); int index = currency.indexOf("."); int length = currency.length(); Long amLong = 0l; if(index == -1){ amLong = Long.valueOf(currency+"00"); }else if(length - index >= 3){ amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", "")); }else if(length - index == 2){ amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0); }else{ amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00"); } return amLong.toString(); } }
WeixinPay.java
package weixinpay; import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.StringUtils; import org.springframework.ui.ModelMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; public class WeixinPay { /** * 微信统一下单 * * @param trade_type 交易类型 * @param openId 用户的openId * @param request * @param wxReturn 微信支付异步回调地址 * @param orderNo 订单号 * @param price 订单金额 * @return */ public static Map<String, String> weixinUniformOrder(String openId, HttpServletRequest request, String wxReturn, String orderNo, Double price) { Map<String, String> paramMap = new HashMap<String, String>(); // 微信分配的公众账号ID(企业号corpid即为此appId)[必填] paramMap.put("appid", PaymentConfig.appid); // 微信支付分配的商户号 [必填] paramMap.put("mch_id", PaymentConfig.mch_id); // 终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB" [非必填] paramMap.put("device_info", "WEB"); // 随机字符串,不长于32位。 [必填] paramMap.put("nonce_str", RandomStringUtils.random(10, Num62.N62_CHARS)); // 商品或支付单简要描述 [必填] paramMap.put("body", PaymentConfig.mch_id); // 商户系统内部的订单号,32个字符内、可包含字母, [必填] paramMap.put("out_trade_no", orderNo); // 符合ISO 4217标准的三位字母代码,默认人民币:CNY. [非必填] paramMap.put("fee_type", "CNY"); // 金额必须为整数 单位为分 [必填] paramMap.put("total_fee", PayUtil.changeY2F(price)); // APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP [必填] paramMap.put("spbill_create_ip", request.getRemoteAddr()); // 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 [必填] paramMap.put("notify_url", wxReturn); // 交易类型{取值如下:JSAPI,NATIVE,APP,(JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付)} // [必填] paramMap.put("trade_type", "JSAPI"); //openid trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识 paramMap.put("openid", openId); // 商品ID{trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。} paramMap.put("product_id", "商品ID"); // 根据微信签名规则,生成签名 paramMap.put("sign", PayUtil.createSign(paramMap, PaymentConfig.appKey)); // 把参数转换成XML数据格式 String xmlWeChat = PayUtil.assembParamToXml(paramMap); String resXml = HttpClientUtil.post(PaymentConfig.pay_url, xmlWeChat); Map<String, String> map = new HashMap<String, String>(); try { if (StringUtils.isNotBlank(resXml)) { map = PayUtil.parseXMLToMap(resXml); } } catch (Exception e) { e.printStackTrace(); } return map; } /** * 微信公众号支付(手机端) * * @param wxReturn 微信支付异步回调地址 * @param openId 用户的openid * @param orderNo 订单号 * @param price 订单金额 * @param request * @param response * @param model * @return */ public static String weixinPayByMobile(String wxReturn, String openId, String orderNo, Double price, HttpServletRequest request, HttpServletResponse response, ModelMap model) { Map<String, String> map = weixinUniformOrder(openId, request, wxReturn, orderNo, price); String returnCode = map.get("return_code"); if (returnCode.equalsIgnoreCase("FAIL")) { //调用失败操作 //当return_code为FAIL时返回信息为错误原因 ,例如 签名失败、参数格式校验错误 map.get("return_msg"); } else if (returnCode.equalsIgnoreCase("SUCCESS")) { if (map.get("err_code") != null) { //支付失败操作 //错误代码描述 map.get("err_code_des"); } else if (map.get("result_code").equalsIgnoreCase( "SUCCESS")) { String prepay_id = map.get("prepay_id"); Long time = System.currentTimeMillis() / 1000; String nonceStr = RandomStringUtils.random(16, Num62.N10_CHARS); //公众号appid model.addAttribute("appId", PaymentConfig.appid); //时间戳 当前的时间 需要转换成秒 model.addAttribute("timeStamp", time); //随机字符串 不长于32位 model.addAttribute("nonceStr", nonceStr); //订单详情扩展字符串 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** model.addAttribute("package", "prepay_id=" + prepay_id); //签名方式 签名算法,暂支持MD5 model.addAttribute("signType", "MD5"); Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put("appId", PaymentConfig.appid); paramMap.put("timeStamp", time.toString()); paramMap.put("nonceStr", nonceStr); paramMap.put("package", "prepay_id=" + prepay_id); paramMap.put("signType", "MD5"); //签名 model.addAttribute("paySign", PayUtil.createSign(paramMap, PaymentConfig.appKey)); //跳转到 weixin_prepay.html return "weixin_prepay.html"; } } // 失败提示跳转页面,自己设计 return "error.html"; } //通知微信正确接收 public static String getSuccessXml() { String xml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>"; return xml; } }
微信公众号支付跳转页面主要逻辑代码
weixin_prepay.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>微信支付</title> <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript"></script> <script type="text/javascript"> wx.config({ debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '${appId!}', // 必填,公众号的唯一标识 timestamp: "${timeStamp!}", // 必填,生成签名的时间戳 nonceStr: '${nonceStr!}', // 必填,生成签名的随机串 signature: "${paySign!}", // 必填,签名,见附录1 jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function () { wx.chooseWXPay({ appId: "${appId!}", timestamp: "${timeStamp!}", nonceStr: "${nonceStr!}", package: "${package!}", signType: "MD5", paySign: "${paySign!}", success: function (res) { alert("成功"); window.location.href = "成功之后跳转地址"; }, cancel: function () { alert("用户已取消"); }, error: function (e) { alert("失败"); } }); }); </script> </head> <body> <div class="w1187b"> <div class="w1173"> </div> </div> </body> </html>
后台控制器调用类
WeixinPayAct.java
package weixinpay; import org.apache.commons.lang.StringUtils; import org.jdom.JDOMException; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import weixin.XMLUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.*; @Controller public class WeixinPayAct { /** * 微信支付 * * @param request * @param response * @param model * @return */ @RequestMapping(value = "weixin_Pay") public String selectPay(HttpServletRequest request, HttpServletResponse response, ModelMap model) { String wxReturn = "微信支付异步回调地址"; String orderNo = "订单号"; String openId = "用户的openId"; //支付金额 Double price = 0.01; return WeixinPay.weixinPayByMobile(wxReturn, openId, orderNo, price, request, response, model); } /** * 微信支付通知 * * @return */ @ResponseBody @RequestMapping(value = "/wechart_notice") public String wechartNotice(HttpServletRequest request, HttpServletResponse response, ModelMap model) { String result = ""; try { 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); } outSteam.close(); inStream.close(); result = new String(outSteam.toByteArray(), "utf-8"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } //判断返回报文是否为空 if (StringUtils.isNotBlank(result)) { try { Map<String, String> map = XMLUtil.doXMLParse(result); //获取商家订单编号 对应orderID String orderNo = map.get("out_trade_no"); //String orderNo = RequestUtils.getQueryParam(request,"orderNo"); //获取微信支付订单号 String transaction_id = map.get("transaction_id"); Order order = orderMng.findByOrderNo(orderNo); //判断支付是否成功 if ("SUCCESS".equals(map.get("result_code"))) { //支付成功,这里之所以加了一个判断,是因为这个回调可能会有多次,所以我们只有当订单状态时未支付的情况下,才执行下面操作 if (!Constants.ORDER_SUCCESS.equals(order.getStatus())) { //当微信支付成功后,把订单支付状态改为已支付 order.setStatus(Constants.ORDER_SUCCESS); } //处理业务逻辑 } else { //支付失败 order.setStatus(Constants.ORDER_FAIL); } orderMng.update(order); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return WeixinPay.getSuccessXml(); } }
如果浏览器请求返回:errMsg: "chooseWXPay:fail, the permission value is offline verifying"
这个错误是由于我们用的开发工具开发,只要在手机上打开就可以了
-----------------------有任何问题可以在评论区评论,也可以私信我,我看到的话会进行回复,欢迎大家指教------------------------
(蓝奏云官网有些地址失效了,需要把请求地址lanzous改成lanzoux才可以)