service层:

package com.imooc.pay.service.impl;
 
//import导入包这些就省掉了,太长不写进来
 
@Slf4j    
@Service  // @Service 注解表示该类是一个服务类
 
public class PayServiceImpl implements IPayService {
 
    private final static String QUEUE_PAY_NOTIFY = "payNotify";
 
    //@Autowired 注解用于依赖注入
    @Autowired
    private BestPayService bestPayService;
 
    @Autowired
    private PayInfoMapper payInfoMapper;
 
    @Autowired
    private AmqpTemplate amqpTemplate;
 
    /**
     * 创建/发起支付:
     * 在该方法中,首先将支付信息写入数据库,
     * 然后使用 BestPayService 发起支付请求,
     * 并返回支付的响应结果
     *
     * @param orderId
     * @param amount
     */
    @Override
    public PayResponse create(String orderId, BigDecimal amount, BestPayTypeEnum bestPayTypeEnum) {
        //写入数据库
        PayInfo payInfo = new PayInfo(Long.parseLong(orderId),
                PayPlatformEnum.getByBestPayTypeEnum(bestPayTypeEnum).getCode(),
                OrderStatusEnum.NOTPAY.name(),
                amount);
        payInfoMapper.insertSelective(payInfo);
 
        PayRequest request = new PayRequest();
        request.setOrderName("4559066-最好的支付sdk");
        request.setOrderId(orderId);
        request.setOrderAmount(amount.doubleValue());
        request.setPayTypeEnum(bestPayTypeEnum);
 
        PayResponse response = bestPayService.pay(request);
        log.info("发起支付 response={}", response);
 
        return response;
 
    }
 
    /**
     * 异步通知处理:在方法内部,首先进行签名检验,
     * 然后查询数据库中的订单信息,并校验订单金额。
     * 如果校验通过,将修改订单支付状态,并通过消息队列发送支付信息。
     * 最后,根据支付平台的不同,返回相应的响应结果
     *
     * @param notifyData
     */
    @Override
    public String asyncNotify(String notifyData) {
        //1. 签名检验
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        log.info("异步通知 response={}", payResponse);
 
        //2. 金额校验(从数据库查订单)
        //比较严重(正常情况下是不会发生的)发出告警:钉钉、短信
        PayInfo payInfo = payInfoMapper.selectByOrderNo(Long.parseLong(payResponse.getOrderId()));
        if (payInfo == null) {
            //告警
            throw new RuntimeException("通过orderNo查询到的结果是null");
        }
        //如果订单支付状态不是"已支付"
        if (!payInfo.getPlatformStatus().equals(OrderStatusEnum.SUCCESS.name())) {
            //Double类型比较大小,精度。1.00  1.0
            if (payInfo.getPayAmount().compareTo(BigDecimal.valueOf(payResponse.getOrderAmount())) != 0) {
                //告警
                throw new RuntimeException("异步通知中的金额和数据库里的不一致,orderNo=" + payResponse.getOrderId());
            }
 
            //3. 修改订单支付状态
            payInfo.setPlatformStatus(OrderStatusEnum.SUCCESS.name());
            payInfo.setPlatformNumber(payResponse.getOutTradeNo());
            payInfoMapper.updateByPrimaryKeySelective(payInfo);
        }
 
        //TODO pay发送MQ消息,mall接受MQ消息
        amqpTemplate.convertAndSend(QUEUE_PAY_NOTIFY, new Gson().toJson(payInfo));
 
        if (payResponse.getPayPlatformEnum() == BestPayPlatformEnum.WX) {
            //4. 告诉微信不要再通知了
            return "<xml>\n" +
                    "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                    "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                    "</xml>";
        }else if (payResponse.getPayPlatformEnum() == BestPayPlatformEnum.ALIPAY) {
            return "success";
        }
 
        throw new RuntimeException("异步通知中错误的支付平台");
    }
 
