java实现网站微信扫码支付
作者:猎狐尊者
这篇文章主要为大家详细介绍了java实现网站微信扫码支付,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
一、网站微信扫码支付开发并没有现成的java示例,总结一下自己微信扫码支付心得
二、首先去微信公众平台申请账户
三、账户开通、开发者认证之后就可以进行微信支付开发了
1、微信统一下单接口调用获取预支付id,以及生成二维码所需的codeUrl
/** * 保存订单,并生成二维码所需的codeUrl * * @param request * @param response * @param notifyURLBuf * @param order * @return * @throws Exception */ @Override public Map<String, String> getWechatOrderInfo(String ip, Cuser user, String notifyUrl, Order order) throws Exception { Map<String, String> resultMap = new HashMap<String, String>(); // 生成并保存订单 order.setUserId(user.getId()); // 支付方式 0:银联 1:支付宝 2:网上银行 3:微信 4:其他 order.setPayType("3"); // 生成订单号 order.setOrderNo(OrderNoGenerator.getOrderNo()); // 订单类型 1:消费 2:退款 order.setOrderType("1"); // 订单创建时间 order.setCreateTime(new Date()); // 订单更新时间 order.setUpdateTime(new Date()); // 订单状态 0: 交易中 1:完成 2:已取消 order.setOrderStatus("0"); // 付款状态 0:失败 1:成功 2、待付款 order.setPayStatus("2"); // 设置订单失效时间 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.HOUR, 2); order.setExpireTime(calendar.getTime()); Integer orderId = this.balanceDao.saveOrder(order); Map<String, String> payPreIdMap = new HashMap<>(); payPreIdMap = WechatUtil.getPayPreId(String.valueOf(orderId), "体检报告", notifyUrl, ip, String.valueOf((order.getMoney().multiply(new BigDecimal(100)).intValue())), orderId.toString()); String prePayId = payPreIdMap.get("prepay_id"); // 更新 order.setId(orderId); order.setPrepayId(prePayId); order.setCodeUrl(payPreIdMap.get("code_url")); this.balanceDao.updateOrder(order); // return WechatUtil.QRfromGoogle(order.getCodeUrl(), 300, 0); resultMap.put("codeUrl", order.getCodeUrl()); resultMap.put("orderId", String.valueOf(order.getId())); return resultMap; }
此方法返回的数据如下
<xml><return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <appid><![CDATA[wxaf0b*****8afbf]]></appid> <mch_id><![CDATA[1408****02]]></mch_id> <nonce_str><![CDATA[zf0vGvdtVycBliwB]]></nonce_str> <sign><![CDATA[A2910F16086211153D747058063B3368]]></sign> <result_code><![CDATA[SUCCESS]]></result_code> <prepay_id><![CDATA[wx201701191109388037e9a12310276591827]]></prepay_id> <trade_type><![CDATA[NATIVE]]></trade_type> <code_url><![CDATA[weixin://wxpay/bizpayurl?pr=1UjorNX]]></code_url> </xml>
2、服务器端接受微信支付结果通知
/** * 保存微信通知结果 * * @param request * @param response * @return * @throws Exception */ @Override public String saveWechatNotify(String notifyInfoXml) throws Exception { Map<String, String> noticeMap = XMLUtil.doXMLParse(notifyInfoXml); // 这个其实是订单 的id String outTradeNo = noticeMap.get("out_trade_no"); Order order = this.balanceDao.getOrderById(Integer.valueOf(outTradeNo)); // 如果支付通知信息不为,说明请求已经处理过,直接返回 if (StringUtil.isNotEmpty(order.getNotifyInfo())) { return "SUCCESS"; } String sign = noticeMap.get("sign"); noticeMap.remove("sign"); // 验签通过 if (WechatUtil.getSignVeryfy(noticeMap, sign)) { // 通信成功此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断 if ("SUCCESS".equals(noticeMap.get("return_code"))) { // 交易成功 if ("SUCCESS".equals(noticeMap.get("result_code"))) { // 商户订单号 // 订单更新时间 order.setUpdateTime(new Date()); // ------------------------------ // 处理业务开始 // ------------------------------ // 是否交易成功,1:成功0:失败 // 微信支付成功 order.setPayStatus("1"); // 订单状态 0: 交易中 1:完成 2:已取消 order.setOrderStatus("1"); // 保存通知信息 order.setNotifyInfo(notifyInfoXml); this.balanceDao.updateOrder(order); // 处理业务完毕 } else { // 错误时,返回结果未签名,记录retcode、retmsg看失败详情。 logger.info("查询验证签名失败或业务错误"); logger.info("retcode:" + noticeMap.get("retcode") + " retmsg:" + noticeMap.get("retmsg")); } return "SUCCESS"; } else { logger.info("后台调用通信失败"); } return "SUCCESS"; } else { logger.info("通知签名验证失败"); } return null; }
3、上面代码用到的工具方法都在WechatUtil.java工具类中
package com.caifu.tencent.common; import java.io.IOException; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.commons.httpclient.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.jdom2.JDOMException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.caifu.login.utils.XMLUtil; public class WechatUtil { private static Logger logger = LoggerFactory.getLogger(WechatUtil.class); public static final String TAG = "Wechat.Util"; private static final int timeout = 5000; public static byte[] httpPost(String url, String entity) throws URISyntaxException, IOException { if (url == null || url.length() == 0) { logger.info(TAG, "httpPost, url is null"); return null; } CloseableHttpClient httpClient = HttpClients.createDefault(); URIBuilder uriBuilder = new URIBuilder(url); HttpPost httpPost = new HttpPost(uriBuilder.build()); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(timeout).setConnectionRequestTimeout(timeout).setConnectTimeout(timeout).build(); httpPost.setConfig(requestConfig); // 避免汉字乱码导致请求失败, httpPost.setEntity(new StringEntity(entity, "UTF-8")); CloseableHttpResponse resp = null; try { resp = httpClient.execute(httpPost); if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { logger.info(TAG, "httpGet fail, status code = " + resp.getStatusLine().getStatusCode()); return null; } return EntityUtils.toByteArray(resp.getEntity()); } catch (Exception e) { logger.info(TAG, "httpPost exception, e = " + e.getMessage()); e.printStackTrace(); return null; } finally { if (httpClient != null) { httpClient.close(); } if (resp != null) { resp.close(); } } } /** * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 * * @param params * 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<String>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * 根据反馈回来的信息,生成签名结果 * * @param Params * 通知返回来的参数数组 * @param sign * 比对的签名结果 * @return 生成的签名结果 */ public static boolean getSignVeryfy(Map<String, String> Params, String sign) { // 过滤空值、sign与sign_type参数 // Map<String, String> sParaNew = AlipayCore.paraFilter(Params); // 获取待签名字符串 String preSignStr = createLinkString(Params); preSignStr += "&key=" + Configure.getKey(); // 获得签名验证结果 String resultSign = MD5.MD5Encode(preSignStr).toUpperCase(); // String resultSign = MD5Util.MD5Encode(preSignStr.toString(), // "UTF-8").toLowerCase(); if (sign.equals(resultSign)) { return true; } else { return false; } } /** * 装配xml,生成请求prePayId所需参数 * * @param params * @return */ public static String toXml(List<NameValuePair> params) { StringBuilder sb = new StringBuilder(); sb.append("<xml>"); for (int i = 0; i < params.size(); i++) { sb.append("<" + params.get(i).getName() + ">"); sb.append(params.get(i).getValue()); sb.append("</" + params.get(i).getName() + ">"); } sb.append("</xml>"); return sb.toString(); } /** * 生成签名 */ public static String genPackageSign(List<NameValuePair> params) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < params.size(); i++) { sb.append(params.get(i).getName()); sb.append('='); sb.append(params.get(i).getValue()); sb.append('&'); } sb.append("key="); sb.append(Configure.getKey()); String packageSign = MD5.MD5Encode(sb.toString()); return packageSign; } /** * * @param goodOrderNo * @param body * @param noticeUrl * @param ip * @param totalFee * @return */ public static String genProductArgs(String goodOrderNo, String body, String noticeUrl, String ip, String totalFee, String productId) { StringBuffer xml = new StringBuffer(); try { String nonceStr = getNonceStr(); xml.append("</xml>"); List<NameValuePair> packageParams = new LinkedList<NameValuePair>(); packageParams.add(new BasicNameValuePair("appid", Configure.getAppid())); packageParams.add(new BasicNameValuePair("body", body)); packageParams.add(new BasicNameValuePair("mch_id", Configure.getMchid())); packageParams.add(new BasicNameValuePair("nonce_str", nonceStr)); packageParams.add(new BasicNameValuePair("notify_url", noticeUrl)); packageParams.add(new BasicNameValuePair("out_trade_no", goodOrderNo)); packageParams.add(new BasicNameValuePair("product_id", productId)); packageParams.add(new BasicNameValuePair("spbill_create_ip", ip)); packageParams.add(new BasicNameValuePair("total_fee", totalFee)); packageParams.add(new BasicNameValuePair("trade_type", "NATIVE")); String sign = genPackageSign(packageParams); packageParams.add(new BasicNameValuePair("sign", sign)); String xmlstring = toXml(packageParams); return xmlstring; } catch (Exception e) { logger.info("genProductArgs fail, ex = " + e.getMessage()); return null; } } /** * 生成支付签名 * * @param params * @return */ public static String genAppSign(List<NameValuePair> params) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < params.size(); i++) { sb.append(params.get(i).getName()); sb.append('='); sb.append(params.get(i).getValue()); sb.append('&'); } sb.append("key="); sb.append(Configure.getKey()); String appSign = MD5.MD5Encode(sb.toString()).toUpperCase(); logger.info("orion", appSign); return appSign; } /** * 生成调用微信支付所需参数 * * @param prepayId * @return */ public static Map<String, String> genPayReq(String prepayId) { Map<String, String> resultMap = new HashMap<String, String>(); String timeStamp = getTimeStamp(); String nonceStr = getNonceStr(); List<NameValuePair> signParams = new LinkedList<NameValuePair>(); signParams.add(new BasicNameValuePair("appid", Configure.getAppid())); signParams.add(new BasicNameValuePair("noncestr", nonceStr)); signParams.add(new BasicNameValuePair("package", "Sign=WXPay")); signParams.add(new BasicNameValuePair("partnerid", Configure.getMchid())); signParams.add(new BasicNameValuePair("prepayid", prepayId)); signParams.add(new BasicNameValuePair("timestamp", timeStamp)); String sign = genAppSign(signParams); resultMap.put("appid", Configure.getAppid()); resultMap.put("noncestr", nonceStr); resultMap.put("packageValue", "Sign=WXPay"); resultMap.put("partnerid", Configure.getMchid()); resultMap.put("prepayid", prepayId); resultMap.put("timestamp", timeStamp); resultMap.put("sign", sign); return resultMap; } /** * 微信支付生成预支付订单 * * @throws IOException * @throws JDOMException */ public static Map<String, String> getPayPreId(String goodOrderNo, String body, String noticeUrl, String ip, String totalFee, String productId) throws Exception { String paramsXml = genProductArgs(goodOrderNo, body, noticeUrl, ip, totalFee, productId); logger.info("orion", paramsXml); byte[] buf = WechatUtil.httpPost(Configure.UNIFIEDORDER_API, paramsXml); String contentXml = new String(buf); Map<String, String> resultMap = XMLUtil.doXMLParse(contentXml); return resultMap; } public static String getNonceStr() { Random random = new Random(); return MD5.MD5Encode(String.valueOf(random.nextInt(10000))); } public static String getTimeStamp() { return String.valueOf(System.currentTimeMillis() / 1000); } /** * 生成支付二维码 * @param request * @param response * @param width * @param height * @param text 微信生成预定id时,返回的codeUrl */ public static void getQRcode(HttpServletRequest request, HttpServletResponse response, Integer width, Integer height, String text) { if (width == null) { width = 300; } if (height == null) { height = 300; } String format = "jpg"; Hashtable hints = new Hashtable(); hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); BitMatrix bitMatrix; try { bitMatrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints); MatrixToImageWriter.writeToStream(bitMatrix, format, response.getOutputStream()); } catch (WriterException e) { e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
生成二维码需要两jar
<!-- google zxing 二维码jar begin --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.3.0</version> </dependency> <!-- google zxing 二维码jar begin -->
4、下面是用到的配置类
package com.caifu.tencent.common; /** * User: rizenguo * Date: 2014/10/29 * Time: 14:40 * 这里放置各种配置数据 */ public class Configure { //这个就是自己要保管好的私有Key了(切记只能放在自己的后台代码里,不能放在任何可能被看到源代码的客户端程序中) // 每次自己Post数据给API的时候都要用这个key来对所有字段进行签名,生成的签名会放在Sign这个字段,API收到Post数据的时候也会用同样的签名算法对Post过来的数据进行签名和验证 // 收到API的返回的时候也要用这个key来对返回的数据算下签名,跟API的Sign数据进行比较,如果值不一致,有可能数据被第三方给篡改 private static String key = "A6gB0Dy4dsfdssuPCPsdfdshkSCDQcr3eXS"; private static String appSecret="7584sdfdsfe4f26fadsfsdfs56f10728a"; //微信分配的公众号ID(开通公众号之后可以获取到) private static String appID = "wxaf0b86sdfsdf8afbf"; //微信支付分配的商户号ID(开通公众号的微信支付功能之后可以获取到) private static String mchID = "14012313702"; //受理模式下给子商户分配的子商户号 private static String subMchID = ""; //HTTPS证书的本地路径 private static String certLocalPath = ""; //HTTPS证书密码,默认密码等于商户号MCHID private static String certPassword = ""; //是否使用异步线程的方式来上报API测速,默认为异步模式 private static boolean useThreadToDoReport = true; //机器IP private static String ip = ""; //以下是几个API的路径: //1)被扫支付API public static String UNIFIEDORDER_API = "https://api.mch.weixin.qq.com/pay/unifiedorder"; public static String PAY_API = "https://api.mch.weixin.qq.com/pay/micropay"; //2)被扫支付查询API public static String PAY_QUERY_API = "https://api.mch.weixin.qq.d/pay/orderquery"; //3)退款API public static String REFUND_API = "https://api.mch.weixin.qq.com/secapi/pay/refund"; //4)退款查询API public static String REFUND_QUERY_API = "https://api.mch.weixin.qq.com/pay/refundquery"; //5)撤销API public static String REVERSE_API = "https://api.mch.weixin.qq.com/secapi/pay/reverse"; //6)下载对账单API public static String DOWNLOAD_BILL_API = "https://api.mch.weixin.qq.com/pay/downloadbill"; //7) 统计上报API public static String REPORT_API = "https://api.mch.weixin.qq.com/payitil/report"; public static boolean isUseThreadToDoReport() { return useThreadToDoReport; } public static void setUseThreadToDoReport(boolean useThreadToDoReport) { Configure.useThreadToDoReport = useThreadToDoReport; } public static String HttpsRequestClassName = "com.tencent.common.HttpsRequest"; public static void setKey(String key) { Configure.key = key; } public static void setAppID(String appID) { Configure.appID = appID; } public static void setMchID(String mchID) { Configure.mchID = mchID; } public static void setSubMchID(String subMchID) { Configure.subMchID = subMchID; } public static void setCertLocalPath(String certLocalPath) { Configure.certLocalPath = certLocalPath; } public static void setCertPassword(String certPassword) { Configure.certPassword = certPassword; } public static void setIp(String ip) { Configure.ip = ip; } public static String getKey(){ return key; } public static String getAppid(){ return appID; } public static String getMchid(){ return mchID; } public static String getSubMchid(){ return subMchID; } public static String getCertLocalPath(){ return certLocalPath; } public static String getCertPassword(){ return certPassword; } public static String getIP(){ return ip; } public static void setHttpsRequestClassName(String name){ HttpsRequestClassName = name; } }
在这里需要注意的配置
private static String key = “A6gB0Dy4dsfdssuPCPsdfdshkSCDQcr3eXS”;
这里的key 是登陆https://pay.weixin.qq.com/index.php/core/info (微信商户平台)设置的api_key
5、xml 解析工具类
package com.caifu.login.utils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.dom4j.io.SAXReader; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; /** * xml工具类 * * @author miklchen * */ public class XMLUtil { /** * 解析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 = XMLUtil.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(XMLUtil.getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } /** * 将requestxml通知结果转出啊成map * @param request * @return * @throws Exception */ public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { // 解析结果存储在HashMap Map<String, String> map = new HashMap<String, String>(); InputStream inputStream = request.getInputStream(); // 读取输入流 SAXReader reader = new SAXReader(); org.dom4j.Document document = reader.read(inputStream); // 得到xml根元素 org.dom4j.Element root = document.getRootElement(); // 得到根元素的所有子节点 List<org.dom4j.Element> elementList = root.elements(); // 遍历所有子节点 for (org.dom4j.Element e : elementList) map.put(e.getName(), e.getText()); // 释放资源 inputStream.close(); inputStream = null; return map; } }
6、整个后台服务已经完成,最后关闭页面微信支付二维码,告知用户支付已经完成了
var f; /* 定时任务方法,异步请求去查询订单是否支付*/ function GetOrder() { var orderId = $('#orderId').val(); if (orderId != '') { $.ajax({ url : "${base}/balance/auth/isPay?orderId=" + orderId, type : "GET", async : false, success : function(d) { if (d == "1") { //当获取到微信支付结果时,关闭二维码div $(".weixinpay").css("display", "none"); $("#zhichutankuang").css("display", "block"); ////当获取到微信支付结果时,关闭定时任务 clearInterval(f); // layer.alert('付款成功', { // skin : 'layui-layer-molv', // 样式类名 // closeBtn : 0 // }, function() { // location.href = "${base}/balance/auth/presentation?tjNo=" + $("#tjNo").val(); // }); } } }); } } //异步请求获取生成二维码的url $(".paylast").click(function() { var $payType = $('input:radio:checked').val(); var $money = $("#money").val(); var $tjReportType = $("#tjReportType").val(); var $tjNo = $("#tjNo").val(); $.ajax({ url : "${base}/balance/auth/wechatInfo", type : "POST", async : false, data : { payType : $payType, money : $money, tjNo : $tjNo, tjReportType : $tjReportType }, success : function(d) { if (d.resultCode == "1000") { //当请求成功时,设置二维码图片地址 $("#codeImg").attr('src', d.obj); $("#orderId").val(d.attributes.orderId); ////当请求成功时,启动定时任务,每隔3秒去后台查询一次订单是否成功 f = setInterval(GetOrder, 3000); // GetOrder(true); } } }); $(".selpaycon").css("display", "none"); $(".weixinpay").css("display", "block"); });
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。