Spring Boot + Cloud + RocketMQ 实现微信V3支付

下面我将为你提供一个完整的实现方案,使用Spring Boot、Spring Cloud和RocketMQ来实现微信V3支付功能。

1. 项目结构

wxpay-service/
├── src/
│   ├── main/
│   │   ├── java/com/example/wxpay/
│   │   │   ├── config/
│   │   │   │   ├── WxPayConfig.java
│   │   │   │   ├── RocketMQConfig.java
│   │   │   ├── controller/
│   │   │   │   ├── WxPayController.java
│   │   │   ├── service/
│   │   │   │   ├── WxPayService.java
│   │   │   │   ├── impl/
│   │   │   │   │   ├── WxPayServiceImpl.java
│   │   │   ├── mq/
│   │   │   │   ├── producer/
│   │   │   │   │   ├── PayMessageProducer.java
│   │   │   │   ├── consumer/
│   │   │   │   │   ├── PayMessageConsumer.java
│   │   │   ├── dto/
│   │   │   │   ├── PayRequest.java
│   │   │   │   ├── PayNotifyDTO.java
│   │   │   ├── exception/
│   │   │   │   ├── WxPayException.java
│   │   ├── resources/
│   │   │   ├── application.yml
│   │   │   ├── application-dev.yml
│   │   │   ├── wechat/
│   │   │   │   ├── apiclient_cert.pem
│   │   │   │   ├── apiclient_key.pem

2. 主要代码实现

2.1 添加依赖

<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring Cloud Starter -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    
    <!-- RocketMQ Starter -->
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>
    
    <!-- 微信支付V3 SDK -->
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-java</artifactId>
        <version>0.2.17</version>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2.2 微信支付配置类

/**
* WxPayConfig.java
* 
**/
@Configuration
public class WxPayConfig {
    
    // 商户号
    @Value("${wxpay.mchId}")
    private String mchId;
    
    // 商户序列号
    @Value("${wxpay.mchSerialNo}")
    private String mchSerialNo;
    
    // v3密钥
    @Value("${wxpay.apiV3Key}")
    private String apiV3Key;
    
    // 私钥
    @Value("${wxpay.privateKeyPath}")
    private String privateKeyPath;
    
    // 回调地址
    @Value("${wxpay.notifyUrl}")
    private String notifyUrl;
    
    @Bean
    public Credentials credentials() throws IOException {
        // 加载商户私钥
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(privateKeyPath));
        
        // 构建Credentials
        return new Credentials(
            mchId,
            new PrivateKeySigner(mchSerialNo, merchantPrivateKey)
        );
    }
    
    @Bean
    public HttpClient httpClient(Credentials credentials) {
        return WechatPayHttpClientBuilder.create()
            .withCredentials(credentials)
            .withValidator(new WechatPay2Validator(apiV3Key.getBytes(StandardCharsets.UTF_8)))
            .build();
    }
    
    @Bean
    public Payments payments(HttpClient httpClient) {
        return new Payments.Builder()
            .httpClient(httpClient)
            .build();
    }
    
}

2.3 支付服务实现

/** 
* WxPayService.java
**/
public interface WxPayService {

    /**
     * 发起JSAPI支付
     */
    JSONObject jsapiPay(PayRequest request);
    