    @Override
    public PayInfo queryByOrderId(String orderId) {
        return payInfoMapper.selectByOrderNo(Long.parseLong(orderId));
    }
}

代码解释:

这段代码展示了一个支付服务的实现类PayServiceImpl,它实现了接口IPayService,主要用于处理创建支付请求、处理支付异步通知以及查询订单支付信息等操作。下面是对关键功能的解析:

创建支付请求 (create 方法)

  • 功能:此方法用于处理创建支付请求的过程,包括将支付信息持久化到数据库,然后调用第三方支付服务bestPayService发起实际的支付请求。
  • 过程:首先,根据传入的订单ID(orderId)、金额(amount)以及支付方式(bestPayTypeEnum)创建PayInfo实体并保存到数据库。接着,构建PayRequest对象,设置必要的支付参数,并通过bestPayService.pay(request)发起支付,最后返回支付响应。

异步通知处理 (asyncNotify 方法)

  • 功能:当支付平台(如微信、支付宝)完成支付后,会向商户系统发送支付结果的通知。这个方法处理这种异步通知,验证通知的有效性,更新订单状态,并通过消息队列传递支付成功信息。
  • 过程
  1. 签名验证:利用bestPayService.asyncNotify(notifyData)验证支付平台发来的通知数据的签名,确保其真实性。
  2. 金额校验:从数据库中查询订单信息,对比通知中的金额与数据库记录的金额是否一致,以防数据篡改。
  3. 状态更新:若校验通过,更新订单的支付状态为成功,并记录交易号等相关信息。
  4. 发送MQ消息:使用amqpTemplate.convertAndSend(QUEUE_PAY_NOTIFY, ...)向名为payNotify的队列发送支付成功的消息,供其他系统(如商城系统)进一步处理。
  5. 响应支付平台:根据支付平台的不同(微信或支付宝),返回特定格式的成功响应,告知支付平台通知已接收并处理。

查询订单支付信息 (queryByOrderId 方法)

  • 功能:根据订单ID查询订单的支付信息。
  • 过程:简单地从数据库中通过订单ID获取对应的PayInfo记录并返回。

整体上,该服务类实现了支付流程的关键步骤,并且通过引入消息队列(MQ)机制来解耦支付成功后的业务逻辑处理,提高了系统的灵活性和可扩展性。

controller层:

package com.imooc.pay.controller;

import com.imooc.pay.pojo.PayInfo;
import com.imooc.pay.service.impl.PayService;
import com.lly835.bestpay.config.WxPayConfig;
import com.lly835.bestpay.enums.BestPayTypeEnum;
import com.lly835.bestpay.model.PayResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**

 */
@Controller
@RequestMapping("/pay")
@Slf4j
public class PayController {

	@Autowired
	private PayService payService;

	@Autowired
	private WxPayConfig wxPayConfig;

	@GetMapping("/create")
	public ModelAndView create(@RequestParam("orderId") String orderId,
							   @RequestParam("amount") BigDecimal amount,
							   @RequestParam("payType") BestPayTypeEnum bestPayTypeEnum
							   ) {
		PayResponse response = payService.create(orderId, amount, bestPayTypeEnum);

		//支付方式不同,渲染就不同, WXPAY_NATIVE使用codeUrl,  ALIPAY_PC使用body
		Map<String, String> map = new HashMap<>();
		if (bestPayTypeEnum == BestPayTypeEnum.WXPAY_NATIVE) {
			map.put("codeUrl", response.getCodeUrl());
			map.put("orderId", orderId);
			map.put("returnUrl", wxPayConfig.getReturnUrl());
			return new ModelAndView("createForWxNative", map);
		}else if (bestPayTypeEnum == BestPayTypeEnum.ALIPAY_PC) {
			map.put("body", response.getBody());
			return new ModelAndView("createForAlipayPc", map);
		}

		throw new RuntimeException("暂不支持的支付类型");
	}

