直入主题:
- 注册微信公众号、微信支付商户号,并做好基础配置(不解释配置详情,无非是获取 appid,商户号等)
- 微信支付接口代码
- 微信支付回调接口代码
- 微信h5支付页面唤起字符密码界面完成支付
1,写代码之前准备工作
(1):利用开源代码 weixin-java-tools来开发效率很高,免去了很多繁琐的代码开发量;
链接 https://github.com/wechat-group/weixin-java-tools
搭建maven工程,引入:
<!-- 微信支付 开始--> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>2.8.0</version> <classifier>sources</classifier> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>2.8.0</version> <classifier>sources</classifier> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-common</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-common</artifactId> <version>2.8.0</version> <classifier>sources</classifier> </dependency> <!-- 微信支付 结束 -->
(2):微信支付开发接口需要 用到用户openId参数,至于微信授权获取用户openId这里不做解释;
(3):获得微信支付所要的配置文件,
这里我配置的有参数的都是必须要填写的,其他的可以不写,这里WX_WEB_URL这个是你网站的网址在回调的时候需要用到,我把这个地址配置到了配置文件里了。
#微信对接配置
WX_APPID=wx11231231231
WX_APPSECRET=321321321321321321
WX_TOKEN=
WX_AESKEY=
#微信支付商户号
WX_mchId=432432432
#微信支付平台商户API密钥
WX_mchKey=fgfdfdewrewrwer432432
#服务商模式下的子商户公众账号ID
WX_subAppId=
#服务商模式下的子商户号
WX_subMchId=
WX_keyPath=
WX_WEB_URL=http://7cvyn3.natappfree.cc/zc
(4):这个是我们从微信平台上获取的配置文件,还有两个重要的授权地址是我们要在微信平台上配置的,这个也是微信极为坑的一点
1:在微信公众平台——》权限接口——》网页授权获取用户基本信息
网址: https://mp.weixin.qq.com 微信公众号登录入口
必须填入外网域名并且要下载提示里的.txt文件,放到你网站的跟目录下,可以通过网站直接访问的路径
推荐一个内网穿透工具,调试微信开发非常方便https://natapp.cn/#download
这一步配置好后,还有一步就是微信支付的路径配置,这里最坑,我已在崩溃的边缘,这就是支付授权目录,注意这里是目录,不是仅仅是域名,后面要加你要掉起支付的html页面的最后一个路径要加/。当初我只写了域名,然后怎么都掉用起不来支付界面。坑了我好几天,
到这一步,如果都没有问,你基础的配置就完成了,下面就到了撸代码的步骤了,由于我们用的是开源工具,代码量其实非常简单,只需要一点配置就好。
2:撸代码,Java搞起来
2.1:先把微信配置文件 通过spring写到bean中;
<!-- 微信基础配置 --> <bean name="wxConfig" class="me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage"> <property name="appId" value="${WX_APPID}" /> <property name="secret" value="${WX_APPSECRET}" /> </bean> <!-- 微信核心service注入 --> <bean id="wxMpService" class="me.chanjar.weixin.mp.api.impl.WxMpServiceImpl"> <property name="wxMpConfigStorage" ref="wxConfig" /> </bean> <!-- 微信支付配置 --> <bean name="wxPayConfig" class="com.github.binarywang.wxpay.config.WxPayConfig"> <property name="appId" value="${WX_APPID}" /> <property name="mchId" value="${WX_mchId}" /> <property name="mchKey" value="${WX_mchKey}" /> <property name="subAppId" value="${WX_subAppId}" /> <property name="subMchId" value="${WX_subMchId}" /> <property name="keyPath" value="${WX_keyPath}" /> </bean> <!-- 微信支付service注入 --> <bean id="wxPayService" class="com.github.binarywang.wxpay.service.impl.WxPayServiceImpl"> <property name="config" ref="wxPayConfig" /> </bean>
2.2:直接进入重点,微信支付控制器,微信支付欲支付接口和回调接口:相关工具类封装在下面。
支付那我已经把业务和微信支付做了分离。
/** * 微信对接控制器,微信支付 * Project Name:zc_app_api * File Name:WxInitController.java * Package Name:com.zc.app.api.controller.weixin * Date:2017年9月30日下午3:10:19 * @author 吉文剑 */ @Controller @RequestMapping("/wxPay/") public class WxPayController extends BaseController { private final Logger logger = LoggerFactory.getLogger("WxPayController"); @Autowired private WxPayConfig payConfig; @Autowired private WxPayService payService; /** *微信公众号支付,业务方法 * @param response * @param request */ @RequestMapping(value = "toPayInfo") public void getJSSDKPayInfo(HttpServletResponse response, HttpServletRequest request) { DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); String orderSubject = "停车缴费0.01元";//商品描述 String merchantTradeNo = df.format(new Date());//商户订单号 Integer totalAmount = 1;//订单总金额,单位为分 String goodsDesc = "万达停车场下午1点-9点"; String gooodsCode = "code"+merchantTradeNo; Map<String, Object> payData = getPayData( merchantTradeNo, orderSubject, totalAmount, goodsDesc, gooodsCode); resultPayData(response,payData); return; } /** 微信公众号支付接口,通过参数生成网页微信js支付参数,掉起支付界面必须参数 * @param merchantTradeNo 商户订单号(必填) * @param orderSubject 订单名称(必填) * @param totalAmount 订单金额,单位分(必填) * @param goodsDesc 商品描述(可空) * @param gooodsCode 商品编码(可空) * Date:2017年12月4日上午11:04:04 * @author 吉文剑 */ private Map<String, Object> getPayData(String merchantTradeNo, String orderSubject, Integer totalAmount, String goodsDesc, String gooodsCode){ Map<String, Object> map = new HashMap<String, Object>(); ConstantUtils instance = ConstantUtils.getInstance(); ZuUser sessUser = (ZuUser) session.getAttribute(BaseController.SESSION_USER); if(null == sessUser || !StringUtils.isValid(sessUser.getOpenId())){ map.put("result", false); return map; } WxPayUnifiedOrderRequest prepayInfo = WxPayUnifiedOrderRequest.newBuilder() .openid(sessUser.getOpenId()) .outTradeNo(merchantTradeNo) .totalFee(totalAmount) .body(orderSubject) .tradeType(WeixinUtils.TRADE_TYPE) .spbillCreateIp(request.getRemoteAddr()) .notifyURL(instance.getPropertyValue("WEB_URL")+WeixinUtils.NOTIFY_URL) .nonceStr(WeixinUtils.getNonceStr()) .detail(goodsDesc) .productId(gooodsCode) .build(); try { Map<String, String> payInfo = this.payService.getPayInfo(prepayInfo); map.put("result", true); map.put("data", payInfo); } catch (WxPayException e) { map.put("result", false); map.put("data", e.getErrCodeDes()); this.logger.error(e.getErrCodeDes()); e.printStackTrace(); } return map; } /** * 微信通知支付结果的回调地址,notifyCallback * * @param request * @param response */ @RequestMapping(value = "notifyCallback") public void notifyCallback(HttpServletRequest request, HttpServletResponse response) { try { synchronized (this) { Map<String, String> kvm = XMLUtil.parseRequestXmlToMap(request); String orderCode = null;//回调 支付订单号 String resultCode = null;//回调支付是否成功状态吗 String totalFee = null;//支付金额 System.out.println("微信支付回调参数:"); System.out.println(kvm); if (SignUtils.checkSign(kvm, this.payConfig.getMchKey())) { orderCode = kvm.get("out_trade_no"); resultCode = kvm.get("result_code"); totalFee = kvm.get("total_fee"); if ("SUCCESS".equals(resultCode)) { //TODO(user) 微信服务器通知此回调接口支付成功后,通知给业务系统做处理 logger.info("out_trade_no: " + orderCode + " pay SUCCESS!"); response.getWriter().write(WeixinUtils.WX_PAY_SUCCESS); } else { this.logger.error("out_trade_no: " + orderCode + " result_code is FAIL"); response.getWriter().write(WeixinUtils.WX_PAY_FAIL); } } else { this.logger.error("out_trade_no: " + orderCode + " check signature FAIL"); response.getWriter().write(WeixinUtils.WX_PAY_SIGN_FAIL); } if("SUCCESS".equals(resultCode)){ //支付成功的业务逻辑 //totalFee 要判断支付金额是否等于订单金额!!! System.out.println("支付成功:订单号:"+orderCode+",支付金额:"+totalFee); }else{ //支付失败的业务逻辑 System.out.println("微信支付 回调 :*-************支付失败"); } } } catch (Exception e) { e.printStackTrace(); } } /** * 客户端返回JSON字符串 * @param response * @param object * @return */ protected String resultPayData(HttpServletResponse response, Object object) { try { response.reset(); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); //解决跨域问题 response.setHeader("Access-Control-Allow-Origin", "*"); response.getWriter().print(new Gson().toJson(object)); return null; } catch (IOException e) { return null; } } }
2.3:自己整理了一个工具类,你们后面会用的到的,基本都是写配置项,不会变的:
/** * 微信通用工具类 * Project Name:zc_app_api * File Name:WeixinUtils.java * Package Name:com.zc.app.api.utils * Date:2017年10月18日下午1:45:01 * @author 吉文剑 */ public class WeixinUtils { /** 微信支付回调支付成功,返回成功结果 */ public static final String WX_PAY_SUCCESS = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[ok]]></return_msg></xml>"; /** 微信支付回调支付失败,返回失败结果 */ public static final String WX_PAY_FAIL = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[result_code is FAIL]]></return_msg></xml>"; /** 微信支付回调支付sign验证失败,返回sign验证失败结果 */ public static final String WX_PAY_SIGN_FAIL = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[check signature FAIL]]></return_msg></xml>"; /** 微信支付回调地址路径 */ public static final String NOTIFY_URL = "/wxPay/notifyCallback.do"; /** 微信获取微信用户授权后用户信息 地址路径 */ public static final String OAUTH2_USERINFO_URL = "/wx/getOAuth2UserInfo.do"; /** 微信官方api接口 */ public static final String URL_OAUTH2 = "https://open.weixin.qq.com/connect/oauth2/authorize?"; /** 获取微信用户信息 */ public static final String SCOPE = "snsapi_userinfo"; /** 交易类型 :jsapi代表微信公众号支付 */ public static final String TRADE_TYPE = "JSAPI"; /** 获取微信openId URL */ public String getWxOpenIdUrl(String toUrl){ ConstantUtils instance = ConstantUtils.getInstance(); StringBuffer url = new StringBuffer(); url.append(URL_OAUTH2) .append("appid=").append(instance.getPropertyValue("WX_APPID")) .append("&redirect_uri=").append(instance.getPropertyValue("WX_WEB_URL") + OAUTH2_USERINFO_URL) .append("&response_type=code") .append("&scope=").append(SCOPE) .append("&state=").append(toUrl) .append("#wechat_redirect"); return url.toString(); } /** * 获得微信支付随机码 * @return * Date:2017年12月4日上午9:50:48 * @author 吉文剑 */ public static String getNonceStr(){ return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); } }
2.4:其他相关工具类:
/** * @ClassName: ConstantUtils * @Description: (读取配置文件的信息,一些公共的属性,参数配置在) * @author JiWenJian */ public class ConstantUtils { private static final String FILEPATH = "/zc_app_api.properties"; private static ConstantUtils instance; private ConstantUtils(){ } public static ConstantUtils getInstance(){ if(null == instance ){ instance = new ConstantUtils(); } return instance; } /** * @Description: (读取文件信息) * @author JiWenJian * @date 2012-11-22 下午01:42:08 * @param key * @return */ public String getPropertyValue(String key) { Properties props = new Properties(); try { InputStream in = getClass().getResourceAsStream(FILEPATH); props.load(in); return props.getProperty(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * @Description: (读取配置Integer数值信息) * @author JiWenJian * @date 2012-11-22 下午01:42:08 * @param key * @return */ public Integer getPropertyIntegerValue(String key) { Properties props = new Properties(); try { InputStream in = getClass().getResourceAsStream(FILEPATH); props.load(in); return Integer.parseInt(props.getProperty(key)); } catch (Exception e) { e.printStackTrace(); return null; } } }
/** * xml解析成map对象 */ public class XMLUtil { /** * 将微信服务器发送的Request请求中Body的XML解析为Map * * @param request * @return * @throws Exception */ public static Map<String, String> parseRequestXmlToMap(HttpServletRequest request) throws Exception { // 解析结果存储在HashMap中 Map<String, String> resultMap; InputStream inputStream = request.getInputStream(); resultMap = parseInputStreamToMap(inputStream); return resultMap; } /** * 将输入流中的XML解析为Map * * @param inputStream * @return * @throws DocumentException * @throws IOException */ public static Map<String, String> parseInputStreamToMap(InputStream inputStream) throws DocumentException, IOException { // 解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); //得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); //遍历所有子节点 for (Element e : elementList) { map.put(e.getName(), e.getText()); } //释放资源 inputStream.close(); return map; } /** * 将String类型的XML解析为Map * * @param str * @return * @throws Exception */ public static Map<String, String> parseXmlStringToMap(String str) throws Exception { Map<String, String> resultMap; InputStream inputStream = new ByteArrayInputStream(str.getBytes("UTF-8")); resultMap = parseInputStreamToMap(inputStream); return resultMap; } }
3:下面到了前台html页面,只剩下最后一小步了,只要掉起微信支付密码界面就大功告成了;
<head> <script type="text/javascript" src="http://g.alicdn.com/sj/lib/jquery/dist/jquery.min.js"></script> <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script src="../js/common.js"></script> <script type="text/javascript"> function toPay(){ if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } } function onBridgeReady(){ $.ajax({ type: "POST", url: DEF_GLOBAL_DOMAIN+"/wxPay/toPayInfo.do", success: function(data){ console.log(data); WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId" : data.data.appId, "timeStamp": data.data.timeStamp, "nonceStr" : data.data.nonceStr, "package" : data.data.package, "signType" : data.data.signType, "paySign" : data.data.paySign },function(res){ $("#msgId").html(res.err_msg); if(res.err_msg == "get_brand_wcpay_request:ok"){ $("#resId").html("支付成功"); // location.href="weixinPayResult.html";//支付成功跳转到指定页面 }else if(res.err_msg == "get_brand_wcpay_request:cancel"){ $("#resId").html("支付取消"); }else{ $("#resId").html("支付失败"); } }); } }); } </script> </head> <body> <div class="content"> <div class="form-area"> <div class="inp"> 支付0.01元 </div> <button class="em-submit-st2" onclick="toPay()" > 确定支付 </button> </div> 结果: <p/> <div id="resId"></div> 参数: <p/> <div id="invokeId"></div> <br/><p/> 返回: <p/> <div id="msgId"></div> </div> </body> </html>
到这里整个微信支付就完成了,总结:难点在于微信平台繁琐的配置,很繁琐,错一个整个都跑不通,还以为是自己代码有问题,坑爹。代码层,感觉没有什么复杂量,就做一些配置就好了,至于微信老是喜欢用各种 签名 sign
这个工具已经给我们解决了,只要各个地方的配置没有问题,其实就很简单了。
整个执行流程 是 :
微信点击支付按钮——》
发送ajax到支付请求控制器——》
返回支付参数——》
用支付参数,调用微信内嵌的掉起支付js方法,发起支付——》
支付结果同步返回结果——》
支付结果异步发送到后台回调控制器做结果处理
我在本文章中只做了代码层和简单的讲解,至于整个微信支付的细节,可以访问下面的作者:写的比较详细:https://www.cnblogs.com/yimiyan/p/5603657.html
感谢同是天涯代码人,花这么长时间来参读本文章,加油把。。。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/13974.html