    /**
     * 处理支付结果通知
     */
    String handlePayNotify(HttpServletRequest request);
    
}
/** 
* WxPayServiceImpl.java
**/
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
    
    private final Payments payments;
    private final String notifyUrl;
    private final String mchId;
    private final PayMessageProducer payMessageProducer;
    
    public WxPayServiceImpl(Payments payments, 
                           @Value("${wxpay.notifyUrl}") String notifyUrl,
                           @Value("${wxpay.mchId}") String mchId,
                           PayMessageProducer payMessageProducer) {
        this.payments = payments;
        this.notifyUrl = notifyUrl;
        this.mchId = mchId;
        this.payMessageProducer = payMessageProducer;
    }
    
    @Override
    public JSONObject jsapiPay(PayRequest request) {
        try {
            PrepayRequest prepayRequest = new PrepayRequest();
            prepayRequest.setAppid(request.getAppId());
            prepayRequest.setMchid(mchId);
            prepayRequest.setDescription(request.getDescription());
            prepayRequest.setOutTradeNo(request.getOutTradeNo());
            prepayRequest.setNotifyUrl(notifyUrl);
            
            Amount amount = new Amount();
            amount.setTotal(request.getTotal());
            prepayRequest.setAmount(amount);
            
            Payer payer = new Payer();
            payer.setOpenid(request.getOpenId());
            prepayRequest.setPayer(payer);
            
            // 调用微信支付API
            PrepayResponse response = payments.jsapi(prepayRequest);
			// 检查返回结果
            if (!result.containsKey("prepay_id")) {
            	throw new RuntimeException("微信v3支付统一下单失败");
            }
            
            // 构建前端需要的支付参数
            JSONObject result = new JSONObject();
            result.put("appId", request.getAppId());
            result.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
            result.put("nonceStr", RandomStringUtils.randomAlphanumeric(32));
            result.put("package", "prepay_id=" + response.getPrepayId());
            result.put("signType", "RSA");
            
            // 签名
            String message = String.format("%s\n%s\n%s\n%s\n", 
                request.getAppId(),
                result.getString("timeStamp"),
                result.getString("nonceStr"),
                result.getString("package"));
            
            String sign = Signatures.create().sign(message);
            result.put("paySign", sign);
            
            return result;
        } catch (Exception e) {
            log.error("微信支付失败", e);
            throw new WxPayException("微信支付失败");
        }
    }
    
    @Override
    public String handlePayNotify(HttpServletRequest request) {
        try {
            // 解析通知内容
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String signature = request.getHeader("Wechatpay-Signature");
            String serial = request.getHeader("Wechatpay-Serial");
            String body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            
            // 验证签名
            if (!verifySign(timestamp, nonce, body, signature, serial)) {
                throw new WxPayException("签名验证失败");
            }
            
            // 解析通知内容
            JSONObject result = JSONObject.parseObject(body);
            String outTradeNo = result.getJSONObject("resource")
                .getString("out_trade_no");
            String transactionId = result.getJSONObject("resource")
                .getString("transaction_id");
            String tradeState = result.getJSONObject("resource")
                .getString("trade_state");
            
            // 构建通知DTO
            PayNotifyDTO notifyDTO = new PayNotifyDTO();
            notifyDTO.setOutTradeNo(outTradeNo);
            notifyDTO.setTransactionId(transactionId);
            notifyDTO.setTradeState(tradeState);
            
            // 发送MQ消息异步处理
            payMessageProducer.sendPayNotifyMessage(notifyDTO);
            
            // 返回成功响应
            return buildSuccessResponse();
        } catch (Exception e) {
            log.error("处理支付通知异常", e);
            return buildFailResponse();
        }
    }
    
    private boolean verifySign(String timestamp, String nonce, String body, 
                             String signature, String serial) {
        // 实现签名验证逻辑
        // ...
    }
    
    private String buildSuccessResponse() {
        JSONObject response = new JSONObject();
        response.put("code", "SUCCESS");
        response.put("message", "成功");
        return response.toJSONString();
    }
    
    private String buildFailResponse() {
        JSONObject response = new JSONObject();
        response.put("code", "FAIL");
        response.put("message", "失败");
        return response.toJSONString();
    }
}

2.4 RocketMQ生产者

/**
* PayMessageProducer.java
**/
@Component
@Slf4j
public class PayMessageProducer {
    
    private final RocketMQTemplate rocketMQTemplate;
    
    // mq主题
    @Value("${rocketmq.topic.pay-notify}")
    private String payNotifyTopic;
    
    public PayMessageProducer(RocketMQTemplate rocketMQTemplate) {
        this.rocketMQTemplate = rocketMQTemplate;
    }
    
    public void sendPayNotifyMessage(PayNotifyDTO notifyDTO) {
        try {
            Message<PayNotifyDTO> message = MessageBuilder.withPayload(notifyDTO)
                .build();
            
            rocketMQTemplate.send(payNotifyTopic, message);
            log.info("发送支付通知消息成功: {}", notifyDTO);
        } catch (Exception e) {
            log.error("发送支付通知消息失败", e);
            throw new RuntimeException("发送支付通知消息失败", e);
        }
    }
}

2.5 RocketMQ消费者

