SpringBoot整合微信支付全流程开发指南

SpringBoot整合微信支付全流程开发指南
本文将详细介绍如何使用Java语言基于SpringBoot框架实现微信小程序支付功能,涵盖预下单、支付处理、退款等核心业务流程。
SpringBoot整合微信支付全流程开发指南
内容导航
一、微信支付配置准备
1、申请微信支付配置
二、搭建开发环境
1、环境要求
2、引入Maven依赖
3、配置application.yml
三、功能实现
1、支付配置类
2、商户初始化设置
3、JSAPI预下单
3.1、创建支付服务类
3.1、响应实体类
3.2、下单请求类
4、支付结果回调
5、通过商户单号查询
5.1 订单查询请求类
6、通过交易单号查询
7、发起退款
8、退款结果通知
四、数据库设计
五、控制器实现
六、退款功能补充
1.退款控制器
2.退款服务
七、退款回调处理
八、常见问题


一、微信支付配置准备

1、 申请微信支付配置

具体操作步骤请参考官方文档:微信支付配置指南
完成配置后需要获取以下关键信息:
* 应用ID
* 商户编号
* 商户私钥文件
* 证书序列号
* APIv3密钥

二、搭建开发环境

1、环境要求

技术栈:Java ,开发工具:IntelliJ IDEA ,框架:SpringBoot ,依赖管理:Maven

2、引入Maven依赖

com.github.wechatpay-apiv3
wechatpay-java
0.2.10

3、配置application.yml

#支付相关配置
wx:
pay:
#应用标识
appId: wx6b5xxxxxxxxxxxx
#商户编号
merchantId: 1xxxxxxxxx
#私钥内容
privateKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#证书序列号
merchantSerialNumber: 315DDXXXXXXXXXXXXXXXXXXXXXXXXXXX
#APIv3密钥
apiV3Key: XXXXXXXXXXXXXXXXXXXXXXXXXX
#支付回调地址
payNotifyUrl: https://xxx.xxxx.xxx.xxx/xx/xxxx/xxxx/openapi/wx/payNotify
#退款回调地址
refundNotifyUrl: https://xxx.xxx.xxx.xxx/xxxx/xxxx/xxxx/openapi/wx/refundNotify

三、功能实现

1、支付配置类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 微信支付参数配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig {
//应用ID
private String appId;
//商户号
private String merchantId;
//私钥内容
private String privateKey;
//证书序列号
private String merchantSerialNumber;
//APIv3密钥
private String apiV3Key;
//支付回调
private String payNotifyUrl;
//退款回调
private String refundNotifyUrl;
}

2、商户初始化设置

import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* 证书自动更新配置
*/
@Configuration
public class WxPayAutoCertificateConfig {
@Resource
private WxPayConfig wxPayConfig;
/**
* 初始化证书配置
*/
@Bean
public RSAAutoCertificateConfig rsaAutoCertificateConfig() {
return new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMerchantId())
.privateKey(wxPayConfig.getPrivateKey())
.merchantSerialNumber(wxPayConfig.getMerchantSerialNumber())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
}
}

3、JSAPI预下单

3.1、创建支付服务类

import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.exception.*;
import com.wechat.pay.java.service.payments.jsapi.*;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
/**
* 支付业务处理
*/
@Slf4j
@Service
public class WxPayService {
@Resource
private WxPayConfig wxPayConfig;
@Autowired
private RSAAutoCertificateConfig rsaAutoCertificateConfig;
/**
* 创建预支付订单
*/
public R createOrder(CreateOrderReq req) throws Exception {
if (req == null) {
return R.error("订单创建失败,参数缺失!");
}
String orderNo = req.getOutTradeNo();
try {
JsapiServiceExtension service = new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA")
.build();
PrepayRequest request = new PrepayRequest();
request.setAppid(wxPayConfig.getAppId());
request.setMchid(wxPayConfig.getMerchantId());
request.setDescription(req.getDescription());
request.setOutTradeNo(orderNo);
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
Amount amount = new Amount();
amount.setTotal(req.getTotalFee());
request.setAmount(amount);
Payer payer = new Payer();
payer.setOpenid(req.getWxOpenId());
request.setPayer(payer);
log.info("预下单请求参数:{}", JSONObject.toJSONString(request));
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
log.info("订单【{}】预支付成功,响应:{}", orderNo, response);
return R.ok().put("data", response);
} catch (HttpException e) {
log.error("HTTP请求异常:{}", e.getHttpRequest());
return R.error("下单失败");
} catch (ServiceException e) {
log.error("服务异常:{}", e.getErrorMessage());
return R.error("下单失败");
} catch (MalformedMessageException e) {
log.error("响应解析异常:{}", e.getMessage());
return R.error("下单失败");
}
}
}

