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.pem
和apiclient_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支付的完整流程,包括支付发起、异步通知处理和业务逻辑解耦。