springboot微信sdk方式进行微信支付
官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
第三方SDK:https://github.com/Pay-Group/best-pay-sdk
首先说明一下,微信支付功能接口权限只有微信服务号才拥有。
首先来看一下公众号支付的业务流程图。
支付开发中,一般在第10步异步通知成功了,就基本上可以百分百说明支付成功了,可以修改支付状态为已支付。
开发步骤:
1.pom.xml中添加Maven依赖
<!-- 微信公众号支付依赖 -->
<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.1.0</version>
</dependency>
2.微信服务号信息配置
package com.wechat.order.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /*微信公众号账号设置*/ @Data//使用了lombok依赖,相当于在代码中getter和setter的作用 @Component @ConfigurationProperties(prefix = "wechat")//账号信息写在了application.yml中。 public class WeChatAccountConfig { private String mpAppId; private String mpAppSecret; /** * 商户号 */ private String mchId; /** * 商户密钥 */ private String mchKey; /** * 商户证书路径 */ private String keyPath; /** * 异步通知路径 */ private String notifyUrl; }
package com.wechat.order.config; import com.lly835.bestpay.config.WxPayH5Config; import com.lly835.bestpay.service.impl.BestPayServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /*微信公众账号支付配置*/ @Component public class WeChatPayConfig { @Autowired private WeChatAccountConfig weChatAccountConfig; //把你要实例化的对象转化成一个Bean,放在IoC容器中 @Bean public BestPayServiceImpl bestPayService() { //支付类, 所有方法都在这个类里 BestPayServiceImpl bestPayService = new BestPayServiceImpl(); bestPayService.setWxPayH5Config(wxPayH5Config()); return bestPayService; } @Bean private WxPayH5Config wxPayH5Config() { WxPayH5Config wxPayH5Config = new WxPayH5Config(); wxPayH5Config.setAppId(weChatAccountConfig.getMpAppId());//设置微信公众号的appid wxPayH5Config.setAppSecret(weChatAccountConfig.getMpAppSecret());// 设置微信公众号的app corpSecret wxPayH5Config.setMchId(weChatAccountConfig.getMchId());// 设置商户号 wxPayH5Config.setMchKey(weChatAccountConfig.getMchKey());// 设置商户密钥 wxPayH5Config.setKeyPath(weChatAccountConfig.getKeyPath());// 设置商户证书路径 wxPayH5Config.setNotifyUrl(weChatAccountConfig.getNotifyUrl());// 设置支付后异步通知url return wxPayH5Config; } }3.调用方法发起支付
controller
/** * 微信支付发起 * @param orderId * @param returnUrl * @return */ @RequestMapping("/create") public ModelAndView create(@RequestParam("orderId")String orderId, @RequestParam("returnUrl")String returnUrl, HashMap<String, Object> map){ //根据订单id查询订单,数据库查询 OrderDTO orderDTO = orderService.findOne(orderId); if(orderDTO == null){//订单不存在抛出错误 throw new SellException(ResultEnum.ORDER_NOT_EXIST); } //微信支付 PayResponse payResponse = wechatPayService.create(orderDTO); //携带参数传入网页发起支付的前端模板 map.put("orderId", orderId); map.put("payResponse", payResponse); return new ModelAndView("pay/create",map); }service:
//微信支付发起 public PayResponse create(OrderDTO orderDTO) { PayRequest payRequest = new PayRequest(); payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);//支付方式,微信公众账号支付 payRequest.setOrderId(orderDTO.getOrderId());//订单号. payRequest.setOrderName(payName);//订单名字. payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());//订单金额. payRequest.setOpenid(orderDTO.getBuyerOpenid());//微信openid, 仅微信支付时需要 log.info("【微信支付】request={}", JsonUtil.toJson(payRequest));//将payRequest格式化一下,再显示在日志上,便于观看数据 PayResponse payResponse = bestPayService.pay(payRequest); log.info("【微信支付】response={}", JsonUtil.toJson(payResponse));//将payResponse格式化一下,再显示在日志上,便于观看响应后返回的数据); return payResponse; }JsonUtil格式转换工具类:
/** * 把对象转换成json格式,便于观看 */ public class JsonUtil { public static String toJson(Object object) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); return gson.toJson(object); } }4.在static文件夹下编写微信内H5调起支付的前端代码pay.html(可以从微信支付官方文档中拷贝代码),检测是否可以支付,此步骤可省略。
ps:为什么把支付的前端代码写在后台中?
如果放在前端的话,加入几个地方或几个模块都可以发起微信支付,每次发起都要调用这个前端代码,
这个前端代码就要写好几次。如果放在后端的话,只要给个请求,就可以直接调用这段代码。
5.对支付的前端代码动态注入参数以发起支付。
这里我们用到模板技术。
(1)在pom.xml中引入freemarker依赖
<!-- freemarker模板技术 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
(2)把controller中发起支付的方法返回类型改为ModelAndView,return new ModelAndView("path",model);//path为模板存放的路径,不包括模板后缀名。这里model我们采用Map类型,把payResponse和returnUrl携带过去。
(3)在相应的地方,创建模板文件xx.ftl。如我上述步骤的path为pay/create,则我在resources/templates目录下创建文件夹pay,在pay目录下再创建create.ftl文件。
(4)修改pay.html成模板,参数动态注入。${xxxx}
(5)模板中,支付后进行跳转到returnUrl。
create.ftl模板:
<script> function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":"${payResponse.appId}", //公众号名称,由商户传入 "timeStamp":"${payResponse.timeStamp}", //时间戳,自1970年以来的秒数 "nonceStr":"${payResponse.nonceStr}", //随机串 "package":"${payResponse.packAge}", "signType":"MD5", //微信签名方式: "paySign":"${payResponse.paySign}" //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } local.href="${returnUrl}"; ); } if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); 20000 document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } </script>6.填写微信支付的支付授权目录。
比如我的前端支付代码访问路径是http://xxxxx(网站根路径)/sell/pay.html。
支付授权目录就写http://xxxxx(网站根路径)/sell/
7.微信异步通知,并在相应的controller中进行支付状态修改。
依靠前端返回的支付成功来判断成功并不安全,容易被篡改。必须依靠后端异步通知来判断是否已支付成功
(1)验证签名,支付状态
(2)校验支付金额与订单金额是否一致,2个金额相减小于0.01即认为相等。
(3)修改订单支付状态
service:
/*微信支付异步通知*/ @Override public PayResponse notify(String notifyData) { //1. 验证签名 //2. 支付的状态 //3. 支付金额 //4. 支付人(下单人 == 支付人) PayResponse payResponse = bestPayService.asyncNotify(notifyData); log.info("【微信支付】异步通知, payResponse={}", JsonUtil.toJson(payResponse)); //查询订单 OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId()); //判断订单是否存在 if (orderDTO == null) { log.error("【微信支付】异步通知, 订单不存在, orderId={}", payResponse.getOrderId()); throw new SellException(ResultEnum.ORDER_NOT_EXIST); } //判断金额是否一致(0.10 0.1) if (!MathUtil.equals(payResponse.getOrderAmount(), orderDTO.getOrderAmount().doubleValue())) { log.error("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}", payResponse.getOrderId(), payResponse.getOrderAmount(), orderDTO.getOrderAmount()); throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR); } //修改订单的支付状态 orderService.paid(orderDTO); return payResponse; }controller:
/** * 微信异步通知 * @param notifyData */ @PostMapping("/notify") public ModelAndView notify(@RequestBody String notifyData) { wechatPayService.notify(notifyData); //返回给微信处理结果 return new ModelAndView("pay/success"); } }success.ftl异步通知成功模板:
<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>
8.微信退款
(1)设置退款人,退款金额,退款方式
(2)退款
service:
/** * 退款 * @param orderDTO */ @Override public RefundResponse refund(OrderDTO orderDTO) { RefundRequest refundRequest = new RefundRequest(); refundRequest.setOrderId(orderDTO.getOrderId()); refundRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue()); refundRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5); log.info("【微信退款】request={}", JsonUtil.toJson(refundRequest)); RefundResponse refundResponse = bestPayService.refund(refundRequest); log.info("【微信退款】response={}", JsonUtil.toJson(refundResponse)); return refundResponse; } }
阅读更多
- 二、spring Boot构建的Web应用中,基于MySQL数据库的几种数据库连接方式进行介绍
- spring boot框架学习学前掌握之重要注解(2)-通过java的配置方式进行配置spring
- SpringBoot基础-用yml方式进行配置
- spring boot框架学习学前掌握之重要注解(2)-通过java的配置方式进行配置spring
- springboot+angular项目 使用token方式进行权限验证
- spring boot框架学习学前掌握之重要注解(2)-通过java的配置方式进行配置spring
- spring学习笔记8--使用spring进行面向切面的(AOP)编程(2)XML配置方式
- SpringBoot使用FeignClient进行服务间的调用,传递headers信息
- 记一次外包项目微信接口开发流程-spring-boot
- springboot读取配置文件的三种方式
- Spring Boot 优雅的配置拦截器方式
- Spring 基于注解方式进行事务管理
- SpringBoot 使用@Aspect进行日志管理(基于反射代理模式)
- Spring 使用注解方式进行事务管理
- Springboot 打jar包分离lib,配置文件正确方式
- springboot linux启动方式
- SpringBoot学习:使用logback进行日志记录
- SpringBoot整合Redis(附带序列化方式对比)
- Spring Boot干货系列:(十)开发常用的热部署方式汇总
- Spring Boot教程(五)对log4j进行多环境不同日志级别的控制