mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-12 21:02:08 +08:00
新增支付接口:支持支付宝pc沙箱支付流程、新增微信native预留接口
This commit is contained in:
+63
@@ -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;
|
||||
}
|
||||
}
|
||||
+70
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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
|
||||
|
||||
+14
@@ -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 {
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
// ========== 拓展参数 ==========
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
-17
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+16
@@ -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,违反唯一约束异常
|
||||
*
|
||||
|
||||
+25
-16
@@ -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, "当前赠送金活动不存在");
|
||||
|
||||
|
||||
+26
-75
@@ -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<String> 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<PayOrderRespDTO> 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<Boolean> 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<String, String> 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";
|
||||
}
|
||||
}
|
||||
|
||||
+45
@@ -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;
|
||||
}
|
||||
+95
@@ -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;
|
||||
}
|
||||
+15
@@ -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<PayOrderDO> {
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
+66
@@ -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());
|
||||
}
|
||||
}
|
||||
+42
@@ -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<String, String> params, String body);
|
||||
}
|
||||
+328
@@ -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<PayOrderDO>()
|
||||
.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<String, String> 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<PayOrderDO>()
|
||||
.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<PayOrderDO>()
|
||||
.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<PayOrderDO>()
|
||||
.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<PayOrderDO>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
+52
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -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<String, String> params, String body) throws Throwable;
|
||||
}
|
||||
+151
@@ -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<String, String> params, String body) throws Throwable {
|
||||
// 1. 校验回调数据
|
||||
verifyNotifyData(params);
|
||||
|
||||
// 2. 解析订单的状态
|
||||
// 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂
|
||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||
Integer status = parseStatus(bodyObj.get("trade_status"));
|
||||
|
||||
Assert.notNull(status, (Supplier<Throwable>) () -> {
|
||||
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<String, String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -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<String, String> params, String body) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+30
@@ -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<? extends Payload>[] payload() default {};
|
||||
}
|
||||
+28
@@ -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<PayChannelCodeValid, Integer> {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user