SpringBoot集成微信支付,支付宝支付
一.主要流程:
用户发起支付,后端生成新订单,向支付平台获取预支付交易会话标识
前端拿着预支付交易会话标识调用支付平台处理支付接口
用户支付完成后,支付平台异步通知我们的回调接口
回调接口验证签名
验证通过后更新订单状态!!!
通知商家有新订单
注意事项:
1. 回调接口必须是公网可访问的
需要正确配置支付宝和微信的公钥用于验证签名
回调接口要做幂等处理,因为可能会收到多次通知
建议记录支付日志,方便排查问题
商家通知可以使用多种方式,如WebSocket、短信等
这样就实现了完整的支付流程:发起支付->用户支付->接收回调->更新状态->通知商家。
二、微信支付(接入APP支付和小程序支付)
官方文档
JSAPI/小程序下单_JSAPI支付|微信支付合作伙伴文档中心
1.准备工作
1.1 服务商配置:
- 申请服务商商户号(sp_mchid)和服务商APPID(sp_appid)
- 申请API证书和APIv3密钥
- 配置支付回调地址
1.2 子商户配置:
- 为子商户进件获取子商户号(sub_mchid)
- 小程序获取子商户APPID(sub_appid)
- APP获取子商户APPID(sub_appid)
# 微信支付配置
wxpay:
app-id: ${your-sp-appid} # 服务商应用ID
mch-id: ${your-sp-mchid} # 服务商户号
sub-app-id: ${your-sub-appid} # 子商户应用ID
sub-mch-id: ${your-sub-mchid} # 子商户号
mch-serial-no: ${your-serial-no}
private-key-path: classpath:cert/apiclient_key.pem
api-v3-key: ${your-api-v3-key}
notify-url: ${your-domain}/pay/notify/wxpay
return-url: ${your-domain}/pay/return/wxpay
refund-notify-url: ${your-domain}/pay/notify/wxpay/refund # 退款通知地址
2.支付流程
1.流程图
app:
小程序:
2.后端调用下单接口(其实就是一个发起支付的过程)返回prepay_id(SysFeeConfigServiceImpl)
小程序:
app:
大致流程
// 2.1 小程序下单
String apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
Map<String, Object> requestBody = new HashMap<>();
// 服务商信息
requestBody.put("sp_appid", wxConfig.getAppId());
requestBody.put("sp_mchid", wxConfig.getMchId());
requestBody.put("sub_appid", wxConfig.getSubAppId());
requestBody.put("sub_mchid", wxConfig.getSubMchId());
// 支付者信息
requestBody.put("payer", Collections.singletonMap("sub_openid", openId));
// 2.2 APP下单
String apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app";
Map<String, Object> requestBody = new HashMap<>();
// 服务商信息
requestBody.put("sp_appid", wxConfig.getAppId());
requestBody.put("sp_mchid", wxConfig.getMchId());
requestBody.put("sub_appid", wxConfig.getSubAppId());
requestBody.put("sub_mchid", wxConfig.getSubMchId());
完成具体代码实现
/**
* 处理微信支付下单
*/
private String processWeChatPayment(Order order) throws Exception {
// 构建基础请求参数
Map<String, Object> requestBody = new HashMap<>();
String apiUrl;
if (isWxMiniProgram()) {
// 小程序下单
apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
// 添加小程序特有参数
Map<String, String> payer = new HashMap<>();
payer.put("sub_openid", getWxOpenId());
requestBody.put("payer", payer);
} else {
// APP下单
apiUrl = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app";
}
// 服务商信息
requestBody.put("sp_appid", wxConfig.getAppId());
requestBody.put("sp_mchid", wxConfig.getMchId());
requestBody.put("sub_appid", wxConfig.getSubAppId());
requestBody.put("sub_mchid", wxConfig.getSubMchId());
// 订单信息
requestBody.put("description", order.getDescription());
requestBody.put("out_trade_no", order.getOrderNo());
requestBody.put("notify_url", wxConfig.getNotifyUrl());
// 订单金额
Map<String, Object> amount = new HashMap<>();
amount.put("total", order.getAmount().multiply(new BigDecimal("100")).intValue());
amount.put("currency", "CNY");
requestBody.put("amount", amount);
// 场景信息
Map<String, Object> sceneInfo = new HashMap<>();
sceneInfo.put("payer_client_ip", getClientIp());
if (order.getMerchantId() != null) {
Map<String, String> storeInfo = new HashMap<>();
storeInfo.put("id", order.getMerchantId().toString());
storeInfo.put("name", "xxx店");
storeInfo.put("area_code", "000000");
storeInfo.put("address", "xxx地址");
sceneInfo.put("store_info", storeInfo);
}
requestBody.put("scene_info", sceneInfo);
// 结算信息
Map<String, Object> settleInfo = new HashMap<>();
settleInfo.put("profit_sharing", true);
requestBody.put("settle_info", settleInfo);
// 发送请求
String prepayId = sendWxPayRequest(apiUrl, requestBody);
// 根据场景返回不同的支付参数
return isWxMiniProgram() ?
generateWxMiniPayParams(prepayId) :
generateWxAppPayParams(prepayId);
}
/**
* 发送微信支付请求
*/
private String sendWxPayRequest(String apiUrl, Map<String, Object> requestBody) throws Exception {
HttpPost httpPost = new HttpPost(apiUrl);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/json");
String token = generateToken(wxConfig.getMchId(),
wxConfig.getMchSerialNo(),
wxConfig.getPrivateKeyPath());
httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + token);
httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(requestBody)));
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
String responseBody = EntityUtils.toString(response.getEntity());
Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
String prepayId = (String) result.get("prepay_id");
if (prepayId == null) {
throw new RuntimeException("获取prepay_id失败: " + responseBody);
}
return prepayId;
}
}
/**
* 生成小程序调起支付所需的参数
*/
private String generateWxMiniPayParams(String prepayId) throws Exception {
Map<String, Object> payParams = new HashMap<>();
payParams.put("appId", wxConfig.getSubAppId()); // 注意这里使用子商户APPID
payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
payParams.put("nonceStr", generateNonceStr());
payParams.put("package", "prepay_id=" + prepayId);
payParams.put("signType", "RSA");
// 签名
String message = payParams.get("appId") + "\n" +
payParams.get("timeStamp") + "\n" +
payParams.get("nonceStr") + "\n" +
payParams.get("package") + "\n";
payParams.put("paySign", signMessage(message));
return objectMapper.writeValueAsString(payParams);
}
/**
* 生成APP调起支付所需的参数
*/
private String generateWxAppPayParams(String prepayId) throws Exception {
Map<String, Object> payParams = new HashMap<>();
payParams.put("appid", wxConfig.getSubAppId()); // 注意这里使用子商户APPID
payParams.put("partnerid", wxConfig.getSubMchId()); // 注意这里使用子商户号
payParams.put("prepayid", prepayId);
payParams.put("package", "Sign=WXPay");
payParams.put("noncestr", generateNonceStr());
payParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
// 签名
String message = payParams.get("appid") + "\n" +
payParams.get("timestamp") + "\n" +
payParams.get("noncestr") + "\n" +
payParams.get("prepayid") + "\n";
payParams.put("sign", signMessage(message));
return objectMapper.writeValueAsString(payParams);
}
/**
* 判断是否是小程序环境
*/
private boolean isWxMiniProgram() {
// TODO: 根据实际情况判断是否是小程序环境
// 可以通过请求头或其他方式判断
return true;
}
/**
* 获取微信openid
*/
private String getWxOpenId() {
// TODO: 获取当前用户的微信openid
// 可以从session或其他地方获取
return "user_openid";
}
/**
* 生成随机字符串
*/
private String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 签名
*/
private String signMessage(String message) throws Exception {
// TODO: 使用商户私钥对消息进行签名
return "signature";
}
/**
* 生成认证Token
*/
private String generateToken(String mchId, String serialNo, String privateKeyPath) throws Exception {
// TODO: 实现微信支付V3认证token生成
// 1. 获取请求时间戳
// 2. 生成随机串
// 3. 构造签名串
// 4. 使用商户私钥签名
// 5. 按照格式拼接认证头
return "mchid=\"" + mchId + "\",nonce_str=\"xxx\",timestamp=\"xxx\",serial_no=\"" + serialNo + "\",signature=\"xxx\"";
}
/**
* 获取客户端IP
*/
private String getClientIp() {
// TODO: 实现获取客户端IP
return "127.0.0.1";
}
3..前端拿着prepay_id去调起支付接口
小程序:
app:
// 3.1 小程序调起支付
wx.requestPayment({
appId: result.appId,
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: 'RSA',
paySign: result.paySign,
success(res) { },
fail(err) { }
})
// 3.2 APP调起支付
// Android
PayReq request = new PayReq();
request.appId = payParams.appid;
request.partnerId = payParams.partnerid;
request.prepayId = payParams.prepayid;
request.packageValue = payParams.package;
request.nonceStr = payParams.noncestr;
request.timeStamp = payParams.timestamp;
request.sign = payParams.sign;
api.sendReq(request);
// iOS
PayReq *request = [[PayReq alloc] init];
request.partnerId = payParams[@"partnerid"];
request.prepayId = payParams[@"prepayid"];
request.package = payParams[@"package"];
request.nonceStr = payParams[@"noncestr"];
request.timeStamp = [payParams[@"timestamp"] intValue];
request.sign = payParams[@"sign"];
[WXApi sendReq:request];
4.后端调用支付结果回调接口(PayCallbackController)
//大致流程
@PostMapping("/pay/notify/wxpay")
public String wxpayNotify(HttpServletRequest request) {
// 4.1 验证签名
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
// 4.2 解密数据
Map<String, Object> resource = (Map<String, Object>)notifyData.get("resource");
String decryptData = decryptResource(resource);
// 4.3 处理订单
if ("SUCCESS".equals(tradeState)) {
// 更新订单状态
updateOrderStatus(order, transactionId);
// 处理分账(可以像我具体实现代码一样直接集成到updateOrderStatus里面)
handleProfitSharing(order, transactionId);
}
}
具体回调通知完整具体实现
/**
* 微信支付回调通知
*/
@PostMapping("/wxpay")
public String wxpayNotify(HttpServletRequest request, HttpServletResponse response) {
try {
// 1. 获取请求头参数
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String signature = request.getHeader("Wechatpay-Signature");
String serial = request.getHeader("Wechatpay-Serial");
// 2. 读取请求体
String body = readRequestBody(request);
// 3. 验证签名
if (!verifyWxSign(timestamp, nonce, body, signature, serial)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return "{\"code\": \"FAIL\",\"message\": \"签名验证失败\"}";
}
// 4. 解析通知数据
Map<String, Object> notifyData = objectMapper.readValue(body, Map.class);
if (!"TRANSACTION.SUCCESS".equals(notifyData.get("event_type"))) {
response.setStatus(HttpServletResponse.SC_OK);
return "";
}
// 5. 解密resource数据
Map<String, Object> resource = (Map<String, Object>) notifyData.get("resource");
String decryptData = decryptResource(resource);
Map<String, Object> orderInfo = objectMapper.readValue(decryptData, Map.class);
// 6. 处理订单
String orderNo = (String) orderInfo.get("out_trade_no");
String transactionId = (String) orderInfo.get("transaction_id");
String tradeState = (String) orderInfo.get("trade_state");
Order order = orderService.selectOrderByNo(orderNo);
if (order != null && "SUCCESS".equals(tradeState)) {
// 更新订单状态
updateOrderStatus(order, transactionId, "WECHAT");
}
// 7. 返回成功
response.setStatus(HttpServletResponse.SC_OK);
return "";
} catch (Exception e) {
log.error("处理微信支付回调异常", e);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return "{\"code\": \"FAIL\",\"message\": \"处理失败\"}";
}
}
5.分账处理(SysFeeConfigServiceImpl)
//大致流程
private void handleProfitSharing(Order order, String transactionId) {
// 5.1 计算分账金额
BigDecimal totalAmount = order.getAmount();
BigDecimal serviceFee = totalAmount.multiply(new BigDecimal("0.05"));//平台收取5%服务费
// 5.2 添加分账接收方
List<ProfitSharingReceiver> receivers = new ArrayList<>();
// 平台分账
receivers.add(new ProfitSharingReceiver("平台", serviceFee));
// 商家分账
receivers.add(new ProfitSharingReceiver("商家", totalAmount.subtract(serviceFee)));
// 5.3 请求分账
profitSharingService.profitSharing(order.getOrderNo(), transactionId, receivers);
}
完整分账代码
/**
* 处理订单支付成功后的分账
*/
private void handleProfitSharing(Order order, String transactionId, String payMethod) {
try {
// 计算平台服务费
BigDecimal totalAmount = order.getAmount();
SysFeeConfig sysFeeConfig=sysFeeConfigService.selectFeeConfigByType(order.getType());
BigDecimal serviceFeeRate = sysFeeConfig.getFeePercent();// 平台抽成比例
BigDecimal serviceFee = totalAmount.multiply(serviceFeeRate);
order.setServiceFee(serviceFee);
// 构建分账请求
ProfitSharingRequest request = new ProfitSharingRequest();
request.setTransactionId(transactionId);
request.setOutOrderNo(order.getOrderNo());
// 添加分账接收方
List<ProfitSharingReceiver> receivers = new ArrayList<>();
// 平台分账
ProfitSharingReceiver platformReceiver = new ProfitSharingReceiver();
platformReceiver.setType("MERCHANT_ID");
platformReceiver.setAccount(getPlatformAccount(payMethod)); // 获取平台收款账号
platformReceiver.setAmount(serviceFee);
platformReceiver.setDescription("平台服务费");
receivers.add(platformReceiver);
// 商家分账
ProfitSharingReceiver merchantReceiver = new ProfitSharingReceiver();
merchantReceiver.setType("MERCHANT_ID");
merchantReceiver.setAccount(order.getMerchantId().toString());
merchantReceiver.setAmount(totalAmount.subtract(serviceFee));
merchantReceiver.setDescription("商家结算");
receivers.add(merchantReceiver);
request.setReceivers(receivers);
// 发起分账请求
ProfitSharingResult result = profitSharingService.profitSharing(request, payMethod);
if (result.isSuccess()) {
// 更新订单分账状态
order.setProfitSharingStatus(Order.PROFIT_SHARING_SUCCESS);
orderService.updateOrder(order);
log.info("订单{}分账成功", order.getOrderNo());
} else {
log.error("订单{}分账失败: {}", order.getOrderNo(), result.getErrorMsg());
// 可以考虑重试机制或人工处理
}
} catch (Exception e) {
log.error("处理订单{}分账时发生错误", order.getOrderNo(), e);
// 记录分账失败,等待定时任务重试或人工处理
}
}
/**
* 获取平台收款账号
*/
private String getPlatformAccount(String payMethod) {
switch (payMethod) {
case "WECHAT":
return wxConfig.getMchId();
case "ALIPAY":
return aliPayConfig.getAppId();
case "UNIONPAY":
return unionPayConfig.getMerId();
default:
throw new IllegalArgumentException("Unsupported payment method: " + payMethod);
}
}
/**
* 更新订单状态并处理分账
*/
private void updateOrderStatus(Order order, String transactionId, String payMethod) {
order.setStatus(Order.STATUS_UNCONFIRMED); // 更新为待确认状态
order.setTransactionId(transactionId); // 保存交易号
order.setPaymentTime(new Date()); // 设置支付时间
orderService.updateOrder(order);
// 处理分账
handleProfitSharing(order, transactionId, payMethod);
// 发送通知给商家
notifyMerchant(order);
}
3.退款流程
这块无论是小程序,还是app还是h5或者是别的,都一样,他们只有支付的时候接口不一样
(SysFeeConfigServiceImpl)
/**
* 处理微信退款
*/
private String processWeChatRefund(Order order, BigDecimal refundAmount, String refundNo) throws Exception {
// 构建退款请求参数
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("sp_appid", wxConfig.getAppId());
requestBody.put("sp_mchid", wxConfig.getMchId());
requestBody.put("sub_mchid", wxConfig.getSubMchId());
requestBody.put("transaction_id", order.getTransactionId());
requestBody.put("out_refund_no", refundNo);
// 退款金额信息
Map<String, Object> amount = new HashMap<>();
amount.put("refund", refundAmount.multiply(new BigDecimal("100")).intValue());
amount.put("total", order.getAmount().multiply(new BigDecimal("100")).intValue());
amount.put("currency", "CNY");
requestBody.put("amount", amount);
// 退款原因
requestBody.put("reason", "商家退款");
requestBody.put("notify_url", wxConfig.getRefundNotifyUrl());
// 发送退款请求
String apiUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
HttpPost httpPost = new HttpPost(apiUrl);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-Type", "application/json");
String token = generateToken(wxConfig.getMchId(),
wxConfig.getMchSerialNo(),
wxConfig.getPrivateKeyPath());
httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048 " + token);
httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(requestBody)));
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
String responseBody = EntityUtils.toString(response.getEntity());
Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);
// 处理退款结果
String status = (String) result.get("status");
if ("SUCCESS".equals(status) || "PROCESSING".equals(status)) {
return refundNo;
} else {
throw new RuntimeException("退款失败: " + result.get("message"));
}
}
}
4.pom文件
<!-- 工具库 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- 微信支付SDK -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version>
</dependency>
<!-- 加密和HTTP -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- Spring Boot Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Google开发的Java工具库
提供集合、缓存、字符串处理、并发等实用工具
可以简化代码编写,提高开发效率
FastJson,阿里巴巴开发的JSON处理库
用于Java对象与JSON字符串的相互转换
性能较好,使用广泛
bouncycastle- 开源的密码学库
bcprov: 提供基础的密码学算法
bcpkix: 提供证书处理相关功能
用于处理PEM格式的密钥、证书等
apache.httpcomponents- Apache提供的HTTP客户端库
用于发送HTTP请求
支持HTTPS、代理、连接池等功能
5、相关配置文件config
微信配置
package com.ruoyi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
/**
* 微信支付配置
*
* @author adi
* #Date: 2025/2/28 18:24
*/
@Data
@Primary
@Component("wxAccountConfig")
@ConfigurationProperties(prefix = "wxpay")
public class WxAccountConfig {
/** 服务商应用ID */
private String appId;
/** 服务商户号 */
private String mchId;
/** 子商户应用ID */
private String subAppId;
/** 子商户号 */
private String subMchId;
/** 商户API证书序列号 */
private String mchSerialNo;
/** 商户私钥文件路径 */
private String privateKeyPath;
/** APIv3密钥 */
private String apiV3Key;
/** 异步通知地址 */
private String notifyUrl;
/** 同步跳转地址 */
private String returnUrl;
/** 退款通知地址 */
private String refundNotifyUrl;
}
6.启动项目加注解
package com.ruoyi;
import com.ruoyi.config.AliPayAccountConfig;
import com.ruoyi.config.WxAccountConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* 启动程序
*
* @author ruoyi
*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableConfigurationProperties({//属性的自动注入
AliPayAccountConfig.class,
WxAccountConfig.class
})
public class RuoYiApplication
{
public static void main(String[] args)
{
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +
" |(_ o _) / _( )_ .' \n" +
" | (_,_).' __ ___(_ o _)' \n" +
" | |\\ \\ | || |(_,_)' \n" +
" | | \\ `' /| `-' / \n" +
" | | \\ / \\ / \n" +
" ''-' `'-' `-..-' ");
}
}
三、支付宝支付
四、集成多种支付统一管理
创建一个PaymentService服务层,实现类里通过processPayment的switch来管理多种支付情况
通过processRefund来管理退款平台
@Override
public String processPayment(Order order, String paymentMethod) {
try {
// 设置订单描述
String description = String.format("%s订单-%s", order.getType(), order.getOrderNo());
order.setDescription(description);
// 设置支付金额(押金 + 费用)
if (order.getAmount() == null) {
BigDecimal totalAmount = order.getDeposit().add(order.getPrice());
order.setAmount(totalAmount);
}
switch (paymentMethod) {
case "WECHAT":
return processWeChatPayment(order);//调用微信支付
case "ALIPAY":
return processAlipayPayment(order);//支付部
case "UNIONPAY":
return processUnionPayment(order);//云闪付
case "BALANCE":
return processBalancePayment(order);//余额支付
default:
throw new IllegalArgumentException("Unsupported payment method: " + paymentMethod);
}
} catch (Exception e) {
log.error("支付处理失败", e);
throw new RuntimeException("支付处理失败: " + e.getMessage());
}
}
@Override
public String processRefund(Order order, BigDecimal refundAmount) throws Exception {
// 验证退款金额
if (refundAmount.compareTo(order.getAmount()) > 0) {
throw new IllegalArgumentException("退款金额不能大于支付金额");
}
// 生成退款单号
String refundNo = generateRefundNo(order.getOrderNo());
// 根据支付方式处理退款
switch (order.getPaymentType()) {
case "WECHAT":
return processWeChatRefund(order, refundAmount, refundNo);
case "ALIPAY":
return processAlipayRefund(order, refundAmount, refundNo);
case "UNIONPAY":
return processUnionPayRefund(order, refundAmount, refundNo);
default:
throw new IllegalArgumentException("Unsupported payment method: " + order.getPaymentType());
}
}
创建一个配置类。集中管理支付配置
package com.ruoyi.config;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.Resource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.commons.io.IOUtils;
@Slf4j
@Configuration
@RequiredArgsConstructor
public class BestPayConfig {
@Qualifier("aliPayAccountConfig")
private final AliPayAccountConfig aliPayConfig;
@Qualifier("wxAccountConfig")
private final WxAccountConfig wxConfig;
/**
* 配置ObjectMapper
*/
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 配置序列化特性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
/**
* 配置支付宝客户端
*/
@Bean
public AlipayClient alipayClient() {
return new DefaultAlipayClient(
aliPayConfig.getGatewayUrl(),
aliPayConfig.getAppId(),
aliPayConfig.getMerchantPrivateKey(),
aliPayConfig.getFormat(),
aliPayConfig.getCharset(),
aliPayConfig.getAlipayPublicKey(),
aliPayConfig.getSignType()
);
}
/**
* 配置微信支付客户端
*/
@Bean
public CloseableHttpClient wxPayClient() {
try {
// 加载商户私钥
String privateKeyPath = wxConfig.getPrivateKeyPath();
String privateKeyContent;
// 使用 Resource 加载文件
Resource resource = new ClassPathResource(privateKeyPath.replace("classpath:", ""));
if (!resource.exists()) {
log.error("微信支付证书文件不存在: {}", privateKeyPath);
// 如果证书不存在,返回一个默认的 HttpClient
// 这样可以让应用启动,但微信支付功能不可用
return HttpClients.createDefault();
}
try (InputStream inputStream = resource.getInputStream()) {
byte[] bytes = IOUtils.toByteArray(inputStream);
privateKeyContent = new String(bytes, StandardCharsets.UTF_8);
log.info("成功加载微信支付证书");
}
// 使用 BouncyCastle 加载私钥
PrivateKey merchantPrivateKey = loadPrivateKeyFromPem(privateKeyContent);
// 创建支付客户端
return WechatPayHttpClientBuilder.create()
.withMerchant(wxConfig.getMchId(), wxConfig.getMchSerialNo(), merchantPrivateKey)
.withValidator(response -> true) // 暂时不验证应答签名
.build();
} catch (Exception e) {
log.error("初始化微信支付客户端失败", e);
// 返回一个默认的 HttpClient,让应用可以启动
return HttpClients.createDefault();
}
}
/**
* 从PEM格式的字符串中加载私钥
*/
private PrivateKey loadPrivateKeyFromPem(String pemContent) throws Exception {
try (PEMParser pemParser = new PEMParser(new StringReader(pemContent))) {
PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemParser.readObject();
return new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo);
}
}
/**
* 配置通用HTTP客户端
*/
@Bean
public CloseableHttpClient httpClient() {
return HttpClients.createDefault();
}
}