博客首页:崇尚学技术的科班人
小肖来了
今天给大家带来的文章是《项目中如何整合微信支付》
希望各位小伙伴们能够耐心的读完这篇文章
博主也在学习阶段,如若发现问题,请告知,非常感谢
同时也非常感谢各位小伙伴们的支持
《如何获取微信用户的openId》
,在前面的文章中我也说过,微信用户的openId
是微信开发的前提。因为往往要涉及到微信的一系列的服务,我们都需要进行微信的网页授权。那么openId
就是一个关键了。而在这一篇文章中,我将跟大家介绍如何在自己的项目中整合微信支付。微信支付的话,涉及到money
的话,那么就会需要大量的微信官方的限制 (比如:如果需要测试的话,那么就需要微信商户号;如果需要进行退款的话,那么就需要商户号的证书)。
<dependency>
<groupId>cn.springbootgroupId>
<artifactId>best-pay-sdkartifactId>
<version>1.1.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author :小肖
* @date :Created in 2022/2/2 10:31
*/
@Component
@Data
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
/**
* 公众号id
*/
private String mpAppId;
/**
* 公众号密钥
*/
private String mpAppSecret;
/**
* 商户号
*/
private String mchId;
/**
* 商户密钥
*/
private String mchKey;
/**
* 商户证书路径
*/
private String keyPath;
/**
* 异步通知url
*/
private String notifyUrl;
}
配置信息
wechat:
# 测试号的appid
mpAppId: 你自己的测试号的appid
mpAppSecret: 你自己的测试号的appSecret
# 商户号的id
mchId: 1483469312
# 商户号的密钥
mchKey: c976503d34ca432c601361f969fd8d85
# 商户号的证书所在路径 一般微信退款需要使用到证书
keyPath: /var/weixin_cert/h5.p12
# 异步通知的url 一般用于处理订单的支付状态
notifyUrl: http://xiao-sell.natapp1.cc/sell/pay/notify
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;
/**
* @author :小肖
* @date :Created in 2022/2/2 20:12
*/
@Component
public class WechatPayConfig {
@Autowired
private WechatAccountConfig wechatAccountConfig;
@Autowired
private WxPayH5Config wxPayH5Config;
@Bean
public BestPayServiceImpl bestPayService(){
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
bestPayService.setWxPayH5Config(wxPayH5Config);
return bestPayService;
}
@Bean
public WxPayH5Config wxPayH5Config(){
WxPayH5Config wxPayH5Config = new WxPayH5Config();
wxPayH5Config.setAppId(wechatAccountConfig.getMpAppId());
wxPayH5Config.setAppSecret(wechatAccountConfig.getMpAppSecret());
wxPayH5Config.setMchId(wechatAccountConfig.getMchId());
wxPayH5Config.setMchKey(wechatAccountConfig.getMchKey());
wxPayH5Config.setKeyPath(wechatAccountConfig.getKeyPath());
wxPayH5Config.setNotifyUrl(wechatAccountConfig.getNotifyUrl());
return wxPayH5Config;
}
}
PayService接口
PayResponse create(OrderDTO orderDTO);
PayService接口实现类
public static final String ORDER_NAME = "微信点餐";
/**
* 1. 创建预付订单
* @param orderDTO
*/
@Override
public PayResponse create(OrderDTO orderDTO) {
PayRequest request = new PayRequest();
request.setOpenid(orderDTO.getBuyerOpenid());
request.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
request.setOrderId(orderDTO.getOrderId());
request.setOrderName(ORDER_NAME);
request.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
log.info("【微信支付】 request={}", JsonUtil.toJson(request));
// TODO 这里的微信支付由于没有微信商户号,所以测试不通过
PayResponse response = bestPayService.pay(request);
log.info("【微信支付】 response={}",JsonUtil.toJson(response));
return response;
}
@Autowired
private OrderService orderService;
@Autowired
private PayService payService;
// 前端的绿色的支付按钮 配置了该url进行绑定
@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. 发起支付
/**
* 1. 调用生成预付订单的业务
* 但是由于微信支付服务号的缘故,所以会报出签名错误的错误
*/
PayResponse payResponse = payService.create(orderDTO);
/**
* 2. 注入支付页面的相关参数以及实现跳转
*/
map.put("payResponse",payResponse);
map.put("returnUrl",returnUrl);
return new ModelAndView("pay/create",map);
}
<script>
// 微信支付开发的第二步,我们平时跳转微信支付的页面,由于没有微信支付的确切的账号,所以无法完成测试
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', { // 如果这里的package写错了就会显示 缺少total_fee参数
"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,但并不保证它绝对可靠。
// 3. 支付之后设置跳转的Url
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>
http://xiao-sell.natapp1.cc/sell/pay/
的url
进行注册。虽然我们都实现了支付的大部分内容,但是还有就是我们需要进行支付的异步通知,该异步通知是作用于商家的。在这个异步通知中,我们可以修改订单的支付状态等。
PayService接口
PayResponse notify(String notifyData);
PayService接口实现类
@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);
}
// 判断订单金额是否一致
// 这里判断金额的时候存在精度问题,所以我们需要进行改进
if(!MathUtil.equals(orderDTO.getOrderAmount().doubleValue(),payResponse.getOrderAmount())){
log.error("【微信支付】 异步通知 订单金额不一致 orderId = {}, 微信金额={}, 系统金额 = {}",
payResponse.getOrderId(),
orderDTO.getOrderAmount(),
payResponse.getOrderAmount());
throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
}
// 修改订单状态
orderService.paid(orderDTO);
return payResponse;
}
微信退款需要使用到相关的证书,我们需要将证书放置在我们项目能够扫描到的且在
yaml
文件中配置了的路径上。
取消订单业务逻辑
@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 = orderMasterDao.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.FINISHED.getCode())){
payService.refund(orderDTO);
}
return orderDTO;
}
PayService接口
RefundResponse refund(OrderDTO orderDTO);
PayService接口实现类
// 该退款接口用于了 取消订单的业务中
@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("【微信退款】 refundRequest={}",JsonUtil.toJson(refundRequest));
RefundResponse refundResponse = bestPayService.refund(refundRequest);
log.info("【微信退款】 refundResponse={}",JsonUtil.toJson(refundResponse));
return refundResponse;
}