1、【基本配置】配置开发者服务器
2、右上角点击公众号名称,【功能设置】业务域名、JS接口安全域名
3、【接口权限】-【网页账号】设置域名
4、【微信支付】-【开发配置】-【支付授权目录】,配置你支付页面所在路径
比如我的路径是 http://XXXX/third_pay/weixin/wxpay_test.html;需要在此处配置 http://XXXX/third_pay/weixin
(如果是配置测试授权目录,还需要配置测试白名单,就是你要进行微信支付测试的帐号)
APP微信支付(需具备微信支付权限 - https://open.weixin.qq.com申请)
以下内容复制完,应该就能行了~
1、服务端方法
Controller
@RequestMapping("/initWechatConfig.xhtml") public void initWechatConfig(HttpServletRequest request, HttpServletResponse response) throws Exception { try { String initType = getParams("initType");//request.getParameter("initType"); if (CommonUtil.isNull(initType)) { throw new BizException(ErrorMsg.PARAMS_NULL); } if ("jsapi".equals(initType)) {//只有公众号微信支付才会调用该类型 HashMaprespMap = new HashMap (); String initUrl = getParams("initUrl");//当前网页地址 if (CommonUtil.isNull(initUrl)) { throw new BizException(ErrorMsg.PARAMS_NULL); } String noncestr = WxPayUtil.CreateNoncestr(); String timestamp = DateUtil.getTimestampWx(); String signature = null; //获取 String appId = Constants.GZH_APPID; Ticket ticket = getJsapiTicket(appId); signature = SHA1Util.signForJsapiTicket(ticket.getTicket(), noncestr, timestamp, initUrl); respMap.put("appId", appId); respMap.put("timestamp", timestamp); respMap.put("nonceStr", noncestr); respMap.put("signature", signature); writeSuccMsg(response, respMap); } else if ("pay".equals(initType) || "appPay".equals(initType)) {//pay 网页端;appPay app端 //校验必要参数 String outTradeNo = getParams("outTradeNo"); //根据outTradeNo获取订单信息 //... Double totalFee = 0.01; String body = "body"; //校验必要参数 //根据支付类型生成预订单 String openId = getParams("openId"); if ("pay".equals(initType) && CommonUtil.isNull(openId)) {//网页端微信支付,必定有openId throw new BizException(ErrorMsg.PARAMS_NULL); } String createIp = null//获取IP地址; //封装信息后,初始化微信预订单 log.info("********************************************************************"); String appId = null; String mchId = null; String key = null; String tradeType = null; if ("pay".equals(initType)) { tradeType = "JSAPI"; appId = Constants.GZH_APPID; mchId = Constants.GZH_PAY_MCHID; key = Constants.GZH_PAY_KEY; } else if ("appPay".equals(initType)) { tradeType = "APP"; appId = Constants.APP_APPID; mchId = Constants.APP_PAY_MCHID; key = Constants.APP_PAY_KEY; } String notify_url = Constants.UNIFIED_THIRD_PAY_NOTIFY_BROKER; log.info("微信支付 - 回调地址:" + notify_url); ThirdPayVo payVo = WxPayUtil.getPayElement(initType, appId, mchId, key, tradeType, outTradeNo, body, createIp, openId, totalFee, notify_url); if (CommonUtil.isNull(payVo)) { throw new BizException(ErrorMsg.PAY_INIT_FAIL); } writeSuccMsg(response, payVo); } else { throw new BizException(ErrorMsg.PARAMS_NULL); } } catch (Exception e) { log.error("初始化微信配置 异常:", e); writeErrorMsg(response, e.getMessage()); } }
(获取access_token需要注意区分全局access_token和绑定openid的access_token)
writeSuccMsg
response.setCharacterEncoding("utf-8"); response.setContentType("text/json"); PrintWriter out = null; try { out = response.getWriter(); } catch (Exception e) { throw new RuntimeException(e); } String jsonstr = JSON.toJSONString(jsonEntity); out.print(jsonstr);
-----------------------------------------------util包----------------------------------------------------
1、SHA1Util方法
public class SHA1Util { public static String signForJsapiTicket(String jsapiTicket, String noncestr, String timestamp, String initUrl) { String signature = null; // 注意这里参数名必须全部小写,且必须有序 jsapi_ticket=JSAPI_TICKET&noncestr=NONCESTR×tamp=TIMESTAMP&url=INIT_URL String signatureStr = Constants.ticket_url_params; signatureStr = signatureStr.replace("JSAPI_TICKET", jsapiTicket); signatureStr = signatureStr.replace("NONCESTR", noncestr); signatureStr = signatureStr.replace("TIMESTAMP", timestamp); signatureStr = signatureStr.replace("INIT_URL", initUrl); try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(signatureStr.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return signature; } private static String byteToHex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b : hash) { formatter.format("%02x", b); } String result = formatter.toString(); formatter.close(); return result; } }3、WxPayUtil
public class WxPayUtil { /** * */ protected final static Logger log = LoggerFactory.getLogger(WxPayUtil.class); public static String CreateNoncestr(int length) { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < length; i++) { Random rd = new Random(); res += chars.indexOf(rd.nextInt(chars.length() - 1)); } return res; } public static String CreateNoncestr() { String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; String res = ""; for (int i = 0; i < 16; i++) { Random rd = new Random(); res += chars.charAt(rd.nextInt(chars.length() - 1)); } return res; } public static String FormatQueryParaMap(HashMap2、MD5SignUtilparameters) throws SDKRuntimeException { String buff = ""; try { List<> > infoIds = new ArrayList<> >(parameters.entrySet()); Collections.sort(infoIds, new Comparator<> >() { public int compare(Map.Entry o1, Map.Entry o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); for (int i = 0; i < infoIds.size(); i++) { Map.Entry item = infoIds.get(i); if (item.getKey() != "") { buff += item.getKey() + "=" + URLEncoder.encode(item.getValue(), "utf-8") + "&"; } } if (buff.isEmpty() == false) { buff = buff.substring(0, buff.length() - 1); } } catch (Exception e) { throw new SDKRuntimeException(e.getMessage()); } return buff; } public static String FormatBizQueryParaMap(HashMap paraMap, boolean urlencode) throws SDKRuntimeException { String buff = ""; try { List<> > infoIds = new ArrayList<> >(paraMap.entrySet()); Collections.sort(infoIds, new Comparator<> >() { public int compare(Map.Entry o1, Map.Entry o2) { return (o1.getKey()).toString().compareTo(o2.getKey().toString()); } }); for (int i = 0; i < infoIds.size(); i++) { Map.Entry item = infoIds.get(i); // System.out.println(item.getKey()); if (item.getKey() != "") { String key = item.getKey(); String val = item.getValue(); if (urlencode) { val = URLEncoder.encode(val, "utf-8"); } buff += key.toLowerCase() + "=" + val + "&"; } } if (buff.isEmpty() == false) { buff = buff.substring(0, buff.length() - 1); } } catch (Exception e) { throw new SDKRuntimeException(e.getMessage()); } return buff; } public static boolean IsNumeric(String str) { if (str.matches("\\d *")) { return true; } else { return false; } } public static String toXml(Map arr) { String xml = " "; Iterator<> > iter = arr.entrySet().iterator(); while (iter.hasNext()) { Entry entry = iter.next(); String key = entry.getKey(); String val = entry.getValue(); if (IsNumeric(val)) { xml += "<" else="" key="" public="" return="" static="" string="" val="" xml=""> map) { Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("xml"); Set set = map.keySet(); for (String s : set) { Element e = root.addElement(s); e.addCDATA(map.get(s)); } try { //return new String(doc.asXML().getBytes(), "utf-8"); return new String(doc.asXML().getBytes(), "ISO8859-1"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // return doc.asXML(); return null; /*return doc.asXML();*/ } public static String GetBizSign(Map bizObj, String key) throws SDKRuntimeException { HashMap bizParameters = new HashMap (); List<> > infoIds = new ArrayList<> >(bizObj.entrySet()); Collections.sort(infoIds, new Comparator<> >() { public int compare(Map.Entry o1, Map.Entry o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); for (int i = 0; i < infoIds.size(); i++) { Map.Entry item = infoIds.get(i); if (!"".equals(item.getKey())) { bizParameters.put(item.getKey().toLowerCase(), item.getValue()); } } if (key == "") { throw new SDKRuntimeException("key为空!"); } String bizString = FormatBizQueryParaMap(bizParameters, false); return MD5SignUtil.Sign(bizString, key); } /** * 公众号微信支付 * @author ershuai * @date 2016年7月18日 下午3:53:52 * @version V1.0 * @param appId * @param noncestr * @param packagestr * @param singType * @param timestamp * @param key * @return * @throws SDKRuntimeException */ public static String GetPaySignForGzh(String appId, String noncestr, String packagestr, String singType, String timestamp, String key) throws SDKRuntimeException { String payString = "appId="+appId+"&nonceStr="+noncestr+"&package="+packagestr+"&signType="+singType+"&timeStamp="+timestamp; log.info("公众号支付签名串:" + payString); return MD5SignUtil.Sign(payString, key); } /** * APP微信支付 * @author ershuai * @date 2016年7月18日 下午3:55:21 * @version V1.0 * @param appId * @param noncestr * @param packagestr * @param partnerId * @param prepayId * @param timestamp * @param key * @return * @throws SDKRuntimeException */ public static String GetPaySignForApp(String appId, String noncestr, String packagestr, String partnerId, String prepayId, String timestamp, String key) throws SDKRuntimeException { //String payString = "appId="+appId+"&nonceStr="+noncestr+"&package="+packagestr+"&partnerId="+partnerId+"&prepayId="+prepayId+"&timeStamp=" + timestamp; String payString = "appid="+appId+"&noncestr="+noncestr+"&package="+packagestr+"&partnerid="+partnerId+"&prepayid="+prepayId+"×tamp=" + timestamp; log.info("APP支付签名串:" + payString); return MD5SignUtil.Sign(payString, key); } /** * * @author ershuai * @date 2016年7月5日 上午11:22:24 * @version V1.0 * @param resultCode * @param returnMsg * @return */ public static String getXMLString(String resultCode, String returnMsg) { Map map = new HashMap (); map.put("return_code", resultCode); map.put("return_msg", returnMsg); return getXMLString(map); } /** * map转xml * @author ershuai * @date 2016年7月5日 上午11:21:55 * @version V1.0 * @param map * @return */ public static String getXMLString(Map map) { // Pattern pattern = Pattern.compile("^[0-9.]$"); StringBuffer sb = new StringBuffer(); sb.append(" "); // String sign = map.get("sign"); if (map != null && !map.isEmpty()) { Iterator it = map.keySet().iterator(); while (it.hasNext()) { String key = it.next(); String value = map.get(key); // if(!pattern.matcher(value).matches()){ sb.append("<" 11:18:45="" author="" catch="" date="" ershuai="" exception="" key="" new="" param="" public="" request="" return="" static="" string="" throws="" try="" unsupportedencodingexception="" v1.0="" version="" xmlstr="sb.toString();"> parseXml(HttpServletRequest request) throws Exception { // 将解析结果存储在HashMap中 Map map = new HashMap (); // 从request中取得输入流 InputStream inputStream = request.getInputStream(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 @SuppressWarnings("unchecked") List elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) { map.put(e.getName(), e.getText()); log.info("e.getName():"+e.getName()); log.info("e.getText():"+e.getText()); } // 释放资源 inputStream.close(); inputStream = null; return map; } /** * 生成预订单 * @author ershuai * @date 2016年7月20日 下午4:07:09 * @version V1.0 * @param initType * @param appId * @param mchId * @param key * @param tradeType * @param outTradeNo * @param body * @param createIp * @param openId * @param totalFee * @param notifyUrl * @return * @throws Exception */ public static ThirdPayVo getPayElement(String initType, String appId, String mchId, String key, String tradeType, String outTradeNo, String body, String createIp, String openId, Double totalFee, String notifyUrl) throws Exception { Map params = new HashMap (); String noncestr = CreateNoncestr(); params.put("appid", appId); params.put("mch_id", mchId); params.put("nonce_str", noncestr); params.put("body", body); params.put("input_charset", "UTF-8"); params.put("out_trade_no", outTradeNo); params.put("total_fee", NumberUtil.formatNumber(totalFee * 100, NumberUtil.NUMBER_IN)); params.put("spbill_create_ip", createIp); //params.put("notify_url", !CommonUtil.isNull(notifyUrl) ? notifyUrl : notifyUrl); params.put("notify_url", notifyUrl); params.put("trade_type", tradeType); params.put("fee_type", "CNY"); if (!CommonUtil.isNull(openId)) { params.put("openid", openId); } String sign = GetBizSign(params, key); params.put("sign", sign); log.info("微信预订单参数:" + params); String xmlStr = toXml(params); String result = HttpClientUtil.httpsRequest(Constants.unified_order_url_v3, xmlStr); log.info("\n wechat getPayElement json \n:" + result + " \n"); Document document = DocumentHelper.parseText(result); Element res = document.getRootElement().element("prepay_id"); ThirdPayVo initVo = null; if (!CommonUtil.isNull(res)) { initVo = new ThirdPayVo(); initVo.setPrepayId(res.getText()); initVo.setOutTradeNo(outTradeNo); initVo.setTimestamp(DateUtil.getTimestampWx());//Long.toString(System.currentTimeMillis() / 1000) initVo.setNonceStr(noncestr); initVo.setSignType(Constants.SING_TYPE);//MD5 String paySign = null; if ("pay".equals(initType)) { initVo.setPackagestr("prepay_id=" + initVo.getPrepayId()); //获取签名 paySign = GetPaySignForGzh(appId, noncestr, initVo.getPackagestr(), initVo.getSignType(), initVo.getTimestamp(), key); } else if ("appPay".equals(initType)) {//app微信支付,需要返回mchId和appId initVo.setAppId(appId); initVo.setMchId(mchId); initVo.setPackagestr("Sign=WXPay");//APP微信支付是写死的; //获取签名 paySign = GetPaySignForApp(appId, noncestr, initVo.getPackagestr(), initVo.getMchId(), initVo.getPrepayId(), initVo.getTimestamp(), key); } initVo.setPaySign(paySign); log.info("返回内容:" + JSON.toJSONString(initVo)); } return initVo; } }
public class MD5SignUtil { public static String Sign(String content, String key) throws SDKRuntimeException { String signStr = ""; if ("".equals(key)) { throw new SDKRuntimeException("签名key不能为空!"); } if ("".equals(content)) { throw new SDKRuntimeException("签名内容不能为空!"); } signStr = content + "&key=" + key; return MD5Util.MD5(signStr).toUpperCase(); } public static boolean VerifySignature(String content, String sign, String md5Key) { String signStr = content + "&key=" + md5Key; String calculateSign = MD5Util.MD5(signStr).toUpperCase(); String tenpaySign = sign.toUpperCase(); return (calculateSign == tenpaySign); } }3、MD5Util
public class MD5Util { public final static String MD5(String s) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; try { byte[] btInput = s.getBytes(); // 获得MD5摘要算法的 MessageDigest 对象 MessageDigest mdInst = MessageDigest.getInstance("MD5"); // 使用指定的字节更新摘要 mdInst.update(btInput); // 获得密文 byte[] md = mdInst.digest(); // 把密文转换成十六进制的字符串形式 int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { e.printStackTrace(); return null; } } 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" }; }4、SDKRuntimeException
public class SDKRuntimeException extends Exception { private static final long serialVersionUID = 1L; public SDKRuntimeException(String str) { super(str); } }
-----------------------------------------------util包----------------------------------------------------
微信支付回调
@RequestMapping(value = "/notifyWxPay.xhtml") public void notifyWxPay(final HttpServletRequest request,final HttpServletResponse response) { String resultCode = "FAIL"; String resultMsg = "this's error"; try { log.info("**************************** 微信 服务订单 回调 开始 "+(new Timestamp(System.currentTimeMillis()))+" ************************************"); //获取微信回调信息 Map<> <> <> <> <> <> <> <" else="" key="" public="" return="" static="" string="" val="" xml=""> <> <> <> <" 11:18:45="" author="" catch="" date="" ershuai="" exception="" key="" new="" param="" public="" request="" return="" static="" string="" throws="" try="" unsupportedencodingexception="" v1.0="" version="" xmlstr="sb.toString();"> requestParams = WxPayUtil.parseXml(request); //return_code String return_code = requestParams.get("return_code"); //result_code String result_code = requestParams.get("result_code"); log.info("return_code:" + return_code); log.info("result_code:" + result_code); if ("SUCCESS".equals(result_code)&&"SUCCESS".equals(return_code)) {//成功回调 log.info("SUCCESS"); } else {//错误回调 log.info("FAIL"); } } catch (Exception e) { log.error("notifyInsurancePay 微信支付 服务订单 回调 异常:", e); } if ("SUCCESS".equals(resultCode)) { log.info("**************************** 服务订单 回调成功 "+(new Timestamp(System.currentTimeMillis()))+" **********************"); } else { log.info("**************************** 服务订单 回调失败 "+(new Timestamp(System.currentTimeMillis()))+" **********************"); } log.info("resultCode:" + resultCode); log.info("resultMsg:" + resultMsg); Map map = new HashMap (); map.put("return_code", resultCode); map.put("return_msg", resultMsg); String notifyXml = WxPayUtil.toXml(map); log.info("notifyXml after:" + notifyXml); try { notifyXml = new String(notifyXml.getBytes(), "ISO8859-1"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); log.error("notifyInsurancePay 微信支付 服务订单 回调 返回编码 异常:", e); } log.info("notifyXml:" + notifyXml); writeSuccMsg(response, notifyXml); }
上面的搞完了,服务端就完事了
接下来写网页
1、wxpay_test.html
2、common.js
/** * 获取参数 * @param paramsName * @returns */ function GetParams(paramsName) { var addr = window.location; if (addr != null && addr != '') { var reg = new RegExp("(^|&)" + paramsName + "=([^&]*)(&|$)"); var r = addr.search.substr(1).match(reg); if (r != null && r != '') { return decodeURI(r[2]); } else { null; } } else { return null; } } /** * 判断是否微信浏览器 * @returns {Boolean} */ function isWeiXin() { var ua = window.navigator.userAgent.toLowerCase(); if(ua.match(/MicroMessenger/i) == 'micromessenger'){ return true; } else { return false; } }3、weixin_common.js
/** * 初始化 微信支付 jsapi * @param initUrl 当前初始化网页地址 * @param outTradeNo 需要支付订单ID */ function initOrder(initUrl, outTradeNo) { if (!isWeiXin()) { hintDiv(null, '请在微信浏览器中打开'); return false; } //弹起遮盖 loading('正在初始化,请稍后…'); $.ajax({ async: false, type : "post", url : weixin_conf_url, data : { "initUrl" : initUrl, "initType" : "jsapi" }, dataType : "json", error : function(request) { alert('初始化失败'); }, success : function(ajaxRes) { closeAllIndex(); var state = ajaxRes.state; if (state == 1) { var data = ajaxRes.data; //初始化微信配置 wxPayConfig(data.appId, data.timestamp, data.nonceStr, data.signature); //支付准备 return wxPayReady(outTradeNo); } else { hintDiv(null, ajaxRes.msg); return false; } } }); }; /** * 初始化微信配置 * @param appId * @param timestamp * @param nonceStr * @param signature */ function wxPayConfig(appId, timestamp, nonceStr, signature) { wx.config({ debug: true, appId: appId, timestamp: timestamp, nonceStr: nonceStr, signature: signature, jsApiList: [ 'checkJsApi', 'chooseWXPay', ] }); wx.error(function (res) { alert(res.errMsg); }); } /** * 支付准备 * @param data */ function wxPayReady(outTradeNo) { var openId = GetParams("openId"); if (openId == null || openId == '') { hintDiv(null, '校验错误,请在微信端打开'); return; } //弹起遮盖 loading('正在生成预订单,请稍后…'); $.ajax({ async: false, type : "post", url : weixin_conf_url, data : { "outTradeNo" : outTradeNo, "initType" : "pay", "payWay" : "jsapi", "openId": openId }, dataType : "json", error : function(request) { hintDiv(null, '生成预订单失败'); return; }, success : function(ajaxRes) { closeAllIndex(); if (ajaxRes.state == 1) { var data = ajaxRes.data; wx.chooseWXPay({ timestamp: data.timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位 package: data.packagestr, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***) signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5' paySign: data.paySign, // 支付签名 success: function (res) { if(res.errMsg == "chooseWXPay:ok" ) { //hintDiv(null, '支付成功'); return true; } else { //hintDiv(null, '支付错误:' + res.errMsg); return false; } } }); } else { hintDiv(null, ajaxRes.msg); return false; } } }); }