	@PostMapping("/notify")
	@ResponseBody
	public String asyncNotify(@RequestBody String notifyData) {
		return payService.asyncNotify(notifyData);
	}

	@GetMapping("/queryByOrderId")
	@ResponseBody
	public PayInfo queryByOrderId(@RequestParam String orderId) {
		log.info("查询支付记录...");
		return payService.queryByOrderId(orderId);
	}
}

代码解释:
这段代码定义了一个名为PayController的控制器类,它负责处理与支付相关的HTTP请求。该控制器类中包含了几个关键的方法,用于创建支付、处理支付异步通知以及查询订单支付信息。下面是每个方法的简要说明:

创建支付请求 (create 方法)

  • URL映射GET /pay/create
  • 参数orderId(订单ID)、amount(支付金额)、payType(支付类型)
  • 功能:该方法接收客户端的支付请求,调用PayServicecreate方法来创建支付订单。根据支付类型(微信原生支付WXPAY_NATIVE或支付宝PC支付ALIPAY_PC),生成不同的支付链接或表单信息,然后返回一个ModelAndView对象,以便前端根据支付方式渲染不同的页面或跳转至支付页面。

异步通知处理 (asyncNotify 方法)

  • URL映射POST /pay/notify
  • 参数:接收支付平台(如微信、支付宝)发送的异步通知数据(notifyData
  • 功能:处理支付平台发送的支付结果通知,直接调用PayServiceasyncNotify方法进行异步通知的处理,并返回响应给支付平台。

查询订单支付信息 (queryByOrderId 方法)

  • URL映射GET /pay/queryByOrderId
  • 参数orderId(订单ID)
  • 功能:根据订单ID查询支付信息,调用PayServicequeryByOrderId方法,并将查询结果以JSON格式返回给前端。

通过这个控制器类,系统能够提供创建支付、处理支付结果通知以及查询支付状态的接口服务,是支付模块的核心控制层实现。它利用了Spring MVC框架的注解来进行路由配置、参数绑定和返回值处理,同时通过依赖注入 (@Autowired) 来使用服务层 (PayService) 和配置类 (WxPayConfig) 的实例。

这段代码是一个支付服务的实现,使用了BestPay SDK。它提供了创建支付订单、处理异步通知以及查询订单的功能。

在 create 方法中,首先将支付信息写入数据库,然后构建支付请求对象 PayRequest,设置订单名称、订单号、订单金额等信息,最后调用 bestPayService.pay(request) 方法发起支付,并返回支付响应对象。

在 asyncNotify 方法中,首先进行签名验证,然后通过异步通知中的订单号查询数据库中的支付信息,比较订单金额是否一致,并修改订单支付状态。如果是微信支付,返回 <return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg> 表示成功;如果是支付宝支付,返回字符串 “success” 表示成功;否则抛出异常表示支付平台错误。

最后,在 queryByOrderId 方法中,根据订单号查询支付信息并返回。

@Override
	public PayResponse create(String orderId, BigDecimal amount, BestPayTypeEnum bestPayTypeEnum) {
		//写入数据库
		PayInfo payInfo = new PayInfo(Long.parseLong(orderId),
				PayPlatformEnum.getByBestPayTypeEnum(bestPayTypeEnum).getCode(),
				OrderStatusEnum.NOTPAY.name(),
				amount);
		payInfoMapper.insertSelective(payInfo);

		PayRequest request = new PayRequest();
		request.setOrderName("4559066-最好的支付sdk");
		request.setOrderId(orderId);
		request.setOrderAmount(amount.doubleValue());
		request.setPayTypeEnum(bestPayTypeEnum);

		PayResponse response = bestPayService.pay(request);
		log.info("发起支付 response={}", response);

		return response;

	}

这段代码是用来创建支付订单的方法。让我来解释一下它的逻辑:

  1. 首先,创建一个 PayInfo 对象,该对象包含了订单的相关信息,如订单号、支付平台类型、订单状态和订单金额等。
  2. PayInfo 对象写入数据库,通过 payInfoMapper.insertSelective(payInfo) 实现。
  3. 构建支付请求对象 PayRequest,设置订单名称、订单号、订单金额和支付类型等信息。
  4. 调用 bestPayService.pay(request) 方法发起支付请求,返回支付响应对象 PayResponse
  5. 最后,记录支付响应日志并返回响应对象。
@Override
    public String asyncNotify(String notifyData) {
        //1. 签名检验
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        log.info("异步通知 response={}", payResponse);
 
        //2. 金额校验(从数据库查订单)
        //比较严重(正常情况下是不会发生的)发出告警:钉钉、短信
        PayInfo payInfo = payInfoMapper.selectByOrderNo(Long.parseLong(payResponse.getOrderId()));
        if (payInfo == null) {
            //告警
            throw new RuntimeException("通过orderNo查询到的结果是null");
        }
        //如果订单支付状态不是"已支付"
        if (!payInfo.getPlatformStatus().equals(OrderStatusEnum.SUCCESS.name())) {
            //Double类型比较大小,精度。1.00  1.0
            if (payInfo.getPayAmount().compareTo(BigDecimal.valueOf(payResponse.getOrderAmount())) != 0) {
                //告警
                throw new RuntimeException("异步通知中的金额和数据库里的不一致,orderNo=" + payResponse.getOrderId());
            }
 
            //3. 修改订单支付状态
            payInfo.setPlatformStatus(OrderStatusEnum.SUCCESS.name());
            payInfo.setPlatformNumber(payResponse.getOutTradeNo());
            payInfoMapper.updateByPrimaryKeySelective(payInfo);
        }
 
        //TODO pay发送MQ消息,mall接受MQ消息
        amqpTemplate.convertAndSend(QUEUE_PAY_NOTIFY, new Gson().toJson(payInfo));
 
        if (payResponse.getPayPlatformEnum() == BestPayPlatformEnum.WX) {
            //4. 告诉微信不要再通知了
            return "<xml>\n" +
                    "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                    "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                    "</xml>";
        }else if (payResponse.getPayPlatformEnum() == BestPayPlatformEnum.ALIPAY) {
            return "success";
        }
 
        throw new RuntimeException("异步通知中错误的支付平台");
    }

这段代码是异步通知处理的方法。让我解释一下它的主要步骤:

  1. 首先,通过异步通知数据 notifyData 调用 bestPayService.asyncNotify(notifyData) 方法,获取支付响应对象 PayResponse
  2. 对签名进行验证,确保通知的合法性。
  3. 从数据库中根据订单号查询支付信息 payInfo,如果查询结果为空,抛出异常,并记录告警信息。
  4. 如果订单支付状态不是已支付,进行金额校验。如果通知中的订单金额与数据库中的金额不一致,抛出异常,并记录告警信息。
  5. 如果校验通过,修改订单支付状态为已支付,并更新支付平台的订单号。
  6. 发送支付信息到消息队列中,以便其他系统处理。
  7. 如果是微信支付,返回成功的 XML 响应;如果是支付宝支付,返回字符串 “success” 表示成功;否则抛出异常表示支付平台错误。

这段代码实现了异步通知的处理,并且通过消息队列将支付信息发送给其他系统处理。

@Override
	public PayInfo queryByOrderId(String orderId) {
		return payInfoMapper.selectByOrderNo(Long.parseLong(orderId));
	}

这段代码是用来根据订单号查询支付信息的方法。让我解释一下它的逻辑:

接收订单号作为参数 orderId。
将订单号转换为 Long 类型,并调用 payInfoMapper.selectByOrderNo(orderId) 方法从数据库中查询支付信息。
返回查询结果,即支付信息对象 PayInfo。
这段代码简单明了,实现了根据订单号查询支付信息的功能。