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
方法)
- 功能:当支付平台(如微信、支付宝)完成支付后,会向商户系统发送支付结果的通知。这个方法处理这种异步通知,验证通知的有效性,更新订单状态,并通过消息队列传递支付成功信息。
- 过程:
- 签名验证:利用
bestPayService.asyncNotify(notifyData)
验证支付平台发来的通知数据的签名,确保其真实性。 - 金额校验:从数据库中查询订单信息,对比通知中的金额与数据库记录的金额是否一致,以防数据篡改。
- 状态更新:若校验通过,更新订单的支付状态为成功,并记录交易号等相关信息。
- 发送MQ消息:使用
amqpTemplate.convertAndSend(QUEUE_PAY_NOTIFY, ...)
向名为payNotify
的队列发送支付成功的消息,供其他系统(如商城系统)进一步处理。 - 响应支付平台:根据支付平台的不同(微信或支付宝),返回特定格式的成功响应,告知支付平台通知已接收并处理。
查询订单支付信息 (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
(支付类型) - 功能:该方法接收客户端的支付请求,调用
PayService
的create
方法来创建支付订单。根据支付类型(微信原生支付WXPAY_NATIVE
或支付宝PC支付ALIPAY_PC
),生成不同的支付链接或表单信息,然后返回一个ModelAndView对象,以便前端根据支付方式渲染不同的页面或跳转至支付页面。
异步通知处理 (asyncNotify
方法)
- URL映射:
POST /pay/notify
- 参数:接收支付平台(如微信、支付宝)发送的异步通知数据(
notifyData
) - 功能:处理支付平台发送的支付结果通知,直接调用
PayService
的asyncNotify
方法进行异步通知的处理,并返回响应给支付平台。
查询订单支付信息 (queryByOrderId
方法)
- URL映射:
GET /pay/queryByOrderId
- 参数:
orderId
(订单ID) - 功能:根据订单ID查询支付信息,调用
PayService
的queryByOrderId
方法,并将查询结果以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;
}
这段代码是用来创建支付订单的方法。让我来解释一下它的逻辑:
- 首先,创建一个
PayInfo
对象,该对象包含了订单的相关信息,如订单号、支付平台类型、订单状态和订单金额等。 - 将
PayInfo
对象写入数据库,通过payInfoMapper.insertSelective(payInfo)
实现。 - 构建支付请求对象
PayRequest
,设置订单名称、订单号、订单金额和支付类型等信息。 - 调用
bestPayService.pay(request)
方法发起支付请求,返回支付响应对象PayResponse
。 - 最后,记录支付响应日志并返回响应对象。
@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("异步通知中错误的支付平台");
}
这段代码是异步通知处理的方法。让我解释一下它的主要步骤:
- 首先,通过异步通知数据
notifyData
调用bestPayService.asyncNotify(notifyData)
方法,获取支付响应对象PayResponse
。 - 对签名进行验证,确保通知的合法性。
- 从数据库中根据订单号查询支付信息
payInfo
,如果查询结果为空,抛出异常,并记录告警信息。 - 如果订单支付状态不是已支付,进行金额校验。如果通知中的订单金额与数据库中的金额不一致,抛出异常,并记录告警信息。
- 如果校验通过,修改订单支付状态为已支付,并更新支付平台的订单号。
- 发送支付信息到消息队列中,以便其他系统处理。
- 如果是微信支付,返回成功的 XML 响应;如果是支付宝支付,返回字符串 “success” 表示成功;否则抛出异常表示支付平台错误。
这段代码实现了异步通知的处理,并且通过消息队列将支付信息发送给其他系统处理。
@Override
public PayInfo queryByOrderId(String orderId) {
return payInfoMapper.selectByOrderNo(Long.parseLong(orderId));
}
这段代码是用来根据订单号查询支付信息的方法。让我解释一下它的逻辑:
接收订单号作为参数 orderId。
将订单号转换为 Long 类型,并调用 payInfoMapper.selectByOrderNo(orderId) 方法从数据库中查询支付信息。
返回查询结果,即支付信息对象 PayInfo。
这段代码简单明了,实现了根据订单号查询支付信息的功能。