1,简单说明
现在好多项目上都需要用到微信支付接口,官方文档上也是简单的描述了下,技术不高深的真的难以理解(我自己看官方文档就看不懂),还是需要自己收集,总结,网上看了好多
有些照着弄最后还是没法成功。接下来我分享下自己的微信支付。这个微信支付的微信公众号或者小程序,都是需要微信认证的,不然无法申请微信支付,这个就不说了,既然到了这一步,相信所有的前提都已经准备好了。直接上代码吧~
2,java微信支付
一,首先了解下各个参数的意义
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1 这个是微信官方的说明 截取部分,详细的请到这个链接查看。
二,微信支付代码
首先创建service层插入如下代码:
public interface WeiXinPayService { Object WeiXinPay(String outTradeNo,String openid, String body, int total_fee) throws UnsupportedEncodingException; }
@Service public class WeiXinPayServiceImpl implements WeiXinPayService { private static final Logger LOGGER = LoggerFactory .getLogger(WeiXinPayServiceImpl.class); @Override public Object WeiXinPay(String outTradeNo,String openid, String body, int total_fee) { String appid = "" // 公众号--》“开发者ID” 微信小程序,或者公众号的APPID String mch_id = "" // 商户号,将该值赋值给partner String key = "" // 微信支付商户平台登录)--》“API安全”--》“API密钥”--“设置密钥”(设置之后的那个值就是partnerkey,32位) LOGGER.debug(appid); LOGGER.debug(mch_id); LOGGER.debug(key); // String body = body; // 描述 int total_fee = total_fee; // 支付金额 String notify_url = ""; // 回调链接 // String out_trade_no = IdUtils.genOrderName();//生成订单号 // LOGGER.debug("outTradeNo---:"+out_trade_no); LOGGER.debug("openid是----"+openid); LOGGER.debug("appid---"+appid); LOGGER.debug("mch_id---"+mch_id); Map<Object, Object> map = null; try { map = WeiXinAtcion.me.weixinPlay(mch_id, appid, key, openid, total_fee, outTradeNo, notify_url, body); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } return map; } }
然后创建一个corcontroller层:
/** * <p>Title: WeiXinPayController</p> * <p>Description: 微信支付</p> * @author Mr Lin * @date 2018年7月7日 */ @Controller public class WeiXinPayController { private static final Logger LOGGER = LoggerFactory .getLogger(WeiXinPayController.class); @Autowired private WeiXinPayService weiXinPayService; /** * 支付接口 * * @param openid * @param body 说明 * @param total_fee 总价 * @return */ @RequestMapping("/WeiXinPay") public @ResponseBody Object WeiXinPay(String outTradeNo,String openid, String body, int total_fee) { LOGGER.debug("outTradeNo-------------"+outTradeNo); LOGGER.debug("openid-------------"+openid); LOGGER.debug("body-------------"+body); LOGGER.debug("total_fee-------------"+total_fee); try { return weiXinPayService.WeiXinPay(outTradeNo,openid, body, total_fee); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return TTResult.fail(); } }
以上代码中使用到的工具类:首先WeiXinAtcion
/** * <p>Title: WeiXinAtcion</p> * <p>Description: </p> * @author Mr Lin * @date 2018年7月7日 */ @Component public class WeiXinAtcion { //密钥 public static final WeiXinAtcion me = new WeiXinAtcion(); private static final Logger LOGGER = LoggerFactory .getLogger(WeiXinAtcion.class); /** * 生成微信订单 * * @param mch_id * @param appid * @param key * @param openid * @param total_fee * @param out_trade_no * @param notify_url * @param body * @return * @throws UnsupportedEncodingException * @throws DocumentException */ public SortedMap<Object, Object> weixinPlay(String mch_id, String appid, String key, String openid, int total_fee, String out_trade_no, String notify_url, String body) throws UnsupportedEncodingException, DocumentException, DocumentException { SortedMap<Object, Object> paymentPo = new TreeMap<Object, Object>(); paymentPo.put("appid", appid); paymentPo.put("mch_id", mch_id); paymentPo.put("nonce_str", WXUtil.generate()); paymentPo.put("body", body); paymentPo.put("out_trade_no", out_trade_no); paymentPo.put("total_fee", String.valueOf(total_fee)); paymentPo.put("spbill_create_ip","服务器的ip地址");//此处是公网ip paymentPo.put("notify_url", notify_url); paymentPo.put("trade_type", "JSAPI"); paymentPo.put("openid", openid); String sign = WXUtil.createSign_ChooseWXPay("UTF-8", paymentPo, key); paymentPo.put("sign", sign); String param = WXUtil.getRequestXml(paymentPo); //将参数通过post请求传给微信端 String request = WXUtil.httpRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", param); Map<String, String> map = new HashMap<String, String>(); // 将解析结果存储在HashMap中 InputStream in = new ByteArrayInputStream(request.getBytes()); SAXReader reader = new SAXReader(); // 读取输入流 Document document = reader.read(in); Element root = document.getRootElement(); // 得到xml根元素 @SuppressWarnings("unchecked") // 得到根元素的所有子节点 List<Element> elementList = root.elements(); for (Element element : elementList) { map.put(element.getName(), element.getText()); } SortedMap<Object, Object> result = new TreeMap<Object, Object>(); LOGGER.debug("第一次签名返回码" + map.get("return_code")); LOGGER.debug("第一次签名返回结果" + map.get("return_msg")); //第一次签名成功 if (map.get("return_code").equals("SUCCESS")) { // 业务结果 String nonceStr = WXUtil.generate(); Long timeStamp = System.currentTimeMillis() / 1000; SortedMap<Object, Object> params = new TreeMap<Object, Object>(); params.put("appId", appid); params.put("nonceStr", nonceStr); params.put("package", "prepay_id=" + map.get("prepay_id")); params.put("signType", "MD5"); params.put("timeStamp", timeStamp); //第二次签名成功 LOGGER.debug("开始第二次签名"); String paySign = WXUtil.createSign_ChooseWXPay("UTF-8", params, key); result.put("paySign", paySign); result.put("timeStamp", timeStamp + ""); result.put("nonceStr", nonceStr); result.put("out_trade_no", paymentPo.get("out_trade_no")); result.put("package", "prepay_id=" + map.get("prepay_id")); result.put("return_code", "SUCCESS"); } else { result.put("return_code", "Fail"); result.put("return_msg", map.get("return_msg")); } return result; } }
工具类:WXUtil
public class WXUtil { private static final Logger LOGGER = LoggerFactory.getLogger(WXUtil.class); /** * 随机字符串 * @return */ public static String generate() { return UUID.randomUUID().toString().trim().replaceAll("-", ""); } /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */ 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 = WXUtil.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(WXUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 将请求参数转换为xml格式的string字符串,微信服务器接收的是xml格式的字符串 * @param parameters * @return */ public static String getRequestXml(SortedMap<Object, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set<Entry<Object, Object>> es = parameters.entrySet(); Iterator<Entry<Object, Object>> it = es.iterator(); while (it.hasNext()) { Entry<Object, Object> entry = (Entry<Object, Object>) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) { sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">"); } else { sb.append("<" + k + ">" + v + "</" + k + ">"); } } sb.append("</xml>"); return sb.toString(); } /** * sign签名,必须使用MD5签名,且编码为UTF-8 * @param characterEncoding * @param parameters * @return */ public static String createSign_ChooseWXPay(String characterEncoding, SortedMap<Object, Object> parameters, String key) { StringBuffer sb = new StringBuffer(); Set<Entry<Object, Object>> es = parameters.entrySet(); Iterator<Entry<Object, Object>> it = es.iterator(); while (it.hasNext()) { Entry<Object, Object> entry = (Entry<Object, Object>) 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); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } public static String httpRequest(String requestUrl,String requestMethod,String outputStr){ // 创建SSLContext StringBuffer buffer=null; try{ URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(requestMethod); conn.setDoOutput(true); conn.setDoInput(true); conn.connect(); //往服务器端写内容 if(null !=outputStr){ OutputStream os=conn.getOutputStream(); os.write(outputStr.getBytes("utf-8")); os.close(); } // 读取服务器端返回的内容 InputStream is = conn.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "utf-8"); BufferedReader br = new BufferedReader(isr); buffer = new StringBuffer(); String line = null; while ((line = br.readLine()) != null) { buffer.append(line); } }catch(Exception e){ e.printStackTrace(); } return buffer.toString(); } public static String urlEncodeUTF8(String source){ String result=source; try { result=java.net.URLEncoder.encode(source, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * 退款和企业付款到银行卡 */ public static Map<String, String> doRefund(HttpServletRequest request,String url,String data,String partner,String apiclient_certLocation) throws Exception { // p12证书的位置 // 微信公众平台:“微信支付”--》“商户信息”--》“交易数据”--》“详情请登录微信支付商户平台查看”(登录)--》“API安全”--》“API证书”--》“下载证书” // 下载证书后将apiclient_cert.p12放在src目录下面(出于安全考虑,请自行下载自己的证书) KeyStore keyStore = KeyStore.getInstance("PKCS12"); String url2 = request.getSession().getServletContext().getRealPath("/") + "cert/" + apiclient_certLocation; LOGGER.debug("url2--->"+url2); File file=new File(url2); FileInputStream instream = new FileInputStream(file);// P12文件目录 try { keyStore.load(instream, partner.toCharArray()); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, partner.toCharArray()).build(); 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(url); // 设置响应头信息 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) "); httpost.setEntity(new StringEntity(data, "UTF-8")); CloseableHttpResponse response = httpclient.execute(httpost); try { HttpEntity entity = response.getEntity(); String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8"); EntityUtils.consume(entity); return WXUtil.doXMLParse(jsonStr); } finally { response.close(); } } finally { httpclient.close(); } } /** * 得到公钥 * @param request * @param url 请求微信端的链接 * @param data//请求的数据 * @param partner * @param apiclient_certLocation * @return * @throws Exception */ public static Object getPublicKey(HttpServletRequest request,String url,String data,String partner,String apiclient_certLocation) throws Exception { // p12证书的位置 // 微信公众平台:“微信支付”--》“商户信息”--》“交易数据”--》“详情请登录微信支付商户平台查看”(登录)--》“API安全”--》“API证书”--》“下载证书” // 下载证书后将apiclient_cert.p12放在src目录下面(出于安全考虑,请自行下载自己的证书) KeyStore keyStore = KeyStore.getInstance("PKCS12"); String url2 = request.getSession().getServletContext().getRealPath("/") + "cert/" + apiclient_certLocation; LOGGER.debug("url2--->"+url2); File file=new File(url2); FileInputStream instream = new FileInputStream(file);// P12文件目录 try { keyStore.load(instream, partner.toCharArray()); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, partner.toCharArray()).build(); 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(url); // 设置响应头信息 httpost.addHeader("Connection", "keep-alive"); httpost.addHeader("Accept", "*/*"); httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); 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) "); httpost.setEntity(new StringEntity(data, "UTF-8")); CloseableHttpResponse response = httpclient.execute(httpost); try { HttpEntity entity = response.getEntity(); String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8"); EntityUtils.consume(entity); return jsonStr; } finally { response.close(); } } finally { httpclient.close(); } } /** * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 * @return boolean */ public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) { StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Entry entry = (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); //算出摘要 String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase(); String tenpaySign = ((String)packageParams.get("sign")).toLowerCase(); return tenpaySign.equals(mysign); } public static Map<String, String> doRefund(String string, String xML, String mCH_ID, String cERT) { // TODO Auto-generated method stub return null; } }
返回结果的工具类:TTResult
public class TTResult { // 定义jackson对象 private static final ObjectMapper MAPPER = new ObjectMapper(); // 响应业务状态 private Integer status; // 200 代表成功, 500 代表失败 // 响应消息 private String msg; // 响应中的数据 private Object data; public static TTResult build(Integer status, String msg, Object data) { return new TTResult(status, msg, data); } public static TTResult ok(Object data) { return new TTResult(data); } public static TTResult ok() { return new TTResult(null); } public static TTResult fail(){ return new TTResult(500,"fail",null); } public static TTResult fail(Object data){ return new TTResult(500,"fail",data); } public TTResult() { } public static TTResult build(Integer status, String msg) { return new TTResult(status, msg, null); } public TTResult(Integer status, String msg, Object data) { this.status = status; this.msg = msg; this.data = data; } public TTResult(Object data) { this.status = 200; this.msg = "OK"; this.data = data; } // public Boolean isOK() { // return this.status == 200; // } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } /** * 将json结果集转化为TTResult对象 * * @param jsonData * json数据 * @param clazz * TTResult中的object类型 * @return */ public static TTResult formatToPojo(String jsonData, Class<?> clazz) { try { if (clazz == null) { return MAPPER.readValue(jsonData, TTResult.class); } JsonNode jsonNode = MAPPER.readTree(jsonData); JsonNode data = jsonNode.get("data"); Object obj = null; if (clazz != null) { if (data.isObject()) { obj = MAPPER.readValue(data.traverse(), clazz); } else if (data.isTextual() || data.isNumber()) { obj = MAPPER.readValue(data.asText(), clazz); } } return build(jsonNode.get("status").intValue(), jsonNode.get("msg") .asText(), obj); } catch (Exception e) { return null; } } /** * 没有object对象的转化 * * @param json * @return */ public static TTResult format(String json) { try { return MAPPER.readValue(json, TTResult.class); } catch (Exception e) { e.printStackTrace(); } return null; } /** * Object是集合转化 * * @param jsonData * json数据 * @param clazz * 集合中的类型 * @return */ public static TTResult formatToList(String jsonData, Class<?> clazz) { try { JsonNode jsonNode = MAPPER.readTree(jsonData); JsonNode data = jsonNode.get("data"); Object obj = null; if (data.isArray() && data.size() > 0) { obj = MAPPER.readValue(data.traverse(), MAPPER.getTypeFactory() .constructCollectionType(List.class, clazz)); } return build(jsonNode.get("status").intValue(), jsonNode.get("msg") .asText(), obj); } catch (Exception e) { return null; } } }
上面代码中所涉及到的参数,要去微信官方文档看说明,每个参数都有解释的,按照我这个来,微信支付是肯定能跑通的。有啥不足之处请指教。