微信支付
用第三方工具SDK微信支付
官方文档:
https://pay.weixin.qq.com/wiki
第三方SDK:
https://github.com/Pay-Group/best-pay-sdk
注意:与官方文档步骤做对比
1.引入依赖
<dependency> <groupId>cn.springboot</groupId> <artifactId>best-pay-sdk</artifactId> <version>1.1.0</version> </dependency>
2.项目配置文件配置
wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9f9079108e2e mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx //上面三个在微信商家平台得到 keyPath: /var/weixin_cert/h5.p12 notifyUrl: http://sell.natapp4.cc/sell/pay/notify------没这个就不能发起支付
3.创建参数配置文件,在授权的基础上补全
@Data @Component @ConfigurationProperties(prefix = "wechat") public class WechatAccountConfig { /** * 公众平台id */ private String mpAppId; /** * 公众平台密钥 */ private String mpAppSecret; /** * 开放平台id */ private String openAppId; /** * 开放平台密钥 */ private String openAppSecret; /** * 商户号 */ private String mchId; /** * 商户密钥 */ private String mchKey; /** * 商户证书路径 */ private String keyPath; /** * 微信支付异步通知地址 */ private String notifyUrl; /** * 微信模版id */ private Map<String, String> templateId; } /
4.配置支付配置文件
@Component public class WechatPayConfig { @Autowired private WechatAccountConfig accountConfig; @Bean public BestPayServiceImpl bestPayService() { BestPayServiceImpl bestPayService = new BestPayServiceImpl(); bestPayService.setWxPayH5Config(wxPayH5Config()); return bestPayService; } @Bean public WxPayH5Config wxPayH5Config() { WxPayH5Config wxPayH5Config = new WxPayH5Config(); wxPayH5Config.setAppId(accountConfig.getMpAppId()); wxPayH5Config.setAppSecret(accountConfig.getMpAppSecret()); wxPayH5Config.setMchId(accountConfig.getMchId()); wxPayH5Config.setMchKey(accountConfig.getMchKey()); wxPayH5Config.setKeyPath(accountConfig.getKeyPath()); wxPayH5Config.setNotifyUrl(accountConfig.getNotifyUrl()); return wxPayH5Config; } }
5.创建payservice与serviceImpl,先测试配置文件中参数是否能得到
public interface PayService { PayResponse create(OrderDTO orderDTO); } public class PayServiceImpl implements PayService { @Autowired private BestPayServiceImpl bestPayService; @Autowired private OrderService orderService; @Override public PayResponse create(OrderDTO orderDTO) { PayRequest payRequest = new PayRequest(); bestPayService.pay(payRequest); } }
6-1.参数json格式化
public class JsonUtil { public static String toJson(Object object) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting(); Gson gson = gsonBuilder.create(); return gson.toJson(object); } }
6-2. serviceimpl传入参数//这一步已经完成调用统一下单api,并返回预付单信息
@Service @Slf4j public class PayServiceImpl implements PayService { private static final String ORDER_NAME = "微信点餐订单";//自定义 @Autowired private BestPayServiceImpl bestPayService; @Autowired private OrderService orderService; @Override public PayResponse create(OrderDTO orderDTO) { PayRequest payRequest = new PayRequest(); payRequest.setOpenid(orderDTO.getBuyerOpenid()); payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue()); payRequest.setOrderId(orderDTO.getOrderId()); payRequest.setOrderName(ORDER_NAME); payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5); log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest)); PayResponse payResponse = bestPayService.pay(payRequest); log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse)); return payResponse; } }
7-1.静态注入参数,每次测试需手动修改数据由网页发起支付,代码放在后端,static文件夹下 pay.html-----------参数就是6-2返回的参数
<script> function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":"wxd898fcb01713c658", //公众号名称,由商户传入 "timeStamp":"1499569906", //时间戳,自1970年以来的秒数 "nonceStr":"bVsQpcfsKUAzO8r0", //随机串 "package":"prepay_id=wx2017070911112036b51eaddc0529394957", "signType":"MD5", //微信签名方式: "paySign":"78CA85306AB823156E1032EFB5BB1C76" //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); } 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(); } </script>
7-2.动态注入参数 使用freemaker模板引擎
7-2-1 引入freemaker依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
7-2-2在controller里引入模板引擎名称
@Controller @RequestMapping("/pay") public class PayController { @Autowired private OrderService orderService; @Autowired private PayService payService; @GetMapping("/create") public ModelAndView create(@RequestParam("orderId") String orderId, @RequestParam("returnUrl") String returnUrl, Map<String, Object> map) { //1. 查询订单 OrderDTO orderDTO = orderService.findOne(orderId); if (orderDTO == null) { throw new SellException(ResultEnum.ORDER_NOT_EXIST); } //2. 发起支付 PayResponse payResponse = payService.create(orderDTO); map.put("payResponse", payResponse); map.put("returnUrl", returnUrl); return new ModelAndView("pay/create", map); } }}
7-2-3 创建create模板,放在templates文件夹下 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,但并不保证它绝对可靠。 location.href = "${returnUrl}"; } ); } 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(); } </script>
7-2-4微信异步通知,改变支付状态
返回给微信处理结果的模板引擎
<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>
controller层
@Controller @RequestMapping("/pay") public class PayController { @Autowired private OrderService orderService; @Autowired private PayService payService; @GetMapping("/create") public ModelAndView create(@RequestParam("orderId") String orderId, @RequestParam("returnUrl") String returnUrl, Map<String, Object> map) { //1. 查询订单 OrderDTO orderDTO = orderService.findOne(orderId); if (orderDTO == null) { throw new SellException(ResultEnum.ORDER_NOT_EXIST); } //2. 发起支付 PayResponse payResponse = payService.create(orderDTO); map.put("payResponse", payResponse); map.put("returnUrl", returnUrl); return new ModelAndView("pay/create", map); } /** * 微信异步通知 * @param notifyData */ @PostMapping("/notify") public ModelAndView notify(@RequestBody String notifyData) { payService.notify(notifyData); //返回给微信处理结果 return new ModelAndView("pay/success"); } }
serviceimpl层 特别注意:金额校验时有精度差别,所以必须处理
金额校验精度处理
public class MathUtil { private static final Double MONEY_RANGE = 0.01; /** * 比较2个金额是否相等 * @param d1 * @param d2 * @return */ public static Boolean equals(Double d1, Double d2) { Double result = Math.abs(d1 - d2); if (result < MONEY_RANGE) { return true; }else { return false; } } }
serviceimpl层
@Service @Slf4j public class PayServiceImpl implements PayService { private static final String ORDER_NAME = "微信点餐订单"; @Autowired private BestPayServiceImpl bestPayService; @Autowired private OrderService orderService; @Override public PayResponse create(OrderDTO orderDTO) { PayRequest payRequest = new PayRequest(); payRequest.setOpenid(orderDTO.getBuyerOpenid()); payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue()); payRequest.setOrderId(orderDTO.getOrderId()); payRequest.setOrderName(ORDER_NAME); payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5); log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest)); PayResponse payResponse = bestPayService.pay(payRequest); log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse)); return payResponse; } @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; } }
8.退款
8-1 在官方文档中下载API安全证书,并配置文件路径
wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9f9079108e2e mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx keyPath: /var/weixin_cert/h5.p12//这个是API安全证书文件配置路径
orderserviceimpl
@Autowired private PayService payService; @Override @Transactional public OrderDTO cancel(OrderDTO orderDTO) { OrderMaster orderMaster = new OrderMaster(); //判断订单状态 if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) { log.error("【取消订单】订单状态不正确, orderId={}, orderStatus={}", orderDTO.getOrderId(), orderDTO.getOrderStatus()); throw new SellException(ResultEnum.ORDER_STATUS_ERROR); } //修改订单状态 orderDTO.setOrderStatus(OrderStatusEnum.CANCEL.getCode()); BeanUtils.copyProperties(orderDTO, orderMaster); OrderMaster updateResult = orderMasterRepository.save(orderMaster); if (updateResult == null) { log.error("【取消订单】更新失败, orderMaster={}", orderMaster); throw new SellException(ResultEnum.ORDER_UPDATE_FAIL); } //返回库存 if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) { log.error("【取消订单】订单中无商品详情, orderDTO={}", orderDTO); throw new SellException(ResultEnum.ORDER_DETAIL_EMPTY); } List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream() .map(e -> new CartDTO(e.getProductId(), e.getProductQuantity())) .collect(Collectors.toList()); productService.increaseStock(cartDTOList); //如果已支付, 需要退款 if (orderDTO.getPayStatus().equals(PayStatusEnum.SUCCESS.getCode())) { payService.refund(orderDTO); } return orderDTO; }
payserviceimpl
@Service @Slf4j public class PayServiceImpl implements PayService { private static final String ORDER_NAME = "微信点餐订单"; @Autowired private BestPayServiceImpl bestPayService; @Autowired private OrderService orderService; @Override public PayResponse create(OrderDTO orderDTO) { PayRequest payRequest = new PayRequest(); payRequest.setOpenid(orderDTO.getBuyerOpenid()); payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue()); payRequest.setOrderId(orderDTO.getOrderId()); payRequest.setOrderName(ORDER_NAME); payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5); log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest)); PayResponse payResponse = bestPayService.pay(payRequest); log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse)); return payResponse; } @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; } /** * 退款 * @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; } }
总结:
支付流程
授权:遇到redirect_url错误时,立马确定授权地址填错了
支付
授权+支付