3.1、响应实体类

import java.util.HashMap;
import java.util.Map;
/**
* 通用响应对象
*/
public class R extends HashMap {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
}
public static R error(String msg) {
return error(500, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok() {
return new R().put("msg", "success").put("data", new HashMap());
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
/**
* 添加数据
*/
public R putData(String key, Object value) {
Map map = (HashMap)this.get("data");
map.put(key, value);
return this;
}
}

3.2、下单请求类

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 下单请求参数
*/
@Data
public class CreateOrderReq {
@ApiModelProperty("商品描述")
private String description;
@ApiModelProperty("用户openid")
private String wxOpenId;
@ApiModelProperty("商户订单号")
private String outTradeNo;
@ApiModelProperty("支付金额(分)")
private Long totalFee;

4、支付结果回调

/**
* 处理支付回调
*/
@Transactional
public synchronized String payNotify(HttpServletRequest request) throws Exception {
log.info("------支付回调处理开始------");
// 获取请求头信息
String signature = request.getHeader("Wechatpay-Signature");
String nonce = request.getHeader("Wechatpay-Nonce");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String serial = request.getHeader("Wechatpay-Serial");
// 构建验证参数
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(HttpServletUtils.getRequestBody(request))
.build();
// 验证签名并解析
NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
Transaction transaction = parser.parse(requestParam, Transaction.class);
log.info("支付回调验证成功:{}", transaction);
Map<String, String> result = new HashMap<>();
result.put("code", "FAIL");
result.put("message", "处理失败");
// 查询订单状态
WxOrderEntity order = queryOrder(transaction.getOutTradeNo());
if (order != null && order.getPayStatus() == 1) {
result.put("code", "SUCCESS");
result.put("message", "处理成功");
return JSONObject.toJSONString(result);
}
if (Transaction.TradeStateEnum.SUCCESS != transaction.getTradeState()) {
log.info("订单【{}】支付未完成", transaction.getOutTradeNo());
if (order != null) {
updateOrderStatus(order, 2, nonce, transaction.getTransactionId());
}
return JSONObject.toJSONString(result);
}
if (order != null) {
completePayment(order, transaction, nonce);
}
result.put("code", "SUCCESS");
result.put("message", "处理成功");
return JSONObject.toJSONString(result);
}

5、通过商户单号查询

/**
* 通过商户单号查询
*/
@Transactional
public R queryOrderByOrderNo(QueryOrderReq req) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setMchid(wxPayConfig.getMerchantId());
request.setOutTradeNo(req.getOrderNo());
try {
JsapiServiceExtension service = new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA")
.build();
Transaction result = service.queryOrderByOutTradeNo(request);
Map<String, Object> response = buildQueryResponse(result);
if (Transaction.TradeStateEnum.SUCCESS == result.getTradeState()) {
processSuccessfulPayment(result, req.getOrderNo());
}
return R.ok().put("data", response);
} catch (ServiceException e) {
log.error("查询失败:{}", e.getErrorMessage());
return R.error("查询失败");
}
}

5.1 订单查询请求类

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 订单查询参数
*/
@Data
public class QueryOrderReq {
@ApiModelProperty("微信交易单号")
private String paymentNo;
@ApiModelProperty("商户订单号")
private String orderNo;
}

6、通过交易单号查询

/**
* 通过交易单号查询
*/
@Transactional
public R queryOrderByPaymentNo(QueryOrderReq req) {
QueryOrderByIdRequest request = new QueryOrderByIdRequest();
request.setMchid(wxPayConfig.getMerchantId());
request.setTransactionId(req.getPaymentNo());
try {
JsapiServiceExtension service = new JsapiServiceExtension.Builder()
.config(rsaAutoCertificateConfig)
.signType("RSA")
.build();
Transaction result = service.queryOrderById(request);
Map<String, Object> response = buildQueryResponse(result);
if (Transaction.TradeStateEnum.SUCCESS == result.getTradeState()) {
processSuccessfulPayment(result, null);
}
return R.ok().put("data", response);
} catch (ServiceException e) {
log.error("查询失败:{}", e.getErrorMessage());
return R.error("查询失败");
}
}

7、发起退款

/**
* 申请退款
*/
public R createRefund(String outTradeNo, Long totalAmount) {
Map<String, Object> response = new LinkedHashMap<>();
response.put("out_trade_no", outTradeNo);
response.put("success", false);
response.put("msg", "退款处理中");
String outRefundNo = "REFUND_" + outTradeNo;
response.put("out_refund_no", outRefundNo);
WxOrderEntity order = getOrderByOutTradeNo(outTradeNo);
if (order == null) {
return R.error("订单不存在");
}
updateOrderStatus(order, 4, null, null);
try {
RefundService service = new RefundService.Builder()
.config(rsaAutoCertificateConfig)
.build();
CreateRequest request = buildRefundRequest(outTradeNo, outRefundNo, totalAmount, order.getTotalFee().longValue());
Refund refund = service.create(request);
log.info("退款响应:{}", refund);
processRefundResult(refund, order, outTradeNo, outRefundNo, totalAmount, response);
} catch (ServiceException e) {
log.error("退款异常:{}", e.getErrorMessage());
return R.error("退款失败");
} catch (Exception e) {
log.error("处理异常:{}", e.getMessage());
return R.error("退款失败");
}
return R.ok().put("data", response);
}

8、退款结果通知

待补充内容...

四、数据库设计

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 订单表
CREATE TABLE `t_wx_order` (
`uuid` varchar(64) NOT NULL,
`trade_name` varchar(255) COMMENT '商品名称',
`description` varchar(255) COMMENT '订单描述',
`out_trade_no` varchar(64) COMMENT '商户订单号',
`transaction_id` varchar(64) COMMENT '微信订单号',
`total_fee` int(10) COMMENT '订单金额(分)',
`pay_nonce` varchar(64) COMMENT '支付随机串',
`pay_time` datetime COMMENT '支付时间',
`pay_date` date COMMENT '支付日期',
`pay_status` int(3) DEFAULT 0 COMMENT '支付状态',
`wx_open_id` varchar(64) COMMENT '用户openid',
`status` int(2) DEFAULT 0 COMMENT '删除状态',
`create_time` datetime COMMENT '创建时间',
`update_time` datetime COMMENT '更新时间',
PRIMARY KEY (`uuid`)
) COMMENT='微信订单表';
-- 支付记录表
CREATE TABLE `t_wx_pay_log` (
`uuid` varchar(64) NOT NULL,
`wx_open_id` varchar(64) COMMENT '用户openid',
`out_trade_no` varchar(64) COMMENT '商户订单号',
`out_refund_no` varchar(64) COMMENT '退款单号',
`transaction_id` varchar(64) COMMENT '微信订单号',
`total_fee` int(10) COMMENT '支付金额',
`pay_status` int(2) COMMENT '交易类型',
`create_time` datetime COMMENT '创建时间',
PRIMARY KEY (`uuid`)
) COMMENT='支付记录表';
SET FOREIGN_KEY_CHECKS = 1;

五、控制器实现

退款回调和控制器的实现暂略,有需要可联系获取。
补充工具类:

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* HTTP请求工具
*/
public class HttpServletUtils {
/**
* 获取请求体内容
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(request.getInputStream(), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
throw new IOException("读取请求数据异常");
}
return content.toString();
}
}

六、退款功能补充

1.退款控制器

```java
@RequestMapping(value = "/xcx/createRefund", method = RequestMethod.POST)
@ApiOperation("微信退款接口")
public ResponseEntity createRefund(String outTradeNo, Long totalAmount, String

版权声明:程序员胖胖胖虎阿 发表于 2025年5月13日 下午3:18。
转载请注明:SpringBoot整合微信支付全流程开发指南 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...