feat(payment): 初始化支付模块核心代码
- 添加支付常量类PaymentConstants,定义支付状态、微信、支付宝、银联等相关常量 - 创建微信支付类型常量类WechatPayType,支持JSAPI、NATIVE、H5、APP支付方式 - 新增支付控制器PaymentController,提供创建支付、查询状态、退款等统一接口 - 实现支付回调控制器PaymentNotifyController,处理微信、支付宝、银联异步通知 - 添加支付请求数据传输对象PaymentRequest,支持多种支付方式参数校验 - 定义支付响应、状态更新请求等相关DTO类- 集成Swagger注解,完善接口文档说明- 添加参数校验和异常处理机制,确保支付流程安全可靠
This commit is contained in:
182
java/payment/service/PaymentService.java
Normal file
182
java/payment/service/PaymentService.java
Normal file
@@ -0,0 +1,182 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.dto.PaymentWithOrderRequest;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一支付服务接口
|
||||
* 提供所有支付方式的统一入口
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
public interface PaymentService {
|
||||
|
||||
/**
|
||||
* 创建支付订单
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 支付创建失败时抛出
|
||||
*/
|
||||
PaymentResponse createPayment(PaymentRequest request) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 创建支付订单(包含订单信息)
|
||||
* 统一支付模块:先创建订单,再发起支付
|
||||
*
|
||||
* @param request 支付与订单创建请求
|
||||
* @param loginUser 当前登录用户
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 创建失败时抛出
|
||||
*/
|
||||
PaymentResponse createPaymentWithOrder(PaymentWithOrderRequest request, User loginUser) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 查询支付状态
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param paymentType 支付类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 支付响应
|
||||
* @throws PaymentException 查询失败时抛出
|
||||
*/
|
||||
PaymentResponse queryPayment(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 处理支付回调通知
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @param headers 请求头
|
||||
* @param body 请求体
|
||||
* @param tenantId 租户ID
|
||||
* @return 处理结果,返回给第三方的响应内容
|
||||
* @throws PaymentException 处理失败时抛出
|
||||
*/
|
||||
String handlePaymentNotify(PaymentType paymentType, Map<String, String> headers, String body, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 申请退款
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param refundNo 退款单号
|
||||
* @param paymentType 支付类型
|
||||
* @param totalAmount 订单总金额
|
||||
* @param refundAmount 退款金额
|
||||
* @param reason 退款原因
|
||||
* @param tenantId 租户ID
|
||||
* @return 退款响应
|
||||
* @throws PaymentException 退款申请失败时抛出
|
||||
*/
|
||||
PaymentResponse refund(String orderNo, String refundNo, PaymentType paymentType,
|
||||
BigDecimal totalAmount, BigDecimal refundAmount,
|
||||
String reason, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 查询退款状态
|
||||
*
|
||||
* @param refundNo 退款单号
|
||||
* @param paymentType 支付类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 退款查询响应
|
||||
* @throws PaymentException 查询失败时抛出
|
||||
*/
|
||||
PaymentResponse queryRefund(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
*
|
||||
* @param orderNo 订单号
|
||||
* @param paymentType 支付类型
|
||||
* @param tenantId 租户ID
|
||||
* @return 关闭结果
|
||||
* @throws PaymentException 关闭失败时抛出
|
||||
*/
|
||||
boolean closeOrder(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 获取支持的支付类型列表
|
||||
*
|
||||
* @return 支付类型列表
|
||||
*/
|
||||
List<PaymentType> getSupportedPaymentTypes();
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持
|
||||
*/
|
||||
boolean isPaymentTypeSupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持退款
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持退款
|
||||
*/
|
||||
boolean isRefundSupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持查询
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持查询
|
||||
*/
|
||||
boolean isQuerySupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否支持关闭订单
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示支持关闭订单
|
||||
*/
|
||||
boolean isCloseSupported(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 检查支付类型是否需要异步通知
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return true表示需要异步通知
|
||||
*/
|
||||
boolean isNotifyNeeded(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 验证支付请求参数
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @throws PaymentException 参数验证失败时抛出
|
||||
*/
|
||||
void validatePaymentRequest(PaymentRequest request) throws PaymentException;
|
||||
|
||||
/**
|
||||
* 获取支付策略信息
|
||||
*
|
||||
* @param paymentType 支付类型
|
||||
* @return 策略信息Map,包含策略名称、描述等
|
||||
*/
|
||||
Map<String, Object> getPaymentStrategyInfo(PaymentType paymentType);
|
||||
|
||||
/**
|
||||
* 获取所有支付策略信息
|
||||
*
|
||||
* @return 所有策略信息列表
|
||||
*/
|
||||
List<Map<String, Object>> getAllPaymentStrategyInfo();
|
||||
|
||||
/**
|
||||
* 检查支付配置
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 配置检查结果
|
||||
*/
|
||||
Map<String, Object> checkPaymentConfig(Integer tenantId);
|
||||
}
|
||||
338
java/payment/service/WxPayConfigService.java
Normal file
338
java/payment/service/WxPayConfigService.java
Normal file
@@ -0,0 +1,338 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.gxwebsoft.common.core.config.CertificateProperties;
|
||||
import com.gxwebsoft.common.core.service.CertificateService;
|
||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||
import com.gxwebsoft.common.core.utils.WxNativeUtil;
|
||||
import com.gxwebsoft.common.system.entity.Payment;
|
||||
import com.gxwebsoft.common.system.param.PaymentParam;
|
||||
import com.gxwebsoft.common.system.service.PaymentService;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 微信支付配置服务
|
||||
* 负责管理微信支付的配置信息和证书
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxPayConfigService {
|
||||
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Resource
|
||||
private CertificateService certificateService;
|
||||
|
||||
@Resource
|
||||
private CertificateProperties certificateProperties;
|
||||
|
||||
@Resource
|
||||
private PaymentService paymentService;
|
||||
|
||||
@Value("${spring.profiles.active:dev}")
|
||||
private String activeProfile;
|
||||
|
||||
/**
|
||||
* 获取支付配置信息(Payment对象)- 公开方法
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 支付配置信息
|
||||
* @throws PaymentException 配置获取失败时抛出
|
||||
*/
|
||||
public Payment getPaymentConfigForStrategy(Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
return getPaymentConfig(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信支付配置
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 微信支付配置
|
||||
* @throws PaymentException 配置获取失败时抛出
|
||||
*/
|
||||
public Config getWxPayConfig(Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
// 先从缓存获取已构建的配置
|
||||
Config cachedConfig = WxNativeUtil.getConfig(tenantId);
|
||||
if (cachedConfig != null) {
|
||||
log.debug("从缓存获取微信支付配置成功,租户ID: {}", tenantId);
|
||||
return cachedConfig;
|
||||
}
|
||||
|
||||
// 构建新的配置
|
||||
Config newConfig = buildWxPayConfig(tenantId);
|
||||
|
||||
// 缓存配置
|
||||
WxNativeUtil.addConfig(tenantId, newConfig);
|
||||
log.info("微信支付配置创建并缓存成功,租户ID: {}", tenantId);
|
||||
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建微信支付配置
|
||||
*/
|
||||
private Config buildWxPayConfig(Integer tenantId) throws PaymentException {
|
||||
try {
|
||||
// 获取支付配置信息
|
||||
Payment payment = getPaymentConfig(tenantId);
|
||||
|
||||
// 获取证书文件路径
|
||||
String certificatePath = getCertificatePath(tenantId, payment);
|
||||
|
||||
// 创建微信支付配置对象
|
||||
return createWxPayConfig(payment, certificatePath);
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("构建微信支付配置失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付配置信息
|
||||
* 优先从缓存获取,缓存没有则查询数据库,最后兜底到开发环境测试配置
|
||||
*/
|
||||
private Payment getPaymentConfig(Integer tenantId) throws PaymentException {
|
||||
String cacheKey = "Payment:wxPay:" + tenantId;
|
||||
Payment payment = redisUtil.get(cacheKey, Payment.class);
|
||||
System.out.println("payment = " + payment);
|
||||
if (payment != null) {
|
||||
log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId);
|
||||
// return payment;
|
||||
}
|
||||
|
||||
// 缓存中没有,尝试从数据库查询
|
||||
try {
|
||||
final PaymentParam paymentParam = new PaymentParam();
|
||||
paymentParam.setType(102);
|
||||
paymentParam.setTenantId(tenantId);
|
||||
|
||||
log.debug("查询数据库支付配置,参数: type=102, tenantId={}", tenantId);
|
||||
payment = paymentService.getByType(paymentParam);
|
||||
log.debug("数据库查询结果: {}", payment != null ? "找到配置" : "未找到配置");
|
||||
|
||||
if (payment != null) {
|
||||
log.info("从数据库获取支付配置成功,租户ID: {},将缓存配置", tenantId);
|
||||
// 将查询到的配置缓存起来,缓存1天
|
||||
redisUtil.set(cacheKey, payment, 1L, TimeUnit.DAYS);
|
||||
return payment;
|
||||
} else {
|
||||
log.warn("数据库中未找到支付配置,租户ID: {}, type: 102", tenantId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("从数据库查询支付配置失败,租户ID: {},错误: {}", tenantId, e.getMessage(), e);
|
||||
// 抛出更详细的异常信息
|
||||
throw PaymentException.systemError("查询支付配置失败,租户ID: " + tenantId + ",错误: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 数据库也没有配置
|
||||
if (!"dev".equals(activeProfile)) {
|
||||
throw PaymentException.systemError("微信支付配置未找到,租户ID: " + tenantId + ",请检查数据库配置", null);
|
||||
}
|
||||
|
||||
log.debug("开发环境模式,将使用测试配置,租户ID: {}", tenantId);
|
||||
// 开发环境返回测试Payment配置
|
||||
return createDevTestPayment(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取证书文件路径
|
||||
*/
|
||||
private String getCertificatePath(Integer tenantId, Payment payment) throws PaymentException {
|
||||
if ("dev".equals(activeProfile)) {
|
||||
return getDevCertificatePath(tenantId);
|
||||
} else {
|
||||
return getProdCertificatePath(payment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开发环境证书路径
|
||||
*/
|
||||
private String getDevCertificatePath(Integer tenantId) throws PaymentException {
|
||||
try {
|
||||
// 根据租户ID构建证书路径
|
||||
String certPath = "dev/wechat/" + tenantId + "/apiclient_key.pem";
|
||||
ClassPathResource resource = new ClassPathResource(certPath);
|
||||
|
||||
if (!resource.exists()) {
|
||||
throw PaymentException.systemError("开发环境微信支付证书文件不存在: " + certPath, null);
|
||||
}
|
||||
|
||||
String absolutePath = resource.getFile().getAbsolutePath();
|
||||
log.debug("开发环境证书路径: {}", absolutePath);
|
||||
return absolutePath;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw PaymentException.systemError("获取开发环境证书路径失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生产环境证书路径
|
||||
*/
|
||||
private String getProdCertificatePath(Payment payment) throws PaymentException {
|
||||
if (payment == null || payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("生产环境支付配置或证书密钥文件为空", null);
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用微信支付证书路径
|
||||
String certificatePath = certificateService.getWechatPayCertPath(payment.getApiclientKey());
|
||||
if (certificatePath == null) {
|
||||
throw PaymentException.systemError("证书文件路径获取失败,证书文件: " + payment.getApiclientKey(), null);
|
||||
}
|
||||
|
||||
log.debug("生产环境证书路径: {}", certificatePath);
|
||||
return certificatePath;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("获取生产环境证书路径失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建微信支付配置对象
|
||||
*/
|
||||
private Config createWxPayConfig(Payment payment, String certificatePath) throws PaymentException {
|
||||
try {
|
||||
if ("dev".equals(activeProfile) && payment == null) {
|
||||
// 开发环境测试配置
|
||||
return createDevTestConfig(certificatePath);
|
||||
} else if (payment != null) {
|
||||
// 正常配置
|
||||
return createNormalConfig(payment, certificatePath);
|
||||
} else {
|
||||
throw PaymentException.systemError("无法创建微信支付配置:配置信息不完整", null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("创建微信支付配置对象失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建开发环境测试Payment配置
|
||||
*/
|
||||
private Payment createDevTestPayment(Integer tenantId) {
|
||||
Payment testPayment = new Payment();
|
||||
testPayment.setTenantId(tenantId);
|
||||
testPayment.setType(102); // Native支付
|
||||
testPayment.setAppId("wxa67c676fc445590e"); // 开发环境测试AppID
|
||||
testPayment.setMchId("1246610101"); // 开发环境测试商户号
|
||||
testPayment.setMerchantSerialNumber("48749613B40AA8F1D768583FC352358E13EB5AF0");
|
||||
testPayment.setApiKey(certificateProperties.getWechatPay().getDev().getApiV3Key());
|
||||
testPayment.setNotifyUrl("http://frps-10550.s209.websoft.top/api/payment/notify");
|
||||
testPayment.setName("微信Native支付-开发环境");
|
||||
testPayment.setStatus(true); // 启用
|
||||
|
||||
log.info("创建开发环境测试Payment配置,租户ID: {}, AppID: {}, 商户号: {}",
|
||||
tenantId, testPayment.getAppId(), testPayment.getMchId());
|
||||
|
||||
return testPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建开发环境测试配置
|
||||
*/
|
||||
private Config createDevTestConfig(String certificatePath) throws PaymentException {
|
||||
String testMerchantId = "1246610101";
|
||||
String testMerchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0";
|
||||
String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key();
|
||||
|
||||
if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) {
|
||||
throw PaymentException.systemError("开发环境APIv3密钥未配置", null);
|
||||
}
|
||||
|
||||
log.info("使用开发环境测试配置");
|
||||
log.debug("测试商户号: {}", testMerchantId);
|
||||
log.debug("测试序列号: {}", testMerchantSerialNumber);
|
||||
|
||||
return new RSAAutoCertificateConfig.Builder()
|
||||
.merchantId(testMerchantId)
|
||||
.privateKeyFromPath(certificatePath)
|
||||
.merchantSerialNumber(testMerchantSerialNumber)
|
||||
.apiV3Key(testApiV3Key)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建正常配置
|
||||
*/
|
||||
private Config createNormalConfig(Payment payment, String certificatePath) throws PaymentException {
|
||||
// 验证配置完整性
|
||||
validatePaymentConfig(payment);
|
||||
|
||||
log.info("使用数据库支付配置");
|
||||
log.debug("商户号: {}", payment.getMchId());
|
||||
|
||||
return new RSAAutoCertificateConfig.Builder()
|
||||
.merchantId(payment.getMchId())
|
||||
.privateKeyFromPath(certificatePath)
|
||||
.merchantSerialNumber(payment.getMerchantSerialNumber())
|
||||
.apiV3Key(payment.getApiKey())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付配置完整性
|
||||
*/
|
||||
private void validatePaymentConfig(Payment payment) throws PaymentException {
|
||||
if (payment == null) {
|
||||
throw PaymentException.systemError("支付配置为空", null);
|
||||
}
|
||||
|
||||
if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("商户号(mchId)未配置", null);
|
||||
}
|
||||
|
||||
if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("商户证书序列号(merchantSerialNumber)未配置", null);
|
||||
}
|
||||
|
||||
if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("APIv3密钥(apiKey)未配置", null);
|
||||
}
|
||||
|
||||
if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) {
|
||||
throw PaymentException.systemError("证书文件名(apiclientKey)未配置", null);
|
||||
}
|
||||
|
||||
log.debug("支付配置验证通过,租户ID: {}, 商户号: {}", payment.getTenantId(), payment.getMchId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定租户的配置缓存
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public void clearConfigCache(Integer tenantId) {
|
||||
WxNativeUtil.addConfig(tenantId, null);
|
||||
log.info("清除微信支付配置缓存,租户ID: {}", tenantId);
|
||||
}
|
||||
}
|
||||
366
java/payment/service/WxPayNotifyService.java
Normal file
366
java/payment/service/WxPayNotifyService.java
Normal file
@@ -0,0 +1,366 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.notification.NotificationConfig;
|
||||
import com.wechat.pay.java.core.notification.NotificationParser;
|
||||
import com.wechat.pay.java.core.notification.RequestParam;
|
||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信支付回调通知处理服务
|
||||
* 负责处理微信支付的异步通知回调
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxPayNotifyService {
|
||||
|
||||
@Resource
|
||||
private WxPayConfigService wxPayConfigService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
/**
|
||||
* 处理微信支付回调通知
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @param body 请求体
|
||||
* @param tenantId 租户ID
|
||||
* @return 处理结果响应
|
||||
*/
|
||||
public String handlePaymentNotify(Map<String, String> headers, String body, Integer tenantId) {
|
||||
log.info("{}, 租户ID: {}", PaymentConstants.LogMessage.NOTIFY_START, tenantId);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateNotifyParams(headers, body, tenantId);
|
||||
|
||||
// 获取微信支付配置
|
||||
Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);
|
||||
|
||||
// 解析并验证回调数据
|
||||
Transaction transaction = parseAndVerifyNotification(headers, body, wxPayConfig);
|
||||
|
||||
// 处理支付结果
|
||||
processPaymentResult(transaction, tenantId);
|
||||
|
||||
log.info("{}, 租户ID: {}, 订单号: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_SUCCESS, tenantId, transaction.getOutTradeNo());
|
||||
|
||||
return "SUCCESS";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, tenantId, e.getMessage(), e);
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调通知参数
|
||||
*/
|
||||
private void validateNotifyParams(Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
throw PaymentException.paramError("请求头不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(body)) {
|
||||
throw PaymentException.paramError("请求体不能为空");
|
||||
}
|
||||
|
||||
// 验证必要的微信支付头部信息
|
||||
String signature = headers.get("Wechatpay-Signature");
|
||||
String timestamp = headers.get("Wechatpay-Timestamp");
|
||||
String nonce = headers.get("Wechatpay-Nonce");
|
||||
String serial = headers.get("Wechatpay-Serial");
|
||||
|
||||
if (!StringUtils.hasText(signature)) {
|
||||
throw PaymentException.paramError("微信支付签名不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(timestamp)) {
|
||||
throw PaymentException.paramError("微信支付时间戳不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(nonce)) {
|
||||
throw PaymentException.paramError("微信支付随机数不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(serial)) {
|
||||
throw PaymentException.paramError("微信支付序列号不能为空");
|
||||
}
|
||||
|
||||
log.debug("回调通知参数验证通过, 租户ID: {}", tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并验证回调通知
|
||||
*/
|
||||
private Transaction parseAndVerifyNotification(Map<String, String> headers, String body, Config wxPayConfig) throws PaymentException {
|
||||
if (wxPayConfig == null) {
|
||||
throw PaymentException.systemError("微信支付配置为空", null);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
RequestParam requestParam = new RequestParam.Builder()
|
||||
.serialNumber(headers.get("Wechatpay-Serial"))
|
||||
.nonce(headers.get("Wechatpay-Nonce"))
|
||||
.signature(headers.get("Wechatpay-Signature"))
|
||||
.timestamp(headers.get("Wechatpay-Timestamp"))
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
// 创建通知解析器
|
||||
NotificationParser parser = new NotificationParser((NotificationConfig) wxPayConfig);
|
||||
|
||||
// 解析并验证通知
|
||||
Transaction transaction = parser.parse(requestParam, Transaction.class);
|
||||
|
||||
if (transaction == null) {
|
||||
throw PaymentException.systemError("解析回调通知失败:transaction为空", null);
|
||||
}
|
||||
|
||||
log.debug("回调通知解析成功, 订单号: {}, 交易状态: {}",
|
||||
transaction.getOutTradeNo(), transaction.getTradeState());
|
||||
|
||||
return transaction;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("解析回调通知失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付结果
|
||||
*/
|
||||
private void processPaymentResult(Transaction transaction, Integer tenantId) throws PaymentException {
|
||||
String outTradeNo = transaction.getOutTradeNo();
|
||||
String tradeState = String.valueOf(transaction.getTradeState());
|
||||
|
||||
if (!StringUtils.hasText(outTradeNo)) {
|
||||
throw PaymentException.paramError("商户订单号不能为空");
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo);
|
||||
if (order == null) {
|
||||
throw PaymentException.systemError("订单不存在: " + outTradeNo, null);
|
||||
}
|
||||
|
||||
// 验证租户ID
|
||||
if (!tenantId.equals(order.getTenantId())) {
|
||||
throw PaymentException.paramError("订单租户ID不匹配");
|
||||
}
|
||||
|
||||
// 验证订单状态 - 使用Boolean类型的payStatus字段
|
||||
if (Boolean.TRUE.equals(order.getPayStatus())) {
|
||||
log.info("订单已支付,跳过处理, 订单号: {}", outTradeNo);
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据交易状态处理
|
||||
switch (tradeState) {
|
||||
case "SUCCESS":
|
||||
handlePaymentSuccess(order, transaction);
|
||||
break;
|
||||
case "REFUND":
|
||||
handlePaymentRefund(order, transaction);
|
||||
break;
|
||||
case "CLOSED":
|
||||
case "REVOKED":
|
||||
case "PAYERROR":
|
||||
handlePaymentFailed(order, transaction);
|
||||
break;
|
||||
default:
|
||||
log.warn("未处理的交易状态: {}, 订单号: {}", tradeState, outTradeNo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付成功
|
||||
*/
|
||||
private void handlePaymentSuccess(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
// 验证金额
|
||||
validateAmount(order, transaction);
|
||||
|
||||
// 更新订单状态
|
||||
order.setPayStatus(true); // 使用Boolean类型
|
||||
order.setTransactionId(transaction.getTransactionId());
|
||||
order.setPayTime(LocalDateTime.parse(transaction.getSuccessTime()));
|
||||
|
||||
// 使用专门的更新方法,会触发支付成功后的业务逻辑
|
||||
shopOrderService.updateByOutTradeNo(order);
|
||||
|
||||
// 推送支付结果通知
|
||||
pushPaymentNotification(order, transaction);
|
||||
|
||||
log.info("支付成功处理完成, 订单号: {}, 微信交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付成功回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付退款
|
||||
*/
|
||||
private void handlePaymentRefund(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
log.info("处理支付退款, 订单号: {}, 微信交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
|
||||
// 这里可以添加退款相关的业务逻辑
|
||||
// 例如:更新订单状态、处理库存、发送通知等
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付退款回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付失败
|
||||
*/
|
||||
private void handlePaymentFailed(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
log.info("处理支付失败, 订单号: {}, 交易状态: {}",
|
||||
order.getOrderNo(), transaction.getTradeState());
|
||||
|
||||
// 这里可以添加支付失败相关的业务逻辑
|
||||
// 例如:释放库存、发送通知等
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付失败回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付金额
|
||||
*/
|
||||
private void validateAmount(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
if (transaction.getAmount() == null || transaction.getAmount().getTotal() == null) {
|
||||
throw PaymentException.amountError("回调通知中金额信息为空");
|
||||
}
|
||||
|
||||
// 将订单金额转换为分
|
||||
BigDecimal orderAmount = order.getMoney();
|
||||
if (orderAmount == null) {
|
||||
throw PaymentException.amountError("订单金额为空");
|
||||
}
|
||||
|
||||
int orderAmountFen = orderAmount.multiply(new BigDecimal(100)).intValue();
|
||||
int callbackAmountFen = transaction.getAmount().getTotal();
|
||||
|
||||
if (orderAmountFen != callbackAmountFen) {
|
||||
throw PaymentException.amountError(
|
||||
String.format("订单金额不匹配,订单金额: %d分, 回调金额: %d分",
|
||||
orderAmountFen, callbackAmountFen));
|
||||
}
|
||||
|
||||
log.debug("金额验证通过, 订单号: {}, 金额: {}分", order.getOrderNo(), orderAmountFen);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送支付结果通知
|
||||
*/
|
||||
private void pushPaymentNotification(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
log.info("开始推送支付成功通知, 订单号: {}, 交易号: {}, 用户ID: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId(), order.getUserId());
|
||||
|
||||
// 1. 记录支付成功日志
|
||||
logPaymentSuccess(order, transaction);
|
||||
|
||||
// 2. 发送支付成功通知(可扩展)
|
||||
sendPaymentSuccessNotification(order, transaction);
|
||||
|
||||
// 3. 触发其他业务逻辑(可扩展)
|
||||
triggerPostPaymentActions(order, transaction);
|
||||
|
||||
log.info("支付结果通知推送完成, 订单号: {}, 交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
} catch (Exception e) {
|
||||
log.warn("支付结果通知推送失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
// 推送失败不影响主流程,只记录日志
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录支付成功日志
|
||||
*/
|
||||
private void logPaymentSuccess(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
log.info("=== 支付成功详细信息 ===");
|
||||
log.info("订单号: {}", order.getOrderNo());
|
||||
log.info("微信交易号: {}", transaction.getTransactionId());
|
||||
log.info("支付金额: {}元", order.getPayPrice());
|
||||
log.info("支付时间: {}", transaction.getSuccessTime());
|
||||
log.info("用户ID: {}", order.getUserId());
|
||||
log.info("租户ID: {}", order.getTenantId());
|
||||
log.info("订单标题: {}", order.getTitle());
|
||||
log.info("========================");
|
||||
} catch (Exception e) {
|
||||
log.warn("记录支付成功日志失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送支付成功通知
|
||||
*/
|
||||
private void sendPaymentSuccessNotification(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
// TODO: 实现具体的通知逻辑
|
||||
// 1. 发送邮件通知
|
||||
// 2. 发送短信通知
|
||||
// 3. 站内消息通知
|
||||
// 4. 微信模板消息通知
|
||||
|
||||
log.debug("支付成功通知发送完成, 订单号: {}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
log.warn("发送支付成功通知失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发支付成功后的其他业务逻辑
|
||||
*/
|
||||
private void triggerPostPaymentActions(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
// TODO: 根据业务需求实现
|
||||
// 1. 开通网站服务
|
||||
// 2. 激活会员权益
|
||||
// 3. 发放积分奖励
|
||||
// 4. 触发营销活动
|
||||
|
||||
log.debug("支付后业务逻辑触发完成, 订单号: {}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
log.warn("触发支付后业务逻辑失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
668
java/payment/service/impl/PaymentServiceImpl.java
Normal file
668
java/payment/service/impl/PaymentServiceImpl.java
Normal file
@@ -0,0 +1,668 @@
|
||||
package com.gxwebsoft.payment.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||
import com.gxwebsoft.common.system.entity.User;
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||
import com.gxwebsoft.payment.dto.PaymentWithOrderRequest;
|
||||
import com.gxwebsoft.payment.enums.PaymentType;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.payment.service.PaymentService;
|
||||
import com.gxwebsoft.payment.service.WxPayConfigService;
|
||||
import com.gxwebsoft.payment.strategy.PaymentStrategy;
|
||||
import com.gxwebsoft.shop.dto.OrderCreateRequest;
|
||||
import com.gxwebsoft.shop.service.OrderBusinessService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 统一支付服务实现
|
||||
* 基于策略模式实现多种支付方式的统一管理
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("unifiedPaymentServiceImpl")
|
||||
public class PaymentServiceImpl implements PaymentService {
|
||||
|
||||
/**
|
||||
* 支付策略映射表
|
||||
*/
|
||||
private final Map<PaymentType, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 注入所有支付策略实现
|
||||
*/
|
||||
@Resource
|
||||
private List<PaymentStrategy> paymentStrategies;
|
||||
|
||||
/**
|
||||
* 订单业务服务
|
||||
*/
|
||||
@Resource
|
||||
private OrderBusinessService orderBusinessService;
|
||||
|
||||
/**
|
||||
* 微信支付配置服务
|
||||
*/
|
||||
@Resource
|
||||
private WxPayConfigService wxPayConfigService;
|
||||
|
||||
/**
|
||||
* 初始化策略映射
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initStrategies() {
|
||||
if (paymentStrategies != null && !paymentStrategies.isEmpty()) {
|
||||
for (PaymentStrategy strategy : paymentStrategies) {
|
||||
try {
|
||||
PaymentType paymentType = strategy.getSupportedPaymentType();
|
||||
strategyMap.put(paymentType, strategy);
|
||||
log.info("注册支付策略: {} -> {}", paymentType.getName(), strategy.getClass().getSimpleName());
|
||||
} catch (Exception e) {
|
||||
log.warn("注册支付策略失败: {}, 错误: {}", strategy.getClass().getSimpleName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("支付策略初始化完成,共注册 {} 种支付方式", strategyMap.size());
|
||||
|
||||
if (strategyMap.isEmpty()) {
|
||||
log.warn("⚠️ 没有可用的支付策略,支付功能将不可用");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse createPayment(PaymentRequest request) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 用户ID: {}, 金额: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_START, request.getPaymentType(),
|
||||
request.getTenantId(), request.getUserId(), request.getFormattedAmount());
|
||||
|
||||
try {
|
||||
// 基础参数验证
|
||||
validatePaymentRequest(request);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(request.getPaymentType());
|
||||
|
||||
// 执行支付
|
||||
PaymentResponse response = strategy.createPayment(request);
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_SUCCESS, request.getPaymentType(),
|
||||
request.getTenantId(), response.getOrderNo(), request.getFormattedAmount());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_FAILED, request.getPaymentType(),
|
||||
request.getTenantId(), e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.PAYMENT_FAILED, request.getPaymentType(),
|
||||
request.getTenantId(), e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付创建失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse createPaymentWithOrder(PaymentWithOrderRequest request, User loginUser) throws PaymentException {
|
||||
log.info("开始创建支付订单(包含订单信息), 支付类型: {}, 租户ID: {}, 用户ID: {}, 金额: {}",
|
||||
request.getPaymentType(), request.getTenantId(), loginUser.getUserId(), request.getFormattedAmount());
|
||||
|
||||
try {
|
||||
// 1. 参数验证
|
||||
validatePaymentWithOrderRequest(request, loginUser);
|
||||
|
||||
// 2. 转换为订单创建请求
|
||||
OrderCreateRequest orderRequest = convertToOrderCreateRequest(request, loginUser);
|
||||
|
||||
// 3. 创建订单(包含商品验证、库存扣减等完整业务逻辑)
|
||||
Map<String, String> wxOrderInfo = orderBusinessService.createOrder(orderRequest, loginUser);
|
||||
|
||||
// 4. 构建支付响应(复用现有的微信支付返回格式)
|
||||
PaymentResponse response = buildPaymentResponseFromWxOrder(wxOrderInfo, request, orderRequest.getOrderNo());
|
||||
|
||||
log.info("支付订单创建成功(包含订单信息), 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}",
|
||||
request.getPaymentType(), request.getTenantId(), response.getOrderNo(), request.getFormattedAmount());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("创建支付订单失败(包含订单信息), 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
request.getPaymentType(), request.getTenantId(), e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("创建支付订单系统错误(包含订单信息), 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
request.getPaymentType(), request.getTenantId(), e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付订单创建失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse queryPayment(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
log.info("开始查询支付状态, 支付类型: {}, 租户ID: {}, 订单号: {}",
|
||||
paymentType, tenantId, orderNo);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateQueryParams(orderNo, paymentType, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持查询
|
||||
if (!strategy.supportQuery()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持查询", paymentType);
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
PaymentResponse response = strategy.queryPayment(orderNo, tenantId);
|
||||
|
||||
log.info("支付状态查询成功, 支付类型: {}, 租户ID: {}, 订单号: {}, 状态: {}",
|
||||
paymentType, tenantId, orderNo, response.getPaymentStatus());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("支付状态查询失败, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("支付状态查询系统错误, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付查询失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handlePaymentNotify(PaymentType paymentType, Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_START, paymentType, tenantId);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateNotifyParams(paymentType, headers, body, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否需要异步通知
|
||||
if (!strategy.needNotify()) {
|
||||
log.warn("该支付方式不需要异步通知, 支付类型: {}", paymentType);
|
||||
return PaymentConstants.Wechat.NOTIFY_SUCCESS;
|
||||
}
|
||||
|
||||
// 处理回调
|
||||
String result = strategy.handleNotify(headers, body, tenantId);
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_SUCCESS, paymentType, tenantId);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, paymentType, tenantId, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, paymentType, tenantId, e.getMessage(), e);
|
||||
throw PaymentException.systemError("支付回调处理失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse refund(String orderNo, String refundNo, PaymentType paymentType,
|
||||
BigDecimal totalAmount, BigDecimal refundAmount,
|
||||
String reason, Integer tenantId) throws PaymentException {
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 退款金额: {}",
|
||||
PaymentConstants.LogMessage.REFUND_START, paymentType, tenantId, orderNo, refundNo, refundAmount);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateRefundParams(orderNo, refundNo, paymentType, totalAmount, refundAmount, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持退款
|
||||
if (!strategy.supportRefund()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持退款", paymentType);
|
||||
}
|
||||
|
||||
// 执行退款
|
||||
PaymentResponse response = strategy.refund(orderNo, refundNo, totalAmount, refundAmount, reason, tenantId);
|
||||
|
||||
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 退款金额: {}",
|
||||
PaymentConstants.LogMessage.REFUND_SUCCESS, paymentType, tenantId, orderNo, refundNo, refundAmount);
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.REFUND_FAILED, paymentType, tenantId, orderNo, refundNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 退款单号: {}, 系统错误: {}",
|
||||
PaymentConstants.LogMessage.REFUND_FAILED, paymentType, tenantId, orderNo, refundNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("退款申请失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentResponse queryRefund(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
log.info("开始查询退款状态, 支付类型: {}, 租户ID: {}, 退款单号: {}",
|
||||
paymentType, tenantId, refundNo);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateRefundQueryParams(refundNo, paymentType, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持退款查询
|
||||
if (!strategy.supportRefund()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持退款查询", paymentType);
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
PaymentResponse response = strategy.queryRefund(refundNo, tenantId);
|
||||
|
||||
log.info("退款状态查询成功, 支付类型: {}, 租户ID: {}, 退款单号: {}, 状态: {}",
|
||||
paymentType, tenantId, refundNo, response.getPaymentStatus());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("退款状态查询失败, 支付类型: {}, 租户ID: {}, 退款单号: {}, 错误: {}",
|
||||
paymentType, tenantId, refundNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("退款状态查询系统错误, 支付类型: {}, 租户ID: {}, 退款单号: {}, 错误: {}",
|
||||
paymentType, tenantId, refundNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("退款查询失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean closeOrder(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
log.info("开始关闭订单, 支付类型: {}, 租户ID: {}, 订单号: {}",
|
||||
paymentType, tenantId, orderNo);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateCloseParams(orderNo, paymentType, tenantId);
|
||||
|
||||
// 获取支付策略
|
||||
PaymentStrategy strategy = getPaymentStrategy(paymentType);
|
||||
|
||||
// 检查是否支持关闭订单
|
||||
if (!strategy.supportClose()) {
|
||||
throw PaymentException.unsupportedPayment("该支付方式不支持关闭订单", paymentType);
|
||||
}
|
||||
|
||||
// 执行关闭
|
||||
boolean result = strategy.closeOrder(orderNo, tenantId);
|
||||
|
||||
log.info("订单关闭{}, 支付类型: {}, 租户ID: {}, 订单号: {}",
|
||||
result ? "成功" : "失败", paymentType, tenantId, orderNo);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (PaymentException e) {
|
||||
log.error("订单关闭失败, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("订单关闭系统错误, 支付类型: {}, 租户ID: {}, 订单号: {}, 错误: {}",
|
||||
paymentType, tenantId, orderNo, e.getMessage(), e);
|
||||
throw PaymentException.systemError("订单关闭失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PaymentType> getSupportedPaymentTypes() {
|
||||
return new ArrayList<>(strategyMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaymentTypeSupported(PaymentType paymentType) {
|
||||
return strategyMap.containsKey(paymentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefundSupported(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.supportRefund();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isQuerySupported(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.supportQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloseSupported(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.supportClose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotifyNeeded(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
return strategy != null && strategy.needNotify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validatePaymentRequest(PaymentRequest request) throws PaymentException {
|
||||
if (request == null) {
|
||||
throw PaymentException.paramError("支付请求不能为空");
|
||||
}
|
||||
|
||||
if (request.getPaymentType() == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
|
||||
if (request.getTenantId() == null || request.getTenantId() <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
|
||||
if (request.getUserId() == null || request.getUserId() <= 0) {
|
||||
throw PaymentException.paramError("用户ID不能为空且必须大于0");
|
||||
}
|
||||
|
||||
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("支付金额必须大于0");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(request.getSubject())) {
|
||||
throw PaymentException.paramError("订单标题不能为空");
|
||||
}
|
||||
|
||||
// 检查支付类型是否支持
|
||||
if (!isPaymentTypeSupported(request.getPaymentType())) {
|
||||
throw PaymentException.unsupportedPayment("不支持的支付类型: " + request.getPaymentType(), request.getPaymentType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getPaymentStrategyInfo(PaymentType paymentType) {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
if (strategy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
info.put("paymentType", paymentType);
|
||||
info.put("strategyName", strategy.getStrategyName());
|
||||
info.put("strategyDescription", strategy.getStrategyDescription());
|
||||
info.put("supportRefund", strategy.supportRefund());
|
||||
info.put("supportQuery", strategy.supportQuery());
|
||||
info.put("supportClose", strategy.supportClose());
|
||||
info.put("needNotify", strategy.needNotify());
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getAllPaymentStrategyInfo() {
|
||||
return strategyMap.keySet().stream()
|
||||
.map(this::getPaymentStrategyInfo)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> checkPaymentConfig(Integer tenantId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("tenantId", tenantId);
|
||||
|
||||
try {
|
||||
// 检查微信支付配置
|
||||
wxPayConfigService.getPaymentConfigForStrategy(tenantId);
|
||||
result.put("wechatConfigExists", true);
|
||||
result.put("wechatConfigError", null);
|
||||
} catch (Exception e) {
|
||||
result.put("wechatConfigExists", false);
|
||||
result.put("wechatConfigError", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查微信支付Config构建
|
||||
wxPayConfigService.getWxPayConfig(tenantId);
|
||||
result.put("wechatConfigValid", true);
|
||||
result.put("wechatConfigValidError", null);
|
||||
} catch (Exception e) {
|
||||
result.put("wechatConfigValid", false);
|
||||
result.put("wechatConfigValidError", e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取支付策略
|
||||
*/
|
||||
private PaymentStrategy getPaymentStrategy(PaymentType paymentType) throws PaymentException {
|
||||
PaymentStrategy strategy = strategyMap.get(paymentType);
|
||||
if (strategy == null) {
|
||||
throw PaymentException.unsupportedPayment("不支持的支付类型: " + paymentType, paymentType);
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证查询参数
|
||||
*/
|
||||
private void validateQueryParams(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调参数
|
||||
*/
|
||||
private void validateNotifyParams(PaymentType paymentType, Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
throw PaymentException.paramError("请求头不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(body)) {
|
||||
throw PaymentException.paramError("请求体不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证退款参数
|
||||
*/
|
||||
private void validateRefundParams(String orderNo, String refundNo, PaymentType paymentType,
|
||||
BigDecimal totalAmount, BigDecimal refundAmount, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(refundNo)) {
|
||||
throw PaymentException.paramError("退款单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("订单总金额必须大于0");
|
||||
}
|
||||
if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("退款金额必须大于0");
|
||||
}
|
||||
if (refundAmount.compareTo(totalAmount) > 0) {
|
||||
throw PaymentException.amountError("退款金额不能大于订单总金额");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证退款查询参数
|
||||
*/
|
||||
private void validateRefundQueryParams(String refundNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(refundNo)) {
|
||||
throw PaymentException.paramError("退款单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证关闭订单参数
|
||||
*/
|
||||
private void validateCloseParams(String orderNo, PaymentType paymentType, Integer tenantId) throws PaymentException {
|
||||
if (!StringUtils.hasText(orderNo)) {
|
||||
throw PaymentException.paramError("订单号不能为空");
|
||||
}
|
||||
if (paymentType == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付与订单创建请求参数
|
||||
*/
|
||||
private void validatePaymentWithOrderRequest(PaymentWithOrderRequest request, User loginUser) throws PaymentException {
|
||||
if (request == null) {
|
||||
throw PaymentException.paramError("请求参数不能为空");
|
||||
}
|
||||
if (loginUser == null) {
|
||||
throw PaymentException.paramError("用户未登录");
|
||||
}
|
||||
if (request.getPaymentType() == null) {
|
||||
throw PaymentException.paramError("支付类型不能为空");
|
||||
}
|
||||
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw PaymentException.amountError("支付金额必须大于0");
|
||||
}
|
||||
if (!StringUtils.hasText(request.getSubject())) {
|
||||
throw PaymentException.paramError("订单标题不能为空");
|
||||
}
|
||||
if (request.getTenantId() == null || request.getTenantId() <= 0) {
|
||||
throw PaymentException.paramError("租户ID不能为空且必须大于0");
|
||||
}
|
||||
if (request.getOrderInfo() == null) {
|
||||
throw PaymentException.paramError("订单信息不能为空");
|
||||
}
|
||||
if (request.getOrderInfo().getGoodsItems() == null || request.getOrderInfo().getGoodsItems().isEmpty()) {
|
||||
throw PaymentException.paramError("订单商品列表不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为订单创建请求
|
||||
*/
|
||||
private OrderCreateRequest convertToOrderCreateRequest(PaymentWithOrderRequest request, User loginUser) {
|
||||
OrderCreateRequest orderRequest = new OrderCreateRequest();
|
||||
|
||||
// 生成订单号(使用雪花算法保证全局唯一)
|
||||
String orderNo = Long.toString(IdUtil.getSnowflakeNextId());
|
||||
orderRequest.setOrderNo(orderNo);
|
||||
log.info("为订单创建请求生成订单号(雪花算法): {}", orderNo);
|
||||
|
||||
// 设置基本信息
|
||||
orderRequest.setType(request.getOrderInfo().getType());
|
||||
orderRequest.setTitle(request.getSubject());
|
||||
orderRequest.setComments(request.getOrderInfo().getComments());
|
||||
orderRequest.setTenantId(request.getTenantId());
|
||||
|
||||
// 设置收货信息
|
||||
orderRequest.setRealName(request.getOrderInfo().getRealName());
|
||||
orderRequest.setAddress(request.getOrderInfo().getAddress());
|
||||
orderRequest.setAddressId(request.getOrderInfo().getAddressId());
|
||||
orderRequest.setDeliveryType(request.getOrderInfo().getDeliveryType());
|
||||
|
||||
// 设置商户信息
|
||||
orderRequest.setMerchantId(request.getOrderInfo().getMerchantId());
|
||||
orderRequest.setMerchantName(request.getOrderInfo().getMerchantName());
|
||||
|
||||
// 设置支付信息
|
||||
orderRequest.setPayType(request.getPaymentType().getCode());
|
||||
orderRequest.setTotalPrice(request.getAmount());
|
||||
orderRequest.setPayPrice(request.getAmount());
|
||||
|
||||
// 设置优惠券
|
||||
orderRequest.setCouponId(request.getOrderInfo().getCouponId());
|
||||
|
||||
// 转换商品列表
|
||||
List<OrderCreateRequest.OrderGoodsItem> goodsItems = request.getOrderInfo().getGoodsItems().stream()
|
||||
.map(this::convertToOrderGoodsItem)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
orderRequest.setGoodsItems(goodsItems);
|
||||
|
||||
return orderRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换商品项
|
||||
*/
|
||||
private OrderCreateRequest.OrderGoodsItem convertToOrderGoodsItem(PaymentWithOrderRequest.OrderGoodsItem item) {
|
||||
OrderCreateRequest.OrderGoodsItem orderItem = new OrderCreateRequest.OrderGoodsItem();
|
||||
orderItem.setGoodsId(item.getGoodsId());
|
||||
orderItem.setSkuId(item.getSkuId());
|
||||
orderItem.setQuantity(item.getQuantity());
|
||||
orderItem.setSpecInfo(item.getSpecInfo());
|
||||
return orderItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从微信订单信息构建支付响应
|
||||
*/
|
||||
private PaymentResponse buildPaymentResponseFromWxOrder(Map<String, String> wxOrderInfo,
|
||||
PaymentWithOrderRequest request,
|
||||
String orderNo) {
|
||||
PaymentResponse response = PaymentResponse.wechatNative(
|
||||
orderNo,
|
||||
wxOrderInfo.get("codeUrl"),
|
||||
request.getAmount(),
|
||||
request.getTenantId()
|
||||
);
|
||||
|
||||
// 设置额外信息
|
||||
response.setSuccess(true);
|
||||
// 确保orderNo被正确设置
|
||||
response.setOrderNo(orderNo);
|
||||
|
||||
// 调试日志
|
||||
log.info("构建支付响应成功, 订单号: {}, 二维码URL: {}, 响应中的orderNo: {}",
|
||||
orderNo, wxOrderInfo.get("codeUrl"), response.getOrderNo());
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user