/** 
* PayMessageConsumer.java
**/
@Slf4j
@Component
@RocketMQMessageListener(
    topic = "${rocketmq.topic.pay-notify}",
    consumerGroup = "${rocketmq.consumer-group.pay-notify}"
)
public class PayMessageConsumer implements RocketMQListener<PayNotifyDTO> {
    
    @Override
    public void onMessage(PayNotifyDTO notifyDTO) {
        try {
            log.info("收到支付通知消息: {}", notifyDTO);
            
            // 处理支付结果
            if ("SUCCESS".equals(notifyDTO.getTradeState())) {
                // 支付成功逻辑
                handlePaySuccess(notifyDTO);
            } else {
                // 支付失败逻辑
                handlePayFail(notifyDTO);
            }
        } catch (Exception e) {
            log.error("处理支付通知消息异常", e);
            // 可以添加重试逻辑
        }
    }
    
    private void handlePaySuccess(PayNotifyDTO notifyDTO) {
        // 实现支付成功业务逻辑
        // 1. 更新订单状态
        // 2. 记录支付流水
        // 3. 其他业务处理
    }
    
    private void handlePayFail(PayNotifyDTO notifyDTO) {
        // 实现支付失败业务逻辑
    }
}

2.6 控制器

/**
* WxPayController.java
**/
@RestController
@RequestMapping("/api/wxpay")
@Slf4j
public class WxPayController {
    
    private final WxPayService wxPayService;
    
    public WxPayController(WxPayService wxPayService) {
        this.wxPayService = wxPayService;
    }
    
    @PostMapping("/jsapi")
    public Result<JSONObject> jsapiPay(@RequestBody PayRequest request) {
        JSONObject result = wxPayService.jsapiPay(request);
        return Result.success(result);
    }
    
    @PostMapping("/notify")
    public String payNotify(HttpServletRequest request) {
        return wxPayService.handlePayNotify(request);
    }
}

2.7 配置文件

# application.yml
server:
  port: 8080

spring:
  application:
    name: wxpay-service
  rocketmq:
    name-server: 127.0.0.1:9876
    producer:
      group: wxpay-producer-group

wxpay:
  mchId: your_mch_id
  mchSerialNo: your_mch_serial_no
  apiV3Key: your_api_v3_key
  privateKeyPath: classpath:wechat/apiclient_key.pem
  notifyUrl: https://yourdomain.com/api/wxpay/notify

rocketmq:
  topic:
    pay-notify: PAY_NOTIFY_TOPIC
  consumer-group:
    pay-notify: PAY_NOTIFY_CONSUMER_GROUP

3. 使用说明

1、发起支付:

客户端调用 /api/wxpay/jsapi 接口获取支付参数

使用返回的参数调用微信JSAPI支付

2、支付结果通知:

微信支付成功后,会回调配置的 notifyUrl

服务端接收到通知后,验证签名并发送MQ消息

消费者异步处理支付结果

3、证书配置:

将微信支付证书(apiclient_cert.pemapiclient_key.pem)放在resources/wechat/目录下(测试、生产环境不建议)

4. 注意事项

1、安全性:

确保私钥文件安全,本地调试确定文件的可读性

验证所有支付通知的签名,本地调试确实小程序/公众号/微信支付的商户号appId这些都是准确无误的

使用HTTPS确保通信安全,本地调试可以使用内网穿透的工具把本地的端口通过域名暴露出去。工具地址https://user.zeronews.cc/

2、幂等性:

支付结果处理要实现幂等性,防止重复处理这里可以结合redis一起使用

3、异常处理:

合理处理各种异常情况,可以在项目中多打印日志,方便自己在本地调试时发现问题、解决问题

支付通知要有重试机制

4、AI辅助编码

微信支付自己提供了相应的依赖,现在微信支付写起来相对简单一些,但是微信支付的规范和要求比较细致,可能在编码的过程中或多或少都会出现一些问题,这个时候把你的遇到问题抛出去,其实很快就能得到解决。

4、RocketMQ配置:

根据实际环境配置NameServer地址

合理设置Topic和Consumer Group

这个实现方案结合了Spring Boot的便捷性、Spring Cloud的分布式能力以及RocketMQ的消息队列特性,实现了微信V3支付的完整流程,包括支付发起、异步通知处理和业务逻辑解耦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值