diff --git a/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/exception/PayException.java b/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/exception/PayException.java new file mode 100644 index 000000000..91695a2a2 --- /dev/null +++ b/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/exception/PayException.java @@ -0,0 +1,63 @@ +package com.cf.imes.framework.common.exception; + +import com.cf.imes.framework.common.exception.enums.ServiceErrorCodeRange; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 支付系统异常 Exception + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PayException extends RuntimeException { + + /** + * 业务错误码 + * + * @see ServiceErrorCodeRange + */ + private Integer code; + /** + * 错误提示 + */ + private String message; + + public PayException(Throwable cause) { + super(cause); + } + + /** + * 空构造方法,避免反序列化问题 + */ + public PayException() { + } + + public PayException(ErrorCode errorCode) { + this.code = errorCode.getCode(); + this.message = errorCode.getMsg(); + } + + public PayException(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public PayException setCode(Integer code) { + this.code = code; + return this; + } + + @Override + public String getMessage() { + return message; + } + + public PayException setMessage(String message) { + this.message = message; + return this; + } +} diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengAlipayConfig.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengAlipayConfig.java new file mode 100644 index 000000000..02224056a --- /dev/null +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengAlipayConfig.java @@ -0,0 +1,70 @@ +package com.cf.imes.framework.pay.config; + +import com.cf.imes.framework.common.util.encrypt.AesUtils; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Gqr + * @since 2025/4/15 16:24 + */ +@ConfigurationProperties(prefix = "chenfeng.pay.alipay") +@Data +public class ChenfengAlipayConfig extends com.alipay.api.AlipayConfig { + @Value("${chenfeng.encrypt.publicKey:}") + private String publicKey; + + /** + * 销售产品码 + */ + private String productCode = "FAST_INSTANT_TRADE_PAY"; + + /** + * 二维码模式:效果参见:https://help.pingxx.com/article/1137360/ + */ + private String qrPayMode = "1"; + + /** + * 给支付宝的回调地址 + */ + private String notifyUrl; + + // 加密数据 + + @Override + public void setAppId(String appId) { + if (StringUtils.isNotEmpty(publicKey)) { + super.setAppId(AesUtils.decrypt(appId, publicKey)); + } else { + super.setAppId(appId); + } + } + + @Override + public void setPrivateKey(String privateKey) { + if (StringUtils.isNotEmpty(publicKey)) { + super.setPrivateKey(AesUtils.decrypt(privateKey, publicKey)); + } else { + super.setPrivateKey(privateKey); + } + } + + @Override + public void setAlipayPublicKey(String alipayPublicKey) { + if (StringUtils.isNotEmpty(publicKey)) { + super.setAlipayPublicKey(AesUtils.decrypt(alipayPublicKey, publicKey)); + } else { + super.setAlipayPublicKey(alipayPublicKey); + } + } + + public void setNotifyUrl(String notifyUrl) { + if (StringUtils.isNotEmpty(publicKey)) { + this.notifyUrl = AesUtils.decrypt(notifyUrl, publicKey); + } else { + this.notifyUrl = notifyUrl; + } + } +} diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengPayAutoConfiguration.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengPayAutoConfiguration.java index 12859b3d1..b769364f3 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengPayAutoConfiguration.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengPayAutoConfiguration.java @@ -3,6 +3,7 @@ package com.cf.imes.framework.pay.config; import com.cf.imes.framework.pay.core.client.PayClientFactory; import com.cf.imes.framework.pay.core.client.impl.PayClientFactoryImpl; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; /** @@ -11,6 +12,7 @@ import org.springframework.context.annotation.Bean; * @author 晨丰科技 */ @AutoConfiguration +@EnableConfigurationProperties({ChenfengAlipayConfig.class, ChenfengWechatPayConfig.class}) public class ChenfengPayAutoConfiguration { @Bean diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java new file mode 100644 index 000000000..88a8b0e3d --- /dev/null +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java @@ -0,0 +1,14 @@ +package com.cf.imes.framework.pay.config; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Gqr + * @since 2025/4/15 16:52 + */ +@ConfigurationProperties(prefix = "chenfeng.pay.wechat") +@Data +public class ChenfengWechatPayConfig extends WxPayConfig { +} diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderRespDTO.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderRespDTO.java index 4a708a028..5338054be 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderRespDTO.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderRespDTO.java @@ -1,6 +1,6 @@ package com.cf.imes.framework.pay.core.client.dto.order; -import com.cf.imes.framework.pay.core.client.exception.PayException; +import com.cf.imes.framework.common.exception.PayException; import com.cf.imes.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import com.cf.imes.framework.pay.core.enums.order.PayOrderStatusRespEnum; import lombok.Data; diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java index 8b9791fdb..23e0cd536 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java @@ -1,6 +1,7 @@ package com.cf.imes.framework.pay.core.client.dto.order; import com.cf.imes.framework.pay.core.enums.order.PayOrderDisplayModeEnum; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.URL; @@ -11,6 +12,9 @@ import javax.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.Map; +import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; + /** * 统一下单 Request DTO * @@ -70,6 +74,7 @@ public class PayOrderUnifiedReqDTO { * 支付过期时间 */ @NotNull(message = "支付过期时间不能为空") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) private LocalDateTime expireTime; // ========== 拓展参数 ========== diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/refund/PayRefundRespDTO.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/refund/PayRefundRespDTO.java index ee388eff2..9959fedd3 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/refund/PayRefundRespDTO.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/dto/refund/PayRefundRespDTO.java @@ -1,6 +1,6 @@ package com.cf.imes.framework.pay.core.client.dto.refund; -import com.cf.imes.framework.pay.core.client.exception.PayException; +import com.cf.imes.framework.common.exception.PayException; import com.cf.imes.framework.pay.core.enums.refund.PayRefundStatusRespEnum; import lombok.Data; diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/exception/PayException.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/exception/PayException.java deleted file mode 100644 index 94ad5d432..000000000 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/exception/PayException.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.cf.imes.framework.pay.core.client.exception; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -/** - * 支付系统异常 Exception - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class PayException extends RuntimeException { - - public PayException(Throwable cause) { - super(cause); - } - -} diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/impl/AbstractPayClient.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/impl/AbstractPayClient.java index 698b33aeb..0315c7b31 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/impl/AbstractPayClient.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/core/client/impl/AbstractPayClient.java @@ -10,7 +10,7 @@ import com.cf.imes.framework.pay.core.client.dto.refund.PayRefundRespDTO; import com.cf.imes.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import com.cf.imes.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import com.cf.imes.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; -import com.cf.imes.framework.pay.core.client.exception.PayException; +import com.cf.imes.framework.common.exception.PayException; import lombok.extern.slf4j.Slf4j; import java.util.Map; diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java index 8af1e1bb4..59d7b58c8 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java @@ -8,7 +8,7 @@ import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil; import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; import com.cf.imes.framework.pay.core.client.dto.refund.PayRefundRespDTO; import com.cf.imes.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; -import com.cf.imes.framework.pay.core.client.exception.PayException; +import com.cf.imes.framework.common.exception.PayException; import com.cf.imes.framework.pay.core.enums.refund.PayRefundStatusRespEnum; import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest; import com.alipay.api.AlipayApiException; diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java index 3e24c8f94..8168a008a 100644 --- a/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java +++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/test/java/com/cf/imes/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java @@ -5,7 +5,7 @@ import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil; import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; -import com.cf.imes.framework.pay.core.client.exception.PayException; +import com.cf.imes.framework.common.exception.PayException; import com.cf.imes.framework.pay.core.enums.order.PayOrderDisplayModeEnum; import com.alipay.api.AlipayApiException; import com.alipay.api.request.AlipayTradePrecreateRequest; diff --git a/cf-framework/cf-spring-boot-starter-web/src/main/java/com/cf/imes/framework/web/core/handler/GlobalExceptionHandler.java b/cf-framework/cf-spring-boot-starter-web/src/main/java/com/cf/imes/framework/web/core/handler/GlobalExceptionHandler.java index f8170c29a..3f7ebfd18 100644 --- a/cf-framework/cf-spring-boot-starter-web/src/main/java/com/cf/imes/framework/web/core/handler/GlobalExceptionHandler.java +++ b/cf-framework/cf-spring-boot-starter-web/src/main/java/com/cf/imes/framework/web/core/handler/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ package com.cf.imes.framework.web.core.handler; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.text.CharSequenceUtil; +import com.cf.imes.framework.common.exception.PayException; import com.cf.imes.framework.common.exception.ServiceException; import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.framework.common.util.monitor.TracerUtils; @@ -98,6 +99,9 @@ public class GlobalExceptionHandler { if (ex instanceof HttpMessageNotReadableException) { return httpMessageNotReadableExceptionHandler(request, (HttpMessageNotReadableException) ex); } + if (ex instanceof PayException) { + return payExceptionHandler((PayException) ex); + } return defaultExceptionHandler(request, ex); } @@ -260,6 +264,18 @@ public class GlobalExceptionHandler { return CommonResult.error(ex.getCode(), ex.getMessage()); } + /** + * 支付异常 PayException + * + * @param ex + * @return + */ + @ExceptionHandler(value = PayException.class) + public CommonResult payExceptionHandler(PayException ex) { + log.info(String.format("==========[payExceptionHandler]==========,%s", ex.getMessage())); + return CommonResult.error(ex.getCode(), ex.getMessage()); + } + /** * 处理数据库DuplicateKeyException,违反唯一约束异常 * diff --git a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java index be6c6405c..8e25bbaa4 100644 --- a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java +++ b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java @@ -421,24 +421,33 @@ public class ErrorCodeConstants { public static final ErrorCode INVOICE_FILE_IS_NULL_NOT_DOWNLOAD = new ErrorCode(1_002_043_006, "发票附件不存在,无法下载"); public static final ErrorCode INVOICE_FILE_DATA_ERROR = new ErrorCode(1_002_043_007, "发票附件下载失败,请重新下载或稍等重试"); + //=========== 支付相关 1-002-044-000 ============ + public static final ErrorCode PAY_CHANNEL_NOT_SUPPORT = new ErrorCode(1_002_044_000, "暂不支持的支付渠道,请使用其他支付渠道"); + public static final ErrorCode PAY_ORDER_NOT_EXIST = new ErrorCode(1_002_044_001, "支付订单不存在,支付编号:{},请重新发起支付"); + public static final ErrorCode PAY_ORDER_CHANNEL_QUERY_NOT_EXIST = new ErrorCode(1_002_044_002, "如果您已确认扫码支付,请稍后刷新页面,支付平台可能存在延迟到账"); + public static final ErrorCode PAY_ORDER_NOTIFY_DATA_PARSE_ERROR = new ErrorCode(1_002_044_003, "解析支付回调数据异常"); + public static final ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_002_044_004, "支付订单不处于待支付"); + public static final ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_002_044_005, "发起渠道支付异常,请联系客服"); - public static final ErrorCode PRODUCTS_NO_EXIST = new ErrorCode(1_002_044_001, "当前产品不存在"); - public static final ErrorCode PRODUCTS_DETAIL_NO_EXIST = new ErrorCode(1_002_044_002, "当前产品的规则不存在"); - public static final ErrorCode PRODUCTS_DURATION_NOT_NULL = new ErrorCode(1_002_044_003, "购买方式为时长时,需确定产品时长"); - public static final ErrorCode PRODUCTS_OUTPUT_NOT_NULL = new ErrorCode(1_002_044_004, "购买方式为产量时,需确定产量数量"); - public static final ErrorCode PRODUCTS_DETAIL_DURATION_EXIST = new ErrorCode(1_002_044_005, "存在当前时长的产品定价规则"); - public static final ErrorCode PRODUCTS_DETAIL_OUTPUT_EXIST = new ErrorCode(1_002_044_006, "存在当前产量的产品定价规则"); - public static final ErrorCode PRODUCTS_DURATION_NUM_ERR = new ErrorCode(1_002_044_007, "产品定价规则,时长数据设置有误"); - public static final ErrorCode PRODUCTS_DURATION_INT_ERR = new ErrorCode(1_002_044_008, "产品定价规则,时长数据为正整数"); - public static final ErrorCode PRODUCTS_DURATION_UNIT_ERR = new ErrorCode(1_002_044_009, "产品定价规则,时长单位设置有误"); + //=========== 产品相关 1-002-045-000 ============ + public static final ErrorCode PRODUCTS_NO_EXIST = new ErrorCode(1_002_045_001, "当前产品不存在"); + public static final ErrorCode PRODUCTS_DETAIL_NO_EXIST = new ErrorCode(1_002_045_002, "当前产品的规则不存在"); + public static final ErrorCode PRODUCTS_DURATION_NOT_NULL = new ErrorCode(1_002_045_003, "购买方式为时长时,需确定产品时长"); + public static final ErrorCode PRODUCTS_OUTPUT_NOT_NULL = new ErrorCode(1_002_045_004, "购买方式为产量时,需确定产量数量"); + public static final ErrorCode PRODUCTS_DETAIL_DURATION_EXIST = new ErrorCode(1_002_045_005, "存在当前时长的产品定价规则"); + public static final ErrorCode PRODUCTS_DETAIL_OUTPUT_EXIST = new ErrorCode(1_002_045_006, "存在当前产量的产品定价规则"); + public static final ErrorCode PRODUCTS_DURATION_NUM_ERR = new ErrorCode(1_002_045_007, "产品定价规则,时长数据设置有误"); + public static final ErrorCode PRODUCTS_DURATION_INT_ERR = new ErrorCode(1_002_045_008, "产品定价规则,时长数据为正整数"); + public static final ErrorCode PRODUCTS_DURATION_UNIT_ERR = new ErrorCode(1_002_045_009, "产品定价规则,时长单位设置有误"); - public static final ErrorCode ADVERTISEMENT_POSITION_NO_EXIST = new ErrorCode(1_003_001_001, "广告所选放置位置不存在"); - public static final ErrorCode ADVERTISEMENT_NO_EXIST = new ErrorCode(1_003_001_002, "广告不存在"); - public static final ErrorCode ADVERTISEMENT_STATUS_ERROR = new ErrorCode(1_003_001_003, "当前广告已经发布,无需立即发布"); - public static final ErrorCode ADVERTISEMENT_STATUS_ERROR2 = new ErrorCode(1_003_001_004, "当前广告已过结束时间,请点击重新发布"); - public static final ErrorCode ADVERTISEMENT_END_ERROR = new ErrorCode(1_003_001_005, "当前广告未发布,无需结束发布"); - public static final ErrorCode ADVERTISEMENT_END_ERROR2 = new ErrorCode(1_003_001_006, "当前广告已结束,无需立即结束"); - public static final ErrorCode ADVERTISEMENT_FAIL = new ErrorCode(1_003_001_007, "自动发布失败"); + //=========== 广告相关 1-002-046-000 ============ + public static final ErrorCode ADVERTISEMENT_POSITION_NO_EXIST = new ErrorCode(1_002_046_001, "广告所选放置位置不存在"); + public static final ErrorCode ADVERTISEMENT_NO_EXIST = new ErrorCode(1_002_046_002, "广告不存在"); + public static final ErrorCode ADVERTISEMENT_STATUS_ERROR = new ErrorCode(1_002_046_003, "当前广告已经发布,无需立即发布"); + public static final ErrorCode ADVERTISEMENT_STATUS_ERROR2 = new ErrorCode(1_002_046_004, "当前广告已过结束时间,请点击重新发布"); + public static final ErrorCode ADVERTISEMENT_END_ERROR = new ErrorCode(1_002_046_005, "当前广告未发布,无需结束发布"); + public static final ErrorCode ADVERTISEMENT_END_ERROR2 = new ErrorCode(1_002_046_006, "当前广告已结束,无需立即结束"); + public static final ErrorCode ADVERTISEMENT_FAIL = new ErrorCode(1_002_046_007, "自动发布失败"); public static final ErrorCode RECHARGE_ACTIVE_NO_EXIST = new ErrorCode(1_004_001_001, "当前赠送金活动不存在"); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java index 78b010426..11bdabec4 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java @@ -1,16 +1,11 @@ package com.cf.imes.module.system.controller.admin.pay; -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.lang.generator.SnowflakeGenerator; -import cn.hutool.http.Method; -import com.alipay.api.AlipayApiException; -import com.alipay.api.AlipayConfig; -import com.alipay.api.DefaultAlipayClient; -import com.alipay.api.domain.AlipayTradePagePayModel; -import com.alipay.api.request.AlipayTradePagePayRequest; -import com.alipay.api.response.AlipayTradePagePayResponse; import com.cf.imes.framework.common.pojo.CommonResult; -import com.cf.imes.framework.pay.core.enums.order.PayOrderDisplayModeEnum; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqVO; +import com.cf.imes.module.system.service.pay.PayOrderService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -21,94 +16,50 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import javax.annotation.Resource; import javax.annotation.security.PermitAll; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.Map; -import java.util.Objects; -import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; import static com.cf.imes.framework.common.pojo.CommonResult.success; /** * @author Gqr * @since 2025/4/8 16:09 */ -//@Tag(name = "管理后台 - 应用注册信息") +@Tag(name = "管理后台 - 统一支付") @RestController @RequestMapping("/system/pay") @Validated @Slf4j public class PayController { -// @Resource -// private SnowflakeIdWorker3rd snowflakeIdWorker3rd; + @Resource + private PayOrderService payOrderService; - @GetMapping("") -// @Operation(summary = "获得应用注册") -// @Parameter(name = "id", description = "编号", required = true, example = "1024") -// @PreAuthorize("@ss.hasPermission('system:application:query')") - @PermitAll - public CommonResult getApplication() throws AlipayApiException { - // 1.1 构建 AlipayTradePagePayModel 请求 - AlipayTradePagePayModel model = new AlipayTradePagePayModel(); - // ① 通用的参数 - SnowflakeGenerator snowflakeGenerator = new SnowflakeGenerator(); - String outTradeNo = snowflakeGenerator.next().toString(); - model.setOutTradeNo(outTradeNo); - model.setSubject("产品名称"); - model.setBody("描述"); - model.setTotalAmount("0.11"); - model.setTimeExpire(LocalDateTimeUtil.format(LocalDateTime.now().plus(100, ChronoUnit.SECONDS), NORM_DATETIME_FORMATTER)); - model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY - // ② 个性化的参数 - // 如果想弄更多个性化的参数,可参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分进行拓展 - model.setQrPayMode("1"); // 跳转模式 - 订单码,效果参见:https://help.pingxx.com/article/1137360/ - // ③ 支付宝 PC 支付有两种展示模式:FORM、URL - String displayMode = PayOrderDisplayModeEnum.URL.getMode(); + @PostMapping() + @Operation(summary = "创建支付订单") + public CommonResult initiatePay(@RequestBody PayOrderUnifiedReqVO payOrderUnifiedReqVO) throws Throwable { + PayOrderRespDTO payOrderRespDTO = payOrderService.initiatePay(payOrderUnifiedReqVO); + return success(payOrderRespDTO); + } - // 1.2 构建 AlipayTradePagePayRequest 请求 - AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); - request.setBizModel(model); - request.setNotifyUrl("https://chenfeng.tech:3345/admin-api/system/pay/order/" + outTradeNo); - request.setReturnUrl(""); - - // 2.1 执行请求 - AlipayTradePagePayResponse response; - AlipayConfig alipayConfig = new AlipayConfig(); - alipayConfig.setServerUrl("https://openapi-sandbox.dl.alipaydev.com/gateway.do"); - alipayConfig.setFormat("json"); - alipayConfig.setCharset("utf-8"); - alipayConfig.setSignType("RSA2"); - alipayConfig.setPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzOXNjRYBHpsJ2WGm43QB4ktKY6m24xf0Mepn5iHPyHtvbBPQXa3lyG7Nztxwk+oL5jbxEj+QB6nNVL2anOJT9fzr96qEwNc9a+7RnOeGDVfWN/FRTEYw0oExzqLoiO1rZlEWUpFpFnhQkEcBco/SJEJAE9WOe9N8y2O5B4EU1ik5667u8fzyC+XukTj/NhIhqdQdN1Vr953rFI8eh3ToDYcnvLjq2yt6zLLbhSxhg6mvw7dapHTyPitqkm/OhgfcNjlfSuTAIj7KtgluFA2eZzWqwbJmeKCqv///6YQl8/sFpHDmVY39WN0hQwoEFTo/dcWChsrN1cH1cbooIWdQ7AgMBAAECggEAO1cjSFQjtBWXv11oq8eot0NNrUm5SHttXGtffcpbMWhcPPk35GU2UbywPvj6vZegNAZktoK56JR62VKXd3h5hDFTE+1Sr0axwBsk0STN8RFN+1P3i4V82ZPVnFnaPylN3EqGez+rj+9WFdc2zLWQaq1sMegZsvC60fgXcwJRh/cT7FKLJe3qxb2nJJB+d+6yEHKMgaHSICv3b7SpGuhJaq8O62iY1oDj/u9EKgLtU28kiAunvXHnby1F6T+Fq26wCn3Q0GnRKWAhGXElXGjAY8hzcR0RBGLuROJbW3oa1wXX1DOMnRiAaHaFXsj7TTkGc3sCyGOLb9RgVaR9jQ9fwQKBgQDmQLQRACspFJeL640mO7C42P2zeu9hcHPypOudkMZIXobvvwZkP7w6rIv9XbG8xah7P5kNbaM1KP3ke5pNHnrCiphEv8rbmHlfoSaNKSS/omLAAa/Xm0OqMUOgpHe3PFITsiE2SARIqZaQoaR8+UUkJsRnIyLuf43SoOG1FC9F2wKBgQDHQ/4He/V/IxuTac7O+qMM9lFu+uyhsc+bMecyylh0AoADmscg4l98Kcs+IS5QB1HYEYo1fNElJ1Rs8iREHm+iK3h4xKIrG6KhObTVnqfYlCz2MYiHDW252KOqPdyxpeEuMSpoIVdcsvDBb+yYCTOT/DfX16eRSyl2pSv7SkFpIQKBgQDih1PgyfaskvrKCPO0YtNp+OoZE2DBz53zYjgAq/e4KW++HYUPSflJDIspuW1tGFW69AYDRRNl8K9hhA15w/vou+w07zCIPYlkASrphEoIU52t47r5d2Lp5U/wkv7JAoqJoB22xsGYL2jXYIBxB9jzSUS/Z70tQcbpK6kDuxkLMwKBgGgEyz/W6qFyjzuVmTqcxa7Hilz3qb0CmBA4rarqy9/LSk4XZul4okWrf4gwQ//MwmGbTl17iaXucPjqMhpJFRc/wASzCMCeDXhCKm71AkNhnlLqKo3taaEBQPZmkqU9pV26lpuDBBrE4j7oEUaQTxdqKzCGR9flt4BS2rGz4o9hAoGAYPufk/GigoOzUhv8KS2Vm7syIEr0jLOM87AmoO5DQrKGLoy80Nvjr5XuAhTwB0UnFUuchyFimmew3Bbi3iTCjAUmC06w4KWcTbY2Lu4YQpZU7gkFViuAWLIuJgN2xmWaCoaiphbVqB/+Wew4gFVgGXF39AUJS4fiXAIj4QiWO/I="); - alipayConfig.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtNsXwyaUwihNYoDLXZbET+jroEDUk1DfarrbcMPZy/uIW423KSDwTQi9SPuR4EgwB4sHy/UFhLe95gk0LnhZeZ0ddTSxsZ/Ps5eHlwxV0CR1Xw5K+yMNKyXkSuKDx5pbIv+xUveWRlOqte9fZDOKfXMplEjKJS/CeosXyKyzKiO8bXuYSBv4+lt6RkgFdKjEPGE5AqtvqyGDN86CHe2zf0lJUWLUwb4nlWpoW/6SihhG9+MKUrnBGPI132lZWkmZz3MriKb6yGo4/7Ospm+oWZYGj7vzI+WX25mFONFzmLx8q8h3PnZfODr7uXenRbaVraj7LRAneVSKKi3sAzdonwIDAQAB"); - alipayConfig.setAppId("9021000142647599"); - - DefaultAlipayClient client = new DefaultAlipayClient(alipayConfig); - if (Objects.equals(displayMode, PayOrderDisplayModeEnum.FORM.getMode())) { - response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求 - } else { - response = client.pageExecute(request, Method.GET.name()); - } - System.out.println(1); - // 2.2 处理结果 - if (!response.isSuccess()) { -// return buildClosedPayOrderRespDTO(reqDTO, response); - } - return success(response.getBody()); -// PayOrderRespDTO.waitingOf(displayMode, response.getBody(), -// reqDTO.getOutTradeNo(), response); -// return success(true); + @GetMapping("/order/{channelCode}/{outTradeNo}") + @Operation(summary = "查询支付订单") + public CommonResult queryPayOrder(@PathVariable("outTradeNo") String outTradeNo, @PathVariable("channelCode") int channelCode) { + payOrderService.queryPayOrderStatus(outTradeNo, channelCode); + return success(true); } - @PostMapping(value = "/order/{channelId}") -// @Operation(summary = "支付渠道的统一【支付】回调") + @PostMapping(value = "/notify/order/{channelCode}") + @Operation(summary = "支付渠道回调") @PermitAll - public String notifyOrder(@PathVariable("channelId") Long channelId, + public String notifyOrder(@PathVariable("channelCode") Integer channelCode, @RequestParam(required = false) Map params, @RequestBody(required = false) String body) { - log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body); + log.debug("[notifyOrder][channelCode({}) 回调数据({}/{})]", channelCode, params, body); + payOrderService.doOrderNotify(channelCode, params, body); + // 支付接口回调相应必须是"success" return "success"; } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayOrderUnifiedReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayOrderUnifiedReqVO.java new file mode 100644 index 000000000..73d5abf71 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayOrderUnifiedReqVO.java @@ -0,0 +1,45 @@ +package com.cf.imes.module.system.controller.admin.pay.vo; + +import com.cf.imes.module.system.validation.pay.PayChannelCodeValid; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.Valid; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 统一支付vo + * + * @author Gqr + * @since 2025/4/15 14:30 + */ +@Data +@Valid +public class PayOrderUnifiedReqVO { + /** + * 商品标题 + */ + @NotEmpty(message = "商品标题不能为空") + @Length(max = 32, message = "商品标题不能超过 32") + private String subject; + + /** + * 支付金额,单位:元 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private BigDecimal price; + + @NotNull(message = "订单失效时间不能为空") + private LocalDateTime expireTime; + + @Schema(description = "支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @PayChannelCodeValid + @NotNull(message = "支付渠道编码不能为空") + private Integer channelCode; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java new file mode 100644 index 000000000..66df5f93d --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java @@ -0,0 +1,95 @@ +package com.cf.imes.module.system.dal.dataobject.pay; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.cf.imes.framework.mybatis.core.dataobject.BaseDO; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; +import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 支付订单 + * + * @author Gqr + * @since 2025/4/15 11:34 + */ +@TableName("pay_order") +@KeySequence("pay_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class PayOrderDO extends BaseDO { + + /** + * 支付订单编号(主键) + */ + @TableId + private Long id; + + /** + * 组织id + */ + @TableField(fill = FieldFill.INSERT) + private Long organId; + + /** + * 渠道编号 + */ + private PayChannelCodeEnum channelCode; + + /** + * 商品标题 + */ + private String subject; + + /** + * 支付金额 + */ + private BigDecimal price; + + /** + * 支付订单号:P + 当前id + 时间戳(yyyyMMddHHmmss) + */ + private String payNo; + + /** + * 支付状态 + */ + private PayOrderStatusEnum status; + + /** + * 订单失效时间 + */ + private LocalDateTime expireTime; + + /** + * 渠道方回调时间 + */ + private LocalDateTime notifyTime; + + /** + * 渠道方支付成功时间 + */ + private LocalDateTime successTime; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + /** + * 支付渠道的同步/异步通知的内容 + * + */ + private String channelNotifyData; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/pay/PayOrderMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/pay/PayOrderMapper.java new file mode 100644 index 000000000..3cc09b92f --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/pay/PayOrderMapper.java @@ -0,0 +1,15 @@ +package com.cf.imes.module.system.dal.mysql.pay; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 支付订单 Mapper + * + * @author Gqr + * @since 2025/4/15 11:41 + */ +@Mapper +public interface PayOrderMapper extends BaseMapper { +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java new file mode 100644 index 000000000..5cedf2a81 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java @@ -0,0 +1,38 @@ +package com.cf.imes.module.system.enums.pay; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付渠道编码 + * + * @author Gqr + * @since 2024/7/5 9:25 + */ +@Getter +@AllArgsConstructor +public enum PayChannelCodeEnum { + /** + * 支付宝 + */ + ALIPAY_PC(0), + /** + * 微信 + */ + WECHAT_NATIVE(1); + + @EnumValue + private final Integer code; + + @JsonCreator + public static PayChannelCodeEnum fromType(Integer type) { + for (PayChannelCodeEnum value : values()) { + if (value.getCode().equals(type)) { + return value; + } + } + return null; + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java new file mode 100644 index 000000000..4f35cdc3a --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java @@ -0,0 +1,66 @@ +package com.cf.imes.module.system.enums.pay; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 支付订单状态 + * + * @author Gqr + * @since 2025/4/15 11:48 + */ +@Getter +@AllArgsConstructor +public enum PayOrderStatusEnum { + /** + * 等待支付 + */ + WAITING(0), + + /** + * 支付成功 + */ + SUCCESS(10), + + /** + * 支付失败 + */ + FAIL(20); + + @EnumValue + private final Integer status; + + @JsonCreator + public static PayOrderStatusEnum fromType(Integer type) { + for (PayOrderStatusEnum value : values()) { + if (value.getStatus().equals(type)) { + return value; + } + } + return null; + } + + /** + * 判断是否支付成功 + * + * @param status 状态 + * @return 是否支付成功 + */ + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + /** + * 判断是否支付关闭 + * + * @param status 状态 + * @return 是否支付关闭 + */ + public static boolean isClosed(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()) || Objects.equals(status, FAIL.getStatus()); + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java new file mode 100644 index 000000000..dd0baf224 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java @@ -0,0 +1,42 @@ +package com.cf.imes.module.system.service.pay; + +import com.alipay.api.AlipayApiException; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqVO; + +import java.util.Map; + +/** + * 支付订单接口 + * + * @author Gqr + * @since 2025/4/17 17:29 + */ +public interface PayOrderService { + /** + * 发起支付 + * + * @param payOrderUnifiedReqVO + * @return + * @throws AlipayApiException + */ + PayOrderRespDTO initiatePay(PayOrderUnifiedReqVO payOrderUnifiedReqVO); + + /** + * 查询支付订单支付状态 + * 未支付、超时等都抛出支付异常 + * + * @param outTradeNo + * @return + */ + void queryPayOrderStatus(String outTradeNo, int channelCode); + + /** + * 解析和处理支付回调数据 + * + * @param params + * @param body + * @return + */ + void doOrderNotify(Integer channelCode, Map params, String body); +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java new file mode 100644 index 000000000..55fc1c1e1 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java @@ -0,0 +1,328 @@ +package com.cf.imes.module.system.service.pay; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; +import com.cf.imes.framework.common.util.json.JsonUtils; +import com.cf.imes.framework.common.util.validation.ValidationUtils; +import com.cf.imes.framework.organ.core.context.OrganContextHolder; +import com.cf.imes.framework.pay.config.ChenfengAlipayConfig; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import com.cf.imes.framework.common.exception.PayException; +import com.cf.imes.framework.pay.core.enums.order.PayOrderStatusRespEnum; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; +import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler; +import com.cf.imes.module.system.service.pay.factory.PayHandlerFactory; +import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqVO; +import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO; +import com.cf.imes.module.system.dal.mysql.pay.PayOrderMapper; +import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import javax.validation.Validator; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_CHANNEL_QUERY_NOT_EXIST; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_NOTIFY_DATA_PARSE_ERROR; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_NOT_EXIST; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_SUBMIT_CHANNEL_ERROR; + +/** + * 支付订单接口 实现类 + * + * @author Gqr + * @since 2025/4/17 17:29 + */ +@Service +@Validated +@Slf4j +public class PayOrderServiceImpl implements PayOrderService { + + @Resource + private PayHandlerFactory payHandlerFactory; + + @Resource + private PayOrderMapper payOrderMapper; + + @Resource + private Validator validator; + + private static final String PAY_ORDER_PREFIX_FORMAT = "P%dO%s%s"; + + @Resource + private ChenfengAlipayConfig alipayConfig; + + @Override + @Transactional(rollbackFor = Exception.class) + public PayOrderRespDTO initiatePay(PayOrderUnifiedReqVO payOrderUnifiedReqVO) { + // 主动校验一次入参 + ValidationUtils.validate(validator, payOrderUnifiedReqVO); + + Integer channelCode = payOrderUnifiedReqVO.getChannelCode(); + // 获取渠道方处理器 + AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode); + + // 创建支付订单 + PayOrderDO payOrder = createPayOrder(payOrderUnifiedReqVO); + payOrderMapper.insert(payOrder); + + // 发起渠道方支付 + PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = null; + try { + payOrderUnifiedReqDTO = createPayOrderUnifiedReqDTO(payOrder); + return payHandler.unifiedOrder(payOrderUnifiedReqDTO); + } catch (Throwable e) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]", + channelCode, JSON.toJSONString(payOrderUnifiedReqDTO), e); + throw new PayException(PAY_ORDER_SUBMIT_CHANNEL_ERROR); + } + } + + /** + * 创建支付订单 + * + * @param payOrderUnifiedReqVO + * @return + */ + private PayOrderDO createPayOrder(PayOrderUnifiedReqVO payOrderUnifiedReqVO) { + PayOrderDO payOrderDO = new PayOrderDO(); + + // 校验获取组织id + Long organId = validOrganId(); + + Long payOrderId = IdUtil.getSnowflake(0, 0).nextId(); + payOrderDO.setId(payOrderId); + payOrderDO.setPayNo(String.format(PAY_ORDER_PREFIX_FORMAT, organId, payOrderId, DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN))); + payOrderDO.setStatus(PayOrderStatusEnum.WAITING); + payOrderDO.setExpireTime(payOrderUnifiedReqVO.getExpireTime()); + payOrderDO.setPrice(payOrderUnifiedReqVO.getPrice()); + payOrderDO.setSubject(payOrderUnifiedReqVO.getSubject()); + payOrderDO.setChannelCode(PayChannelCodeEnum.fromType(payOrderUnifiedReqVO.getChannelCode())); + payOrderDO.setOrganId(organId); + return payOrderDO; + } + + /** + * 创建支付渠道方请求对象 + * + * @param payOrderDO + * @return + */ + private PayOrderUnifiedReqDTO createPayOrderUnifiedReqDTO(PayOrderDO payOrderDO) { + PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = new PayOrderUnifiedReqDTO(); + + String subject = payOrderDO.getSubject(); + payOrderUnifiedReqDTO.setSubject(subject); + payOrderUnifiedReqDTO.setBody(subject); + payOrderUnifiedReqDTO.setOutTradeNo(payOrderDO.getPayNo()); + payOrderUnifiedReqDTO.setPrice(payOrderDO.getPrice().intValue()); + payOrderUnifiedReqDTO.setExpireTime(payOrderDO.getExpireTime()); + payOrderUnifiedReqDTO.setNotifyUrl(String.format(alipayConfig.getNotifyUrl(), payOrderDO.getChannelCode().getCode())); + + return payOrderUnifiedReqDTO; + } + + @Override + public void queryPayOrderStatus(String outTradeNo, int channelCode) { + // 校验支付订单是否存在 + PayOrderDO payOrderDO = payOrderMapper.selectOne(new LambdaQueryWrapper() + .eq(PayOrderDO::getPayNo, outTradeNo) + .eq(PayOrderDO::getOrganId, validOrganId()) + .eq(PayOrderDO::getDeleted, false) + ); + if (ObjectUtils.isNull(payOrderDO)) { + throw exception(PAY_ORDER_NOT_EXIST, outTradeNo); + } + // 订单已被更新成功,直接支付成功了 + if (PayOrderStatusEnum.SUCCESS.getStatus().equals(payOrderDO.getStatus())) { + return; + } + + // 支付订单未成功,查询渠道方 + // 获取渠道方处理器 + AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode); + try { + PayOrderRespDTO payOrderRespDTO = payHandler.queryPayOrder(outTradeNo); + + boolean paySuccess = PayOrderStatusRespEnum.SUCCESS.getStatus().equals(payOrderRespDTO.getStatus()); + if (!paySuccess) { + throw new PayException(PAY_ORDER_CHANNEL_QUERY_NOT_EXIST); + } + } catch (Throwable e) { + log.error("渠道方查询支付订单异常", e); + throw new PayException(PAY_ORDER_CHANNEL_QUERY_NOT_EXIST); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void doOrderNotify(Integer channelCode, Map params, String body) { + // 获取渠道方处理器 + AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode); + + try { + // 解析回调数据 + PayOrderRespDTO payOrderRespDTO = payHandler.doParseOrderNotify(params, body); + + // 处理支付回调数据 + this.notifyOrder(payOrderRespDTO); + } catch (Throwable e) { + log.error("[parseOrderNotify][客户端({}) params({}) body({}) headers({}) 解析失败]", channelCode, params, body, e); + throw new PayException(PAY_ORDER_NOTIFY_DATA_PARSE_ERROR); + } + } + + /** + * 处理回调数据成功/失败 + * + * @param notify + */ + public void notifyOrder(PayOrderRespDTO notify) { + // 从payNo中解析组织id + Long organId = getOrganIdFromPayNo(notify.getOutTradeNo()); + + // 情况一:支付成功的回调 + if (PayOrderStatusEnum.isSuccess(notify.getStatus())) { + notifyOrderSuccess(notify, organId); + return; + } + // 情况二:支付失败的回调 + if (PayOrderStatusEnum.isClosed(notify.getStatus())) { + notifyOrderClosed(notify, organId); + } + } + + /** + * 从payNo中解析组织id + * + * @param outTradeNo + * @return + */ + private Long getOrganIdFromPayNo(String outTradeNo) { + Pattern pattern = Pattern.compile("P(\\d+)O"); + Matcher matcher = pattern.matcher(outTradeNo); + + if (matcher.find()) { + return Long.valueOf(matcher.group(1)); // 提取 P 和 O 中间的数字 + } else { + throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED); + } + } + + /** + * 支付成功回调处理 + * + * @param notify + */ + private void notifyOrderSuccess(PayOrderRespDTO notify, Long organId) { + // 1. 判断 PayOrderDO 是否处于待支付 + String outTradeNo = notify.getOutTradeNo(); + PayOrderDO order = payOrderMapper.selectOne(new LambdaQueryWrapper() + .eq(PayOrderDO::getPayNo, outTradeNo) + .eq(PayOrderDO::getOrganId, organId)); + if (ObjectUtil.isNull(order)) { + throw exception(PAY_ORDER_NOT_EXIST, outTradeNo); + } + // 如果已经是成功,直接返回,不用重复更新 + if (PayOrderStatusEnum.isSuccess(order.getStatus().getStatus())) { + log.debug("[updateOrderExtensionSuccess][order({}) 已经是已支付,无需更新]", order.getId()); + return; + } + if (!PayOrderStatusEnum.WAITING.equals(order.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderDO + int updateCounts = payOrderMapper.update(new LambdaUpdateWrapper() + .eq(PayOrderDO::getId, order.getId()) + .eq(PayOrderDO::getStatus, PayOrderStatusEnum.WAITING) + .eq(PayOrderDO::getOrganId, organId) + .set(PayOrderDO::getStatus, PayOrderStatusEnum.SUCCESS.getStatus()) + .set(PayOrderDO::getSuccessTime, notify.getSuccessTime()) + .set(PayOrderDO::getNotifyTime, LocalDateTime.now()) + .set(PayOrderDO::getChannelNotifyData, JsonUtils.zipString(JSON.toJSONString(notify)))); + + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + log.debug("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId()); + } + + /** + * 支付失败回调处理 + * + * @param notify + */ + private void notifyOrderClosed(PayOrderRespDTO notify, Long organId) { + // 1. 查询 PayOrderDO + String outTradeNo = notify.getOutTradeNo(); + PayOrderDO order = payOrderMapper.selectOne(new LambdaQueryWrapper() + .eq(PayOrderDO::getPayNo, outTradeNo) + .eq(PayOrderDO::getOrganId, organId)); + if (ObjectUtil.isNull(order)) { + throw exception(PAY_ORDER_NOT_EXIST, outTradeNo); + } + + Integer orderStatus = order.getStatus().getStatus(); + if (PayOrderStatusEnum.isClosed(orderStatus)) { // 如果已经是关闭,直接返回,不用重复更新 + log.debug("[notifyOrderClosed][order({}) 已经是支付关闭,无需更新]", order.getId()); + return; + } + // 一般出现先是支付成功,然后支付关闭,都是全部退款导致关闭的场景。这个情况,我们不更新支付拓展单,只通过退款流程,更新支付单 + if (PayOrderStatusEnum.isSuccess(orderStatus)) { + log.debug("[notifyOrderClosed][order({}) 是已支付,无需更新为支付关闭]", order.getId()); + return; + } + if (ObjectUtil.notEqual(orderStatus, PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderDO + int updateCounts = payOrderMapper.update(new LambdaUpdateWrapper() + .eq(PayOrderDO::getId, order.getId()) + .eq(PayOrderDO::getStatus, PayOrderStatusEnum.WAITING) + .eq(PayOrderDO::getOrganId, organId) + .set(PayOrderDO::getStatus, PayOrderStatusEnum.FAIL.getStatus()) + .set(PayOrderDO::getNotifyTime, LocalDateTime.now()) + .set(PayOrderDO::getChannelErrorCode, notify.getChannelErrorCode()) + .set(PayOrderDO::getChannelErrorMsg, notify.getChannelErrorMsg()) + .set(PayOrderDO::getChannelNotifyData, JsonUtils.zipString(JSON.toJSONString(notify)))); + + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + log.debug("[notifyOrderClosed][order({}) 更新为支付关闭]", order.getId()); + } + + /** + * 校验获取当前token请求下的组织id + * + * @return + */ + private Long validOrganId() { + // 校验token + Long organId = OrganContextHolder.getOrganId(); + if (ObjectUtil.isNull(organId)) { + throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED); + } + return organId; + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/PayHandlerFactory.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/PayHandlerFactory.java new file mode 100644 index 000000000..4fd32fdf6 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/PayHandlerFactory.java @@ -0,0 +1,52 @@ +package com.cf.imes.module.system.service.pay.factory; + +import cn.hutool.core.util.ObjectUtil; +import com.cf.imes.framework.common.exception.PayException; +import com.cf.imes.module.system.service.pay.factory.impl.alipay.AlipayPcPayHandler; +import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler; +import com.cf.imes.module.system.service.pay.factory.impl.wechat.WechatNativePayHandler; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_CHANNEL_NOT_SUPPORT; + +/** + * 支付处理器工厂 + * + * @author Gqr + * @since 2025/4/15 15:43 + */ +@Component +public class PayHandlerFactory { + private final ApplicationContext applicationContext; + + @Autowired + public PayHandlerFactory(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + /** + * 根据渠道编码获取对应的支付处理器 + * + * @param channelCode + * @return + */ + public AbstractPayHandler getPayHandler(int channelCode) { + PayChannelCodeEnum payChannelCodeEnum = PayChannelCodeEnum.fromType(channelCode); + if (ObjectUtil.isNull(payChannelCodeEnum)) { + throw new PayException(PAY_CHANNEL_NOT_SUPPORT); + } + switch (payChannelCodeEnum) { + // 支付宝电脑支付 + case ALIPAY_PC: + return applicationContext.getBean(AlipayPcPayHandler.class); + // 微信native支付 + case WECHAT_NATIVE: + return applicationContext.getBean(WechatNativePayHandler.class); + default: + throw new PayException(PAY_CHANNEL_NOT_SUPPORT); + } + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/AbstractPayHandler.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/AbstractPayHandler.java new file mode 100644 index 000000000..6eb3c0d53 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/AbstractPayHandler.java @@ -0,0 +1,40 @@ +package com.cf.imes.module.system.service.pay.factory.impl; + +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; + +import java.util.Map; + +/** + * 支付处理器抽象类 + * + * @author Gqr + * @since 2025/4/15 15:47 + */ +public abstract class AbstractPayHandler { + + /** + * 统一支付 + * + * @param reqDTO + * @return + */ + public abstract PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable; + + /** + * 查询支付订单 + * + * @param outTradeNo + */ + public abstract PayOrderRespDTO queryPayOrder(String outTradeNo) throws Throwable; + + + /** + * 解析支付回调数据 + * + * @param params + * @param body + * @return + */ + public abstract PayOrderRespDTO doParseOrderNotify(Map params, String body) throws Throwable; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/alipay/AlipayPcPayHandler.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/alipay/AlipayPcPayHandler.java new file mode 100644 index 000000000..bc0fbb112 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/alipay/AlipayPcPayHandler.java @@ -0,0 +1,151 @@ +package com.cf.imes.module.system.service.pay.factory.impl.alipay; + +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import com.alipay.api.AlipayApiException; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.AlipayTradePagePayModel; +import com.alipay.api.domain.AlipayTradeQueryModel; +import com.alipay.api.internal.util.AlipaySignature; +import com.alipay.api.request.AlipayTradePagePayRequest; +import com.alipay.api.request.AlipayTradeQueryRequest; +import com.alipay.api.response.AlipayTradePagePayResponse; +import com.alipay.api.response.AlipayTradeQueryResponse; +import com.cf.imes.framework.common.util.object.ObjectUtils; +import com.cf.imes.framework.pay.config.ChenfengAlipayConfig; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import com.cf.imes.framework.pay.core.enums.order.PayOrderStatusRespEnum; +import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER; + +/** + * 支付宝电脑支付处理器 + * + * @author Gqr + * @since 2025/4/15 15:46 + */ +@Slf4j +@Service("alipayPcPayHandler") +public class AlipayPcPayHandler extends AbstractPayHandler { + + @Resource + private ChenfengAlipayConfig alipayConfig; + + @Override + public PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable { + // 1.1 构建 AlipayTradePagePayModel 请求 + AlipayTradePagePayModel model = new AlipayTradePagePayModel(); + // ① 通用的参数 + String outTradeNo = reqDTO.getOutTradeNo(); + model.setOutTradeNo(outTradeNo); + model.setSubject(reqDTO.getSubject()); + model.setBody(reqDTO.getSubject()); + model.setTotalAmount(String.valueOf(reqDTO.getPrice())); + model.setTimeExpire(LocalDateTimeUtil.format(reqDTO.getExpireTime(), NORM_DATETIME_FORMATTER)); + model.setProductCode(alipayConfig.getProductCode()); + model.setQrPayMode(alipayConfig.getQrPayMode()); + + // 1.2 构建 AlipayTradePagePayRequest 请求 + AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); + request.setBizModel(model); + request.setNotifyUrl(reqDTO.getNotifyUrl()); + + // 2.1 执行请求 + DefaultAlipayClient client = new DefaultAlipayClient(alipayConfig); + AlipayTradePagePayResponse response = client.pageExecute(request, Method.GET.name()); + + // 2.2 处理结果 + if (!response.isSuccess()) { + Assert.isFalse(response.isSuccess()); + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + return PayOrderRespDTO.waitingOf(null, response.getBody(), + outTradeNo, response); + } + + @Override + public PayOrderRespDTO queryPayOrder(String outTradeNo) throws Throwable { + // 1.1 构建 AlipayTradeRefundModel 请求 + AlipayTradeQueryModel model = new AlipayTradeQueryModel(); + model.setOutTradeNo(outTradeNo); + // 1.2 构建 AlipayTradeQueryRequest 请求 + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + request.setBizModel(model); + + DefaultAlipayClient client = new DefaultAlipayClient(alipayConfig); + AlipayTradeQueryResponse response = client.execute(request); + if (!response.isSuccess()) { // 不成功,例如说订单不存在 + return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(), + outTradeNo, response); + } + // 2.2 解析订单的状态 + Integer status = parseStatus(response.getTradeStatus()); + Assert.notNull(status, () -> { + throw new IllegalArgumentException(CharSequenceUtil.format("body({}) 的 trade_status 不正确", response.getBody())); + }); + return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()), + outTradeNo, response); + } + + @Override + public PayOrderRespDTO doParseOrderNotify(Map params, String body) throws Throwable { + // 1. 校验回调数据 + verifyNotifyData(params); + + // 2. 解析订单的状态 + // 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂 + Map bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8); + Integer status = parseStatus(bodyObj.get("trade_status")); + + Assert.notNull(status, (Supplier) () -> { + throw new IllegalArgumentException(CharSequenceUtil.format("body({}) 的 trade_status 不正确", body)); + }); + return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")), + bodyObj.get("out_trade_no"), body); + } + + /** + * 校验回调数据 + * + * @param params 回调参数 + * @throws Throwable 验签失败时抛出异常 + */ + protected void verifyNotifyData(Map params) throws AlipayApiException { + boolean verify = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), + StandardCharsets.UTF_8.name(), alipayConfig.getSignType()); + Assert.isTrue(verify, "验签结果不通过"); + } + + /** + * 解析查询状态 + * + * @param tradeStatus + * @return + */ + private static Integer parseStatus(String tradeStatus) { + return Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING.getStatus() + : ObjectUtils.equalsAny(tradeStatus, "TRADE_FINISHED", "TRADE_SUCCESS") ? PayOrderStatusRespEnum.SUCCESS.getStatus() + : Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED.getStatus() : null; + } + + protected LocalDateTime parseTime(String str) { + return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER); + } + +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java new file mode 100644 index 000000000..9f45ee3f4 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java @@ -0,0 +1,35 @@ +package com.cf.imes.module.system.service.pay.factory.impl.wechat; + +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; +import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * 微信native支付处理器 + * + * @author Gqr + * @since 2025/4/15 15:46 + */ +@Service("wechatNativePayHandler") +@Slf4j +public class WechatNativePayHandler extends AbstractPayHandler { + + @Override + public PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) { + return null; + } + + @Override + public PayOrderRespDTO queryPayOrder(String outTradeNo) { + return null; + } + + @Override + public PayOrderRespDTO doParseOrderNotify(Map params, String body) { + return null; + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/validation/pay/PayChannelCodeValid.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/validation/pay/PayChannelCodeValid.java new file mode 100644 index 000000000..c07fc1cff --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/validation/pay/PayChannelCodeValid.java @@ -0,0 +1,30 @@ +package com.cf.imes.module.system.validation.pay; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 支付渠道编码校验注解 + * @author Gqr + * @since 2025/4/18 10:21 + */ +@Target({ + ElementType.FIELD, +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint( + validatedBy = {PayChannelCodeValidator.class} +) +public @interface PayChannelCodeValid { + String message() default "不支持的支付渠道,0:支付宝、1:微信"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/validation/pay/PayChannelCodeValidator.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/validation/pay/PayChannelCodeValidator.java new file mode 100644 index 000000000..7b7845b59 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/validation/pay/PayChannelCodeValidator.java @@ -0,0 +1,28 @@ +package com.cf.imes.module.system.validation.pay; + +import cn.hutool.core.util.ObjectUtil; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 支付渠道编码校验器 + * + * @author Gqr + * @since 2025/4/18 10:22 + */ +public class PayChannelCodeValidator implements ConstraintValidator { + @Override + public boolean isValid(Integer value, ConstraintValidatorContext context) { + if (ObjectUtil.isNull(value)) { + return true; + } + for (PayChannelCodeEnum codeEnum : PayChannelCodeEnum.values()) { + if (ObjectUtil.equal(codeEnum.getCode(), value)) { + return true; + } + } + return false; + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml index e43534b45..b9a09a66b 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml @@ -237,4 +237,11 @@ chenfeng: gray: default-version: 1.0.0 version: 1.0.0 + pay: + alipay: + serverUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do + appId: 8wb0qYthTUqf9290UJfLejTcOA5CU1wiAhmpLbXmwDCCt+ZakcBPBK1a88E= + privateKey: H/XEvo3RBcn9PFtuV6R94kMDyLkhAyVP0TrY6LNZPDKSpt6m47265Q5wZJzlcZ2FO1ChVaqd4SNQQuQiSkTwzS3dlHtJuZO4SDzSzwDfCQCgAbGkMNjJyMue9WOjtumxC19vDZVQvmQHP+iFis/psw5MMppp+GwtyVGm64kwhKAlUvbSOJWN38H3z0i67KdcRC07tdoD5NTh5vWQOljRK52ISHlxssaK8+oC9rTSsI6mvkNL1OH/MktfLfmWEfZTX8tuSIrLkWikU9NFfXjD+IWrkC7qRIxdDeWHwCHTU86KSkYteRkMaltLmvWXBYycieM18iRIf2QvT6/gymkT8qosvderqKS0wz5gCIq9LVEZVdYsNKcXAZvN56lVlbb3zsHIFCtZBTfwSHJ5xy3r4Xn3kor8QqeCMk+ACZoxTaORxJ/phPEltCgIEVhAXCMdRmpJKd11NsjAHAP0P633MOhl/i2fzeagJWhV1wk1S8N0m4hfvchn//JEjxsqU90S3fuQokvj7KnDfXUsu9RgOAySAyZbkuMM7q/s7ZPNd0y41WmBoK3gVesu3qKfNzeap9vRD/N8ccyiRG1HwhH3NJhJ8Uo158KNFlJydFsq1Lllm0MsokeJEwEQuVUX07RfM9sPbdFfluXLTP1Xi817lKO8duIqwTmHcesv9Zlb2DX9prMtuTWB/CokboGhXh1L1gAtPXMCIHcjNpz+ACIOhxdQEP5hW9zRWnhF6LMc9lReUPnkKj7Lx08FLTsX8O00gvzX7plR8UyrMJc7a7MMRjN98L1XBKDzHg3gonIJXdGJxJP1j0elyILjr6DUDB8H4fYVmndm9FcuCMTXx84poIK39BAey5PJPvFTm1l8l6vw3gXjajffLhgdqCqn60nwG20464FvZusdMzmx/7s5HPB5wGfCNQZ0J+Q5FctITR92juHTUe6BsBbo7ZlGkWBlJByGy0kw4m81jCFzQpymTmpWaxCi56ysHnMXRnbeMyPe7v/6pHvNqwMLOBJzKBZi72I3o0gEuBnxR87F4r1GAVljlmSthxYFqWe4Jt9zG+dnUrscWJ+/pvGaRz40exLl8ahywPNX63sm9uXqt96PAqosOB1Il9vwY31xa+OeSOp4gT5thKKbbUtTPQIu92vp82HdFCTLLwDYOa3NQPrvxZjWH8cR6sYM7D1AUizzHNc6WfiU1YutDOHQVev6U9RKqwsZKgj94ySa1bH+24CiglXojLyAQ1uU99Hkth3JwCX2hPpCyNHJ34mhsMoe0XZZRrpzmtxRBkhVHtyF44pVekCH/SU3qZkeNuk5ETk+13GpZ7vhbmE9dBpLR4LsiPEfGJHqNH8D624cEKbcXcAL4tWfYiBqQAHlqnnd3dLHLawaAbXGr6xM0/hXVT/Z9Cb1Yh//pPtGnuHPZVCKSzdL14NLDlhJ3IDDYcmDW8pjbCRXNsfi75eZgNJwju2A6+I7j3Ovl0ma8kHrA73SAy2e8ldx25t7kvuw3s/kcYLCuGo9d4Fq0RpTk3YzF7XlevoHxrfXjxf/tGoZCrttURFjaWVtNnOmo2He894V3mSpl3IuaPwvo2VrPPlSFQ3aNkK0/Et2Gmt7OWGRHWgzPKWH378zMxPMo5cYcktmKXvOBvGyrMrHbKEl1lMMM/cAZh6pMdtcqU5J3kCKUbickAkf10KgULzqenPku+7rFB34Uv0Emw5s+l42bW9pcia58FyY+3km6Fl3dkPe2nm7YRvWSfisCAyCi/CHqxHecgchCParCJ9EcGoMADQfzkfsJInIuOT5c8IQXYzwqpSpHMk9n5JjqcyfCNPWEO1/YQOYTExkeDp4SENYIf6TZxvPQCb5cOnYzZcct2YNSrPzKahmzoTwpYmTxxMYuunFlWmyineV/Y+1sGoQ/Lzk+M83uf3UCHG9Pbsp5gFj9t21U95Yukw4Qs45fG8bVPPYfU97K/DrWDd4tfroFUyResqAP2HsXKNd0k+59rpBzEg2TrJH0XlU9zBdU0Oe93HSC/0TggZrKtd717uQXKqo5hJ65lV0JqbZInl92qK2t6BqZE/sKj4hMdeHnAebxROfLOojLFtJB5se6Dn9XnG+sf/cExSMmJqgDIGsOyGt7aA1dV0tDbzgio2V6jjOE2U9kx4luHuaTd2aWJ0oIAuWo6v2U3kMcLH8z+KqtnWAgGS0mEgpr7iz27w= + alipayPublicKey: KbUGE7zsPBKWPx0/zMcAdLRldKrcgy2LRiOrqOBm7y4iPJLXTYtXFJT15p2JMQiyOM8mqiYQR0OEDwfrKxDgHXLtIut/e0EAk9bZwZoLrqVe0pN1huseRC38zEglbWil3yz+tZfHZuAaEq2EzvWJltX/tRKOzNBqAT9l+UvnqNedgCFB9MY2EErqJjbn/at3gYCkV2idt31ghF0z8SEQ5h9ztNYtmTvk8c8ipi0/hIjX8XryN6mlOvGQfyZ4rWrBWBuZ6iD/SyuThwxKmFYU4dhHbHVpBrva5nJWU43CP7RVaJm649nNJnce+Ppj2Tv8+ugSLe5poZWo2zmGe3Xn5WmN6OTZyRI1pnmn2/hGxtOMmTZJX7Gh0pI4v3wmU0gOd/gbRlqlvN+u12T92nyH8MQrHdA7UXuttAtolYUa/Rli/20qHX69QtOtvfZk8mNvDSj9dDvODseGcrFHm07jyfxpaIkQDeN75cQ3NwNgDQEW8urQszaU1IJ7hc+20tt0Z3td4+7MfiVPpGfVxsGE66gwLgpB8wsxEhyBTaBr7osMLqOv + notifyUrl: VvJ5DOVuGOBw39j7tyrvsEEtLSseVM8T3u9kqsWKago+iI5ao8JzoggmeSe+N9LrsYxutK4fkGpmr4jOGJ3JTQFZ7YBXLxf2oLIXOZH9O27EnDtm debug: false