From 270e543f2370a20e8fa0a556bb0df60cf7473e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 29 Aug 2025 19:28:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(payment):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E6=9C=8D=E5=8A=A1=E5=AE=9E=E7=8E=B0=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 PaymentServiceImpl 类添加统一支付服务标识 - 在 PaymentController 中明确引用统一支付服务 --- .../shop/config/WxPayProperties.java | 108 ------ .../controller/WxNativePayController.java | 266 --------------- .../com/gxwebsoft/shop/dto/WxPayRequest.java | 117 ------- .../shop/exception/WxPayException.java | 196 ----------- .../shop/exception/WxPayExceptionHandler.java | 140 -------- .../shop/service/WxPayConfigService.java | 275 ---------------- .../shop/service/WxPayNotifyService.java | 309 ------------------ 7 files changed, 1411 deletions(-) delete mode 100644 src/main/java/com/gxwebsoft/shop/config/WxPayProperties.java delete mode 100644 src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java delete mode 100644 src/main/java/com/gxwebsoft/shop/dto/WxPayRequest.java delete mode 100644 src/main/java/com/gxwebsoft/shop/exception/WxPayException.java delete mode 100644 src/main/java/com/gxwebsoft/shop/exception/WxPayExceptionHandler.java delete mode 100644 src/main/java/com/gxwebsoft/shop/service/WxPayConfigService.java delete mode 100644 src/main/java/com/gxwebsoft/shop/service/WxPayNotifyService.java diff --git a/src/main/java/com/gxwebsoft/shop/config/WxPayProperties.java b/src/main/java/com/gxwebsoft/shop/config/WxPayProperties.java deleted file mode 100644 index 6ee83ce..0000000 --- a/src/main/java/com/gxwebsoft/shop/config/WxPayProperties.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.gxwebsoft.shop.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 微信支付配置属性 - * 管理微信支付相关的配置项 - * - * @author 科技小王子 - * @since 2025-01-26 - */ -@Data -@Component -@ConfigurationProperties(prefix = "wx.pay") -public class WxPayProperties { - - /** - * 服务器URL(用于构建回调地址) - */ - private String serverUrl = "https://server.gxwebsoft.com"; - - /** - * 回调路径模板 - */ - private String notifyUrlTemplate = "/api/system/wx-native-pay/notify/{tenantId}"; - - /** - * 订单超时时间(分钟) - */ - private int orderTimeoutMinutes = 30; - - /** - * 是否启用签名验证 - */ - private boolean enableSignatureVerification = true; - - /** - * 是否启用金额验证 - */ - private boolean enableAmountVerification = true; - - /** - * 最大重试次数 - */ - private int maxRetryCount = 3; - - /** - * 重试间隔(毫秒) - */ - private long retryIntervalMs = 1000; - - /** - * 是否启用详细日志 - */ - private boolean enableDetailedLogging = false; - - /** - * 测试环境配置 - */ - private TestConfig test = new TestConfig(); - - /** - * 测试环境配置 - */ - @Data - public static class TestConfig { - /** - * 测试商户号 - */ - private String merchantId = "1246610101"; - - /** - * 测试商户序列号 - */ - private String merchantSerialNumber = "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7"; - - /** - * 是否启用测试模式 - */ - private boolean enabled = false; - - /** - * 测试金额(分) - */ - private int testAmount = 1; - } - - /** - * 构建完整的回调URL - * - * @param tenantId 租户ID - * @return 完整的回调URL - */ - public String buildNotifyUrl(Integer tenantId) { - return serverUrl + notifyUrlTemplate.replace("{tenantId}", tenantId.toString()); - } - - /** - * 获取订单超时时间(秒) - * - * @return 超时时间(秒) - */ - public long getOrderTimeoutSeconds() { - return orderTimeoutMinutes * 60L; - } -} diff --git a/src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java b/src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java deleted file mode 100644 index 59d4126..0000000 --- a/src/main/java/com/gxwebsoft/shop/controller/WxNativePayController.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.gxwebsoft.shop.controller; - -import com.alibaba.fastjson.JSONObject; -import com.gxwebsoft.common.core.utils.CommonUtil; -import com.gxwebsoft.common.core.utils.RedisUtil; -import com.gxwebsoft.common.core.web.ApiResult; -import com.gxwebsoft.common.core.web.BaseController; -import com.gxwebsoft.common.system.entity.Payment; -import com.gxwebsoft.common.system.service.SettingService; -import com.gxwebsoft.shop.constants.WxPayConstants; -import com.gxwebsoft.shop.entity.ShopOrder; -import com.gxwebsoft.shop.exception.WxPayException; -import com.gxwebsoft.shop.service.ShopOrderService; -import com.gxwebsoft.shop.config.WxPayProperties; -import com.gxwebsoft.shop.dto.WxPayRequest; -import com.gxwebsoft.shop.service.WxPayConfigService; -import com.gxwebsoft.shop.service.WxPayNotifyService; -import com.wechat.pay.java.core.Config; -import com.wechat.pay.java.service.payments.nativepay.NativePayService; -import com.wechat.pay.java.service.payments.nativepay.model.Amount; -import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest; -import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import javax.annotation.Resource; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Positive; -import java.math.BigDecimal; -import java.util.Map; - - -@Slf4j -@Validated -@Tag(name = "微信Native支付接口") -@RestController -@RequestMapping("/api/system/wx-native-pay") -public class WxNativePayController extends BaseController { - - @Resource - private RedisUtil redisUtil; - - @Resource - private SettingService settingService; - - @Resource - private ShopOrderService shopOrderService; - - @Resource - private WxPayConfigService wxPayConfigService; - - @Resource - private WxPayNotifyService wxPayNotifyService; - - @Resource - private WxPayProperties wxPayProperties; - - - @Operation(summary = "生成付款码") - @PostMapping("/codeUrl") - public ApiResult getCodeUrl(@Valid @RequestBody WxPayRequest payRequest) { - log.info("{}, 租户ID: {}, 金额: {}", - WxPayConstants.LogMessage.PAY_REQUEST_START, payRequest.getTenantId(), payRequest.getFormattedAmount()); - - try { - // 设置当前租户ID(从请求参数中获取) - setCurrentTenantId(payRequest.getTenantId()); - - // 获取支付配置 - Payment payment = getPaymentConfig(); - - // 获取微信小程序配置 - String appId = getWxAppId(payRequest.getTenantId()); - - // 准备订单数据 - ShopOrder order = buildOrderFromRequest(payRequest); - - // 创建支付请求 - PrepayRequest request = buildPrepayRequest(order, payment, appId); - - // 调用微信支付API - PrepayResponse response = callWxPayApi(request); - - log.info("{}, 租户ID: {}, 订单号: {}, 金额: {}", - WxPayConstants.LogMessage.PAY_REQUEST_SUCCESS, - payRequest.getTenantId(), order.getOrderNo(), payRequest.getFormattedAmount()); - - return success("生成付款码", response.getCodeUrl()); - - } catch (WxPayException e) { - log.error("{}, 租户ID: {}, 错误: {}", - WxPayConstants.LogMessage.PAY_REQUEST_FAILED, payRequest.getTenantId(), e.getMessage()); - if (wxPayProperties.isEnableDetailedLogging()) { - log.error("详细错误信息", e); - } - return fail(e.getMessage()); - } catch (Exception e) { - log.error("{}, 租户ID: {}, 系统错误: {}", - WxPayConstants.LogMessage.PAY_REQUEST_FAILED, payRequest.getTenantId(), e.getMessage(), e); - return fail(WxPayConstants.ErrorMessage.SYSTEM_INTERNAL_ERROR); - } - } - - - /** - * 设置当前租户ID(用于多租户上下文) - */ - private void setCurrentTenantId(Integer tenantId) { - // 这里可以设置到ThreadLocal或其他上下文中 - // 具体实现取决于你的多租户架构 - log.debug("设置当前租户ID: {}", tenantId); - } - - /** - * 从支付请求构建订单对象 - */ - private ShopOrder buildOrderFromRequest(WxPayRequest payRequest) { - ShopOrder order = new ShopOrder(); - - // 基本信息 - order.setTenantId(payRequest.getTenantId()); - order.setPayPrice(payRequest.getPayPrice()); - order.setMoney(payRequest.getPayPrice()); - order.setTotalPrice(payRequest.getPayPrice()); - order.setComments(payRequest.getEffectiveDescription()); - - // 生成订单号 - if (StringUtils.hasText(payRequest.getOrderNo())) { - order.setOrderNo(payRequest.getOrderNo()); - } else { - order.setOrderNo(CommonUtil.createOrderNo()); - } - - // 其他字段 - order.setUserId(payRequest.getUserId()); - order.setFormId(payRequest.getGoodsId()); - order.setTotalNum(payRequest.getQuantity()); - order.setType(payRequest.getOrderType()); - order.setDeliveryType(payRequest.getDeliveryType()); - order.setChannel(payRequest.getChannel()); - order.setBuyerRemarks(payRequest.getBuyerRemarks()); - order.setAddressId(payRequest.getAddressId()); - order.setSelfTakeMerchantId(payRequest.getSelfTakeMerchantId()); - - // 设置默认值 - order.setPayStatus(false); - order.setOrderStatus(0); - order.setPayType(102); // 微信Native支付 - - log.debug("从支付请求构建订单完成, 订单号: {}, 金额: {}", - order.getOrderNo(), order.getPayPrice()); - - return order; - } - - /** - * 获取支付配置 - */ - private Payment getPaymentConfig() throws WxPayException { - String cacheKey = WxPayConstants.CacheKey.PAYMENT_CONFIG_PREFIX + getTenantId(); - Payment payment = redisUtil.get(cacheKey, Payment.class); - - if (payment == null) { - throw WxPayException.configError(WxPayConstants.ErrorMessage.CONFIG_NOT_FOUND, getTenantId()); - } - - log.debug("获取支付配置成功, 租户ID: {}", getTenantId()); - return payment; - } - - /** - * 获取微信小程序AppId - */ - private String getWxAppId(Integer tenantId) throws WxPayException { - JSONObject setting = settingService.getBySettingKey(WxPayConstants.CacheKey.MP_WEIXIN_CONFIG, tenantId); - if (setting == null) { - throw WxPayException.configError("微信小程序配置未找到", tenantId); - } - - String appId = setting.getString("appId"); - if (!StringUtils.hasText(appId)) { - throw WxPayException.configError("微信小程序AppId未配置", tenantId); - } - - log.debug("获取微信AppId成功, 租户ID: {}", tenantId); - return appId; - } - - - - /** - * 构建预支付请求 - */ - private PrepayRequest buildPrepayRequest(ShopOrder order, Payment payment, String appId) throws WxPayException { - PrepayRequest request = new PrepayRequest(); - - // 设置金额(转换为分) - BigDecimal amountYuan = order.getMoney(); - int amountFen = amountYuan.multiply(new BigDecimal(WxPayConstants.Config.AMOUNT_MULTIPLIER)).intValue(); - - Amount amount = new Amount(); - amount.setTotal(amountFen); - amount.setCurrency(WxPayConstants.Config.CURRENCY_CNY); - - request.setAmount(amount); - request.setAppid(appId); - request.setMchid(payment.getMchId()); - request.setDescription(order.getComments()); - request.setOutTradeNo(order.getOrderNo()); - - // 构建回调URL - String notifyUrl = wxPayProperties.buildNotifyUrl(getTenantId()); - request.setNotifyUrl(notifyUrl); - - log.debug("预支付请求构建完成, 订单号: {}, 金额: {}分, 回调URL: {}", - order.getOrderNo(), amountFen, notifyUrl); - - return request; - } - - /** - * 调用微信支付API - */ - private PrepayResponse callWxPayApi(PrepayRequest request) throws WxPayException { - try { - // 获取微信支付配置 - Config wxPayConfig = wxPayConfigService.getWxPayConfig(getTenantId()); - - // 构建服务 - NativePayService service = new NativePayService.Builder().config(wxPayConfig).build(); - - // 调用预支付接口 - PrepayResponse response = service.prepay(request); - - if (response == null || !StringUtils.hasText(response.getCodeUrl())) { - throw WxPayException.networkError("微信支付API返回数据异常", null); - } - - log.debug("微信支付API调用成功, 订单号: {}", request.getOutTradeNo()); - return response; - - } catch (Exception e) { - if (e instanceof WxPayException) { - throw e; - } - throw WxPayException.networkError("调用微信支付API失败: " + e.getMessage(), e); - } - } - - @Operation(summary = "异步通知") - @PostMapping("/notify/{tenantId}") - public String wxNotify(@RequestHeader Map headers, - @RequestBody String body, - @PathVariable("tenantId") Integer tenantId) { - - log.info("收到微信支付回调通知, 租户ID: {}", tenantId); - - // 委托给专门的回调处理服务 - return wxPayNotifyService.handlePaymentNotify(headers, body, tenantId); - } -} diff --git a/src/main/java/com/gxwebsoft/shop/dto/WxPayRequest.java b/src/main/java/com/gxwebsoft/shop/dto/WxPayRequest.java deleted file mode 100644 index a125c85..0000000 --- a/src/main/java/com/gxwebsoft/shop/dto/WxPayRequest.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.gxwebsoft.shop.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import javax.validation.constraints.*; -import java.math.BigDecimal; - -/** - * 微信支付请求DTO - * 用于接收和验证微信支付请求参数 - * - * @author 科技小王子 - * @since 2025-01-26 - */ -@Data -@Schema(name = "微信支付请求", description = "微信支付请求参数") -public class WxPayRequest { - - @Schema(description = "租户ID", required = true) - @NotNull(message = "租户ID不能为空") - @Positive(message = "租户ID必须为正数") - private Integer tenantId; - - @Schema(description = "支付金额", required = true, example = "0.01") - @NotNull(message = "支付金额不能为空") - @DecimalMin(value = "0.01", message = "支付金额必须大于0.01元") - @DecimalMax(value = "999999.99", message = "支付金额不能超过999999.99元") - @Digits(integer = 6, fraction = 2, message = "支付金额格式不正确,最多6位整数2位小数") - private BigDecimal payPrice; - - @Schema(description = "订单描述", example = "商品订单") - @Size(max = 127, message = "订单描述不能超过127个字符") - private String description; - - @Schema(description = "订单号(可选,不提供则自动生成)") - @Size(max = 32, message = "订单号不能超过32个字符") - @Pattern(regexp = "^[a-zA-Z0-9_-]*$", message = "订单号只能包含字母、数字、下划线和横线") - private String orderNo; - - @Schema(description = "用户ID") - @Positive(message = "用户ID必须为正数") - private Integer userId; - - @Schema(description = "商品ID") - @Positive(message = "商品ID必须为正数") - private Integer goodsId; - - @Schema(description = "购买数量", example = "1") - @Min(value = 1, message = "购买数量必须大于0") - @Max(value = 9999, message = "购买数量不能超过9999") - private Integer quantity = 1; - - @Schema(description = "订单类型", example = "0") - @Min(value = 0, message = "订单类型不能为负数") - @Max(value = 10, message = "订单类型值超出范围") - private Integer orderType = 0; - - @Schema(description = "配送方式", example = "1") - @Min(value = 0, message = "配送方式不能为负数") - private Integer deliveryType; - - @Schema(description = "下单渠道", example = "0") - @Min(value = 0, message = "下单渠道不能为负数") - private Integer channel = 0; - - @Schema(description = "买家备注") - @Size(max = 500, message = "买家备注不能超过500个字符") - private String buyerRemarks; - - @Schema(description = "收货地址ID") - @Positive(message = "收货地址ID必须为正数") - private Integer addressId; - - @Schema(description = "自提店铺ID") - @Positive(message = "自提店铺ID必须为正数") - private Integer selfTakeMerchantId; - - /** - * 获取有效的订单描述 - * 如果没有提供描述,返回默认值 - */ - public String getEffectiveDescription() { - if (description == null || description.trim().isEmpty()) { - return "商品订单"; - } - return description.trim(); - } - - /** - * 验证必要参数是否完整 - */ - public boolean isValid() { - return tenantId != null && tenantId > 0 - && payPrice != null && payPrice.compareTo(BigDecimal.ZERO) > 0; - } - - /** - * 获取格式化的金额字符串 - */ - public String getFormattedAmount() { - if (payPrice == null) { - return "0.00"; - } - return String.format("%.2f", payPrice); - } - - /** - * 转换为分(微信支付API需要) - */ - public Integer getAmountInCents() { - if (payPrice == null) { - return 0; - } - return payPrice.multiply(new BigDecimal(100)).intValue(); - } -} diff --git a/src/main/java/com/gxwebsoft/shop/exception/WxPayException.java b/src/main/java/com/gxwebsoft/shop/exception/WxPayException.java deleted file mode 100644 index 2733537..0000000 --- a/src/main/java/com/gxwebsoft/shop/exception/WxPayException.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.gxwebsoft.shop.exception; - -/** - * 微信支付异常类 - * 用于处理微信支付相关的业务异常 - * - * @author 科技小王子 - * @since 2025-01-26 - */ -public class WxPayException extends Exception { - - private static final long serialVersionUID = 1L; - - /** - * 错误代码 - */ - private String errorCode; - - /** - * 租户ID(可选) - */ - private Integer tenantId; - - public WxPayException(String message) { - super(message); - } - - public WxPayException(String message, Throwable cause) { - super(message, cause); - } - - public WxPayException(String errorCode, String message) { - super(message); - this.errorCode = errorCode; - } - - public WxPayException(String errorCode, String message, Throwable cause) { - super(message, cause); - this.errorCode = errorCode; - } - - public WxPayException(String errorCode, String message, Integer tenantId) { - super(message); - this.errorCode = errorCode; - this.tenantId = tenantId; - } - - public WxPayException(String errorCode, String message, Integer tenantId, Throwable cause) { - super(message, cause); - this.errorCode = errorCode; - this.tenantId = tenantId; - } - - public String getErrorCode() { - return errorCode; - } - - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } - - public Integer getTenantId() { - return tenantId; - } - - public void setTenantId(Integer tenantId) { - this.tenantId = tenantId; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("WxPayException{"); - if (errorCode != null) { - sb.append("errorCode='").append(errorCode).append("', "); - } - if (tenantId != null) { - sb.append("tenantId=").append(tenantId).append(", "); - } - sb.append("message='").append(getMessage()).append("'"); - sb.append("}"); - return sb.toString(); - } - - /** - * 微信支付错误代码常量 - */ - public static class ErrorCode { - /** 配置错误 */ - public static final String CONFIG_ERROR = "CONFIG_ERROR"; - - /** 证书错误 */ - public static final String CERTIFICATE_ERROR = "CERTIFICATE_ERROR"; - - /** 参数错误 */ - public static final String PARAM_ERROR = "PARAM_ERROR"; - - /** 网络错误 */ - public static final String NETWORK_ERROR = "NETWORK_ERROR"; - - /** 签名验证失败 */ - public static final String SIGNATURE_ERROR = "SIGNATURE_ERROR"; - - /** 订单状态错误 */ - public static final String ORDER_STATUS_ERROR = "ORDER_STATUS_ERROR"; - - /** 金额错误 */ - public static final String AMOUNT_ERROR = "AMOUNT_ERROR"; - - /** 租户配置错误 */ - public static final String TENANT_CONFIG_ERROR = "TENANT_CONFIG_ERROR"; - - /** 回调处理错误 */ - public static final String CALLBACK_ERROR = "CALLBACK_ERROR"; - - /** 系统内部错误 */ - public static final String SYSTEM_ERROR = "SYSTEM_ERROR"; - } - - /** - * 创建配置错误异常 - */ - public static WxPayException configError(String message) { - return new WxPayException(ErrorCode.CONFIG_ERROR, message); - } - - /** - * 创建配置错误异常(带租户ID) - */ - public static WxPayException configError(String message, Integer tenantId) { - return new WxPayException(ErrorCode.CONFIG_ERROR, message, tenantId); - } - - /** - * 创建证书错误异常 - */ - public static WxPayException certificateError(String message) { - return new WxPayException(ErrorCode.CERTIFICATE_ERROR, message); - } - - /** - * 创建参数错误异常 - */ - public static WxPayException paramError(String message) { - return new WxPayException(ErrorCode.PARAM_ERROR, message); - } - - /** - * 创建网络错误异常 - */ - public static WxPayException networkError(String message, Throwable cause) { - return new WxPayException(ErrorCode.NETWORK_ERROR, message, cause); - } - - /** - * 创建签名验证失败异常 - */ - public static WxPayException signatureError(String message) { - return new WxPayException(ErrorCode.SIGNATURE_ERROR, message); - } - - /** - * 创建订单状态错误异常 - */ - public static WxPayException orderStatusError(String message) { - return new WxPayException(ErrorCode.ORDER_STATUS_ERROR, message); - } - - /** - * 创建金额错误异常 - */ - public static WxPayException amountError(String message) { - return new WxPayException(ErrorCode.AMOUNT_ERROR, message); - } - - /** - * 创建租户配置错误异常 - */ - public static WxPayException tenantConfigError(String message, Integer tenantId) { - return new WxPayException(ErrorCode.TENANT_CONFIG_ERROR, message, tenantId); - } - - /** - * 创建回调处理错误异常 - */ - public static WxPayException callbackError(String message, Throwable cause) { - return new WxPayException(ErrorCode.CALLBACK_ERROR, message, cause); - } - - /** - * 创建系统内部错误异常 - */ - public static WxPayException systemError(String message, Throwable cause) { - return new WxPayException(ErrorCode.SYSTEM_ERROR, message, cause); - } -} diff --git a/src/main/java/com/gxwebsoft/shop/exception/WxPayExceptionHandler.java b/src/main/java/com/gxwebsoft/shop/exception/WxPayExceptionHandler.java deleted file mode 100644 index e241fe8..0000000 --- a/src/main/java/com/gxwebsoft/shop/exception/WxPayExceptionHandler.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.gxwebsoft.shop.exception; - -import com.gxwebsoft.common.core.web.ApiResult; -import com.gxwebsoft.shop.constants.WxPayConstants; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.validation.BindException; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * 微信支付异常处理器 - * 处理微信支付相关的异常和参数验证异常 - * - * @author 科技小王子 - * @since 2025-01-26 - */ -@Slf4j -@RestControllerAdvice(basePackages = "com.gxwebsoft.shop.controller") -public class WxPayExceptionHandler { - - /** - * 处理微信支付业务异常 - */ - @ExceptionHandler(WxPayException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResult handleWxPayException(WxPayException e) { - log.warn("微信支付业务异常: {}", e.getMessage()); - - if (e.getTenantId() != null) { - log.warn("异常租户ID: {}", e.getTenantId()); - } - - if (e.getErrorCode() != null) { - log.warn("错误代码: {}", e.getErrorCode()); - } - - return ApiResult.fail(e.getMessage()); - } - - /** - * 处理参数验证异常(@Valid注解) - */ - @ExceptionHandler(MethodArgumentNotValidException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - List fieldErrors = e.getBindingResult().getFieldErrors(); - - String errorMessage = fieldErrors.stream() - .map(error -> error.getField() + ": " + error.getDefaultMessage()) - .collect(Collectors.joining("; ")); - - log.warn("参数验证失败: {}", errorMessage); - - return ApiResult.fail(WxPayConstants.ErrorMessage.PARAM_VALIDATION_FAILED + ": " + errorMessage); - } - - /** - * 处理绑定异常 - */ - @ExceptionHandler(BindException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResult handleBindException(BindException e) { - List fieldErrors = e.getBindingResult().getFieldErrors(); - - String errorMessage = fieldErrors.stream() - .map(error -> error.getField() + ": " + error.getDefaultMessage()) - .collect(Collectors.joining("; ")); - - log.warn("数据绑定失败: {}", errorMessage); - - return ApiResult.fail(WxPayConstants.ErrorMessage.PARAM_VALIDATION_FAILED + ": " + errorMessage); - } - - /** - * 处理约束违反异常(@Validated注解) - */ - @ExceptionHandler(ConstraintViolationException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResult handleConstraintViolationException(ConstraintViolationException e) { - Set> violations = e.getConstraintViolations(); - - String errorMessage = violations.stream() - .map(violation -> violation.getPropertyPath() + ": " + violation.getMessage()) - .collect(Collectors.joining("; ")); - - log.warn("约束验证失败: {}", errorMessage); - - return ApiResult.fail(WxPayConstants.ErrorMessage.PARAM_VALIDATION_FAILED + ": " + errorMessage); - } - - /** - * 处理非法参数异常 - */ - @ExceptionHandler(IllegalArgumentException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResult handleIllegalArgumentException(IllegalArgumentException e) { - log.warn("非法参数异常: {}", e.getMessage()); - return ApiResult.fail(WxPayConstants.ErrorMessage.PARAM_VALIDATION_FAILED + ": " + e.getMessage()); - } - - /** - * 处理空指针异常 - */ - @ExceptionHandler(NullPointerException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ApiResult handleNullPointerException(NullPointerException e) { - log.error("空指针异常", e); - return ApiResult.fail(WxPayConstants.ErrorMessage.SYSTEM_INTERNAL_ERROR); - } - - /** - * 处理其他运行时异常 - */ - @ExceptionHandler(RuntimeException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ApiResult handleRuntimeException(RuntimeException e) { - log.error("运行时异常: {}", e.getMessage(), e); - return ApiResult.fail(WxPayConstants.ErrorMessage.SYSTEM_INTERNAL_ERROR); - } - - /** - * 处理其他异常 - */ - @ExceptionHandler(Exception.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ApiResult handleException(Exception e) { - log.error("未知异常: {}", e.getMessage(), e); - return ApiResult.fail(WxPayConstants.ErrorMessage.SYSTEM_INTERNAL_ERROR); - } -} diff --git a/src/main/java/com/gxwebsoft/shop/service/WxPayConfigService.java b/src/main/java/com/gxwebsoft/shop/service/WxPayConfigService.java deleted file mode 100644 index 865d36c..0000000 --- a/src/main/java/com/gxwebsoft/shop/service/WxPayConfigService.java +++ /dev/null @@ -1,275 +0,0 @@ -package com.gxwebsoft.shop.service; - -import com.gxwebsoft.common.core.config.CertificateProperties; -import com.gxwebsoft.common.core.config.ConfigProperties; -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.shop.exception.WxPayException; -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.File; - -/** - * 微信支付配置服务 - * 负责处理微信支付的配置获取和管理 - * - * @author 科技小王子 - * @since 2025-01-26 - */ -@Slf4j -@Service -public class WxPayConfigService { - - @Value("${spring.profiles.active}") - private String activeProfile; - - @Resource - private RedisUtil redisUtil; - - @Resource - private ConfigProperties configProperties; - - @Resource - private CertificateService certificateService; - - @Resource - private CertificateProperties certificateProperties; - - /** - * 获取微信支付配置 - * - * @param tenantId 租户ID - * @return 微信支付配置 - * @throws WxPayException 配置获取失败时抛出 - */ - public Config getWxPayConfig(Integer tenantId) throws WxPayException { - if (tenantId == null) { - throw new WxPayException("租户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 WxPayException { - try { - // 获取支付配置信息 - Payment payment = getPaymentConfig(tenantId); - - // 获取证书路径 - String certificatePath = getCertificatePath(tenantId, payment); - - // 构建配置 - return createWxPayConfig(payment, certificatePath); - - } catch (Exception e) { - log.error("构建微信支付配置失败,租户ID: {}, 错误: {}", tenantId, e.getMessage(), e); - throw new WxPayException("微信支付配置构建失败: " + e.getMessage(), e); - } - } - - /** - * 获取支付配置信息 - */ - private Payment getPaymentConfig(Integer tenantId) throws WxPayException { - String cacheKey = "Payment:wxPay:" + tenantId; - Payment payment = redisUtil.get(cacheKey, Payment.class); - - if (payment == null && !"dev".equals(activeProfile)) { - throw new WxPayException("微信支付配置未找到,租户ID: " + tenantId); - } - - if (payment != null) { - log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId); - } else { - log.debug("开发环境模式,将使用测试配置,租户ID: {}", tenantId); - } - - return payment; - } - - /** - * 获取证书文件路径 - */ - private String getCertificatePath(Integer tenantId, Payment payment) throws WxPayException { - if ("dev".equals(activeProfile)) { - return getDevCertificatePath(tenantId); - } else { - return getProdCertificatePath(payment); - } - } - - /** - * 获取开发环境证书路径 - */ - private String getDevCertificatePath(Integer tenantId) throws WxPayException { - try { - String relativePath = certificateService.getWechatPayCertPath( - certificateProperties.getWechatPay().getDev().getPrivateKeyFile() - ); - - String certificatePath; - if (certificateProperties.isClasspathMode()) { - // classpath模式 - try { - ClassPathResource resource = new ClassPathResource(relativePath); - if (resource.exists()) { - certificatePath = resource.getFile().getAbsolutePath(); - } else { - certificatePath = "classpath:" + relativePath; - } - } catch (Exception e) { - certificatePath = "classpath:" + relativePath; - } - } else { - // 文件系统模式 - certificatePath = new File(relativePath).getAbsolutePath(); - } - - // 验证证书文件是否存在 - if (!certificateService.certificateExists("wechat", - certificateProperties.getWechatPay().getDev().getPrivateKeyFile())) { - throw new WxPayException("微信支付私钥证书文件不存在"); - } - - log.debug("开发环境证书路径: {}", certificatePath); - return certificatePath; - - } catch (Exception e) { - throw new WxPayException("获取开发环境证书路径失败: " + e.getMessage(), e); - } - } - - /** - * 获取生产环境证书路径 - */ - private String getProdCertificatePath(Payment payment) throws WxPayException { - if (payment == null || payment.getApiclientKey() == null) { - throw new WxPayException("生产环境支付配置或证书路径为空"); - } - - String relativePath = payment.getApiclientKey(); - String certificatePath = configProperties.getUploadPath() + "file" + relativePath; - - // 验证证书文件是否存在 - File certFile = new File(certificatePath); - if (!certFile.exists()) { - throw new WxPayException("生产环境证书文件不存在: " + certificatePath); - } - - log.debug("生产环境证书路径: {}", certificatePath); - return certificatePath; - } - - /** - * 创建微信支付配置对象 - */ - private Config createWxPayConfig(Payment payment, String certificatePath) throws WxPayException { - try { - if ("dev".equals(activeProfile) && payment == null) { - // 开发环境测试配置 - return createDevTestConfig(certificatePath); - } else if (payment != null) { - // 正常配置 - return createNormalConfig(payment, certificatePath); - } else { - throw new WxPayException("无法创建微信支付配置:配置信息不完整"); - } - } catch (Exception e) { - throw new WxPayException("创建微信支付配置对象失败: " + e.getMessage(), e); - } - } - - /** - * 创建开发环境测试配置 - */ - private Config createDevTestConfig(String certificatePath) throws WxPayException { - String testMerchantId = "1246610101"; - String testMerchantSerialNumber = "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7"; - String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key(); - - if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) { - throw new WxPayException("开发环境APIv3密钥未配置"); - } - - 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 WxPayException { - if (payment.getMchId() == null || payment.getMerchantSerialNumber() == null || payment.getApiKey() == null) { - throw new WxPayException("支付配置信息不完整:商户号、序列号或APIv3密钥为空"); - } - - log.info("使用数据库支付配置"); - log.debug("商户号: {}", payment.getMchId()); - - return new RSAAutoCertificateConfig.Builder() - .merchantId(payment.getMchId()) - .privateKeyFromPath(certificatePath) - .merchantSerialNumber(payment.getMerchantSerialNumber()) - .apiV3Key(payment.getApiKey()) - .build(); - } - - /** - * 清除指定租户的配置缓存 - * - * @param tenantId 租户ID - */ - public void clearConfigCache(Integer tenantId) { - WxNativeUtil.addConfig(tenantId, null); - log.info("清除微信支付配置缓存,租户ID: {}", tenantId); - } - - /** - * 验证支付配置是否有效 - * - * @param tenantId 租户ID - * @return 配置是否有效 - */ - public boolean isConfigValid(Integer tenantId) { - try { - getWxPayConfig(tenantId); - return true; - } catch (Exception e) { - log.warn("微信支付配置验证失败,租户ID: {}, 错误: {}", tenantId, e.getMessage()); - return false; - } - } -} diff --git a/src/main/java/com/gxwebsoft/shop/service/WxPayNotifyService.java b/src/main/java/com/gxwebsoft/shop/service/WxPayNotifyService.java deleted file mode 100644 index 044fa8f..0000000 --- a/src/main/java/com/gxwebsoft/shop/service/WxPayNotifyService.java +++ /dev/null @@ -1,309 +0,0 @@ -package com.gxwebsoft.shop.service; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; -import com.gxwebsoft.common.core.constants.OrderConstants; -import com.gxwebsoft.common.core.utils.RequestUtil; -import com.gxwebsoft.shop.constants.WxPayConstants; -import com.gxwebsoft.shop.entity.ShopOrder; -import com.gxwebsoft.shop.exception.WxPayException; -import com.wechat.pay.java.core.Config; -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.util.Map; - -/** - * 微信支付回调通知处理服务 - * 负责处理微信支付的异步通知回调 - * - * @author 科技小王子 - * @since 2025-01-26 - */ -@Slf4j -@Service -public class WxPayNotifyService { - - @Resource - private WxPayConfigService wxPayConfigService; - - @Resource - private ShopOrderService shopOrderService; - - @Resource - private RequestUtil requestUtil; - - /** - * 处理微信支付回调通知 - * - * @param headers 请求头 - * @param body 请求体 - * @param tenantId 租户ID - * @return 处理结果响应 - */ - public String handlePaymentNotify(Map headers, String body, Integer tenantId) { - log.info("{}, 租户ID: {}", WxPayConstants.LogMessage.CALLBACK_START, tenantId); - - try { - // 参数验证 - validateNotifyParams(headers, body, tenantId); - - // 获取微信支付配置 - Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId); - - // 解析并验证回调数据 - Transaction transaction = parseAndVerifyNotification(headers, body, wxPayConfig); - - // 处理支付结果 - processPaymentResult(transaction, tenantId); - - log.info("{}, 租户ID: {}, 订单号: {}", - WxPayConstants.LogMessage.CALLBACK_SUCCESS, tenantId, transaction.getOutTradeNo()); - - return WxPayConstants.Notify.SUCCESS_RESPONSE; - - } catch (WxPayException e) { - log.error("{}, 租户ID: {}, 错误: {}", - WxPayConstants.LogMessage.CALLBACK_FAILED, tenantId, e.getMessage(), e); - return WxPayConstants.Notify.FAIL_RESPONSE; - } catch (Exception e) { - log.error("{}, 租户ID: {}, 系统错误: {}", - WxPayConstants.LogMessage.CALLBACK_FAILED, tenantId, e.getMessage(), e); - return WxPayConstants.Notify.FAIL_RESPONSE; - } - } - - /** - * 验证回调通知参数 - */ - private void validateNotifyParams(Map headers, String body, Integer tenantId) - throws WxPayException { - - if (tenantId == null) { - throw WxPayException.paramError("租户ID不能为空"); - } - - if (headers == null || headers.isEmpty()) { - throw WxPayException.paramError("请求头不能为空"); - } - - if (!StringUtils.hasText(body)) { - throw WxPayException.paramError("请求体不能为空"); - } - - // 验证必要的微信支付头部 - String signature = headers.get(WxPayConstants.Header.WECHATPAY_SIGNATURE); - String timestamp = headers.get(WxPayConstants.Header.WECHATPAY_TIMESTAMP); - String nonce = headers.get(WxPayConstants.Header.WECHATPAY_NONCE); - String serial = headers.get(WxPayConstants.Header.WECHATPAY_SERIAL); - - if (!StringUtils.hasText(signature)) { - throw WxPayException.paramError("微信支付签名不能为空"); - } - - if (!StringUtils.hasText(timestamp)) { - throw WxPayException.paramError("微信支付时间戳不能为空"); - } - - if (!StringUtils.hasText(nonce)) { - throw WxPayException.paramError("微信支付随机数不能为空"); - } - - if (!StringUtils.hasText(serial)) { - throw WxPayException.paramError("微信支付序列号不能为空"); - } - - log.debug("回调通知参数验证通过, 租户ID: {}", tenantId); - } - - /** - * 解析并验证回调通知 - */ - private Transaction parseAndVerifyNotification(Map headers, String body, Config wxPayConfig) - throws WxPayException { - - try { - // 构建请求参数 - RequestParam requestParam = new RequestParam.Builder() - .serialNumber(headers.get(WxPayConstants.Header.WECHATPAY_SERIAL)) - .nonce(headers.get(WxPayConstants.Header.WECHATPAY_NONCE)) - .signature(headers.get(WxPayConstants.Header.WECHATPAY_SIGNATURE)) - .timestamp(headers.get(WxPayConstants.Header.WECHATPAY_TIMESTAMP)) - .body(body) - .build(); - - // 创建通知解析器 - NotificationParser parser = new NotificationParser(wxPayConfig); - - // 解析并验证通知 - Transaction transaction = parser.parse(requestParam, Transaction.class); - - if (transaction == null) { - throw WxPayException.callbackError("解析回调通知失败:transaction为空", null); - } - - log.debug("回调通知解析成功, 订单号: {}, 交易状态: {}", - transaction.getOutTradeNo(), transaction.getTradeState()); - - return transaction; - - } catch (Exception e) { - if (e instanceof WxPayException) { - throw e; - } - throw WxPayException.signatureError("签名验证失败: " + e.getMessage()); - } - } - - /** - * 处理支付结果 - */ - private void processPaymentResult(Transaction transaction, Integer tenantId) throws WxPayException { - String outTradeNo = transaction.getOutTradeNo(); - String tradeState = transaction.getTradeState(); - - if (!StringUtils.hasText(outTradeNo)) { - throw WxPayException.paramError("商户订单号不能为空"); - } - - // 查询订单 - ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo); - if (order == null) { - throw WxPayException.orderStatusError("订单不存在: " + outTradeNo); - } - - // 验证租户ID - if (!tenantId.equals(order.getTenantId())) { - throw WxPayException.tenantConfigError("订单租户ID不匹配", tenantId); - } - - // 验证订单状态 - 使用Boolean类型的payStatus字段 - if (Boolean.TRUE.equals(order.getPayStatus())) { - log.info("订单已支付,跳过处理, 订单号: {}", outTradeNo); - return; - } - - // 根据交易状态处理 - switch (tradeState) { - case WxPayConstants.PayStatus.SUCCESS: - handlePaymentSuccess(order, transaction); - break; - case WxPayConstants.PayStatus.REFUND: - handlePaymentRefund(order, transaction); - break; - case WxPayConstants.PayStatus.CLOSED: - case WxPayConstants.PayStatus.REVOKED: - case WxPayConstants.PayStatus.PAYERROR: - handlePaymentFailed(order, transaction); - break; - default: - log.warn("未处理的交易状态: {}, 订单号: {}", tradeState, outTradeNo); - break; - } - } - - /** - * 处理支付成功 - */ - private void handlePaymentSuccess(ShopOrder order, Transaction transaction) throws WxPayException { - try { - // 验证金额 - validateAmount(order, transaction); - - // 更新订单状态 - order.setPayStatus(true); // 使用Boolean类型 - order.setTransactionId(transaction.getTransactionId()); - order.setPayTime(transaction.getSuccessTime()); - - // 使用专门的更新方法,会触发支付成功后的业务逻辑 - shopOrderService.updateByOutTradeNo(order); - - // 推送支付结果通知 - pushPaymentNotification(order, transaction); - - log.info("支付成功处理完成, 订单号: {}, 微信交易号: {}", - order.getOrderNo(), transaction.getTransactionId()); - - } catch (Exception e) { - throw WxPayException.callbackError("处理支付成功回调失败: " + e.getMessage(), e); - } - } - - /** - * 处理支付退款 - */ - private void handlePaymentRefund(ShopOrder order, Transaction transaction) throws WxPayException { - try { - log.info("处理支付退款, 订单号: {}, 微信交易号: {}", - order.getOrderNo(), transaction.getTransactionId()); - - // 这里可以添加退款相关的业务逻辑 - // 例如:更新订单状态、处理库存、发送通知等 - - } catch (Exception e) { - throw WxPayException.callbackError("处理支付退款回调失败: " + e.getMessage(), e); - } - } - - /** - * 处理支付失败 - */ - private void handlePaymentFailed(ShopOrder order, Transaction transaction) throws WxPayException { - try { - log.info("处理支付失败, 订单号: {}, 交易状态: {}", - order.getOrderNo(), transaction.getTradeState()); - - // 这里可以添加支付失败相关的业务逻辑 - // 例如:释放库存、发送通知等 - - } catch (Exception e) { - throw WxPayException.callbackError("处理支付失败回调失败: " + e.getMessage(), e); - } - } - - /** - * 验证支付金额 - */ - private void validateAmount(ShopOrder order, Transaction transaction) throws WxPayException { - if (transaction.getAmount() == null || transaction.getAmount().getTotal() == null) { - throw WxPayException.amountError("回调通知中金额信息为空"); - } - - // 将订单金额转换为分 - BigDecimal orderAmount = order.getMoney(); - if (orderAmount == null) { - throw WxPayException.amountError("订单金额为空"); - } - - int orderAmountFen = orderAmount.multiply(new BigDecimal(WxPayConstants.Config.AMOUNT_MULTIPLIER)).intValue(); - int callbackAmountFen = transaction.getAmount().getTotal(); - - if (orderAmountFen != callbackAmountFen) { - throw WxPayException.amountError( - String.format("订单金额不匹配,订单金额: %d分, 回调金额: %d分", - orderAmountFen, callbackAmountFen)); - } - - log.debug("金额验证通过, 订单号: {}, 金额: {}分", order.getOrderNo(), orderAmountFen); - } - - /** - * 推送支付结果通知 - */ - private void pushPaymentNotification(ShopOrder order, Transaction transaction) { - try { - // 使用现有的推送工具 - requestUtil.pushWxPayNotify(transaction, null); - log.debug("支付结果通知推送成功, 订单号: {}", order.getOrderNo()); - } catch (Exception e) { - log.warn("支付结果通知推送失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage()); - // 推送失败不影响主流程,只记录日志 - } - } -}