ref 支付上下文, 主支付流程, 支付接口管理

This commit is contained in:
nws
2023-12-22 23:19:36 +08:00
parent e6c7747351
commit 5aa1b015ab
16 changed files with 493 additions and 16 deletions

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
@@ -16,6 +17,7 @@ import java.time.LocalDateTime;
public class PayCommonParam {
/** 客户端ip */
@NotBlank(message = "客户端ip不可为空")
@Schema(description = "客户端ip")
private String clientIp;
@@ -48,11 +50,12 @@ public class PayCommonParam {
/** API版本号 */
@Schema(description = "API版本号")
@NotBlank()
@NotBlank(message = "API版本号必填")
private String version;
/** 请求时间,时间戳转时间 */
@Schema(description = "请求时间,传输时间戳")
@NotNull(message = "请求时间必填")
private LocalDateTime reqTime;
}

View File

@@ -0,0 +1,20 @@
package cn.bootx.platform.daxpay.annotation;
import java.lang.annotation.*;
/**
* 支付接口标识
* @author xxm
* @since 2023/12/22
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PaymentApi {
/**
* 支付编码
*/
String code();
}

View File

@@ -0,0 +1,34 @@
package cn.bootx.platform.daxpay.common.context;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 支付上下文
* @author xxm
* @since 2023/12/22
*/
@Getter
@Setter
@Accessors(chain = true)
public class PaymentContext {
/** 当前支付接口编码 */
private String apiCode;
/** 是否开启回调通知 */
private boolean notice;
/** 请求参数是否签名 */
private boolean reqSign;
/** 响应参数是否签名 */
private boolean resSign;
/** 回调信息是否签名 */
private boolean noticeSign;
/** 是否记录请求的信息 */
private boolean record;
}

View File

@@ -0,0 +1,28 @@
package cn.bootx.platform.daxpay.common.filter;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 支付上下文本地过滤器
* @author xxm
* @since 2023/12/22
*/
@Order(value = Integer.MIN_VALUE)
@Component
@RequiredArgsConstructor
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class PaymentContextLocalFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
PaymentContextLocal.clear();
}
}

View File

@@ -0,0 +1,45 @@
package cn.bootx.platform.daxpay.common.local;
import cn.bootx.platform.daxpay.common.context.PaymentContext;
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.experimental.UtilityClass;
import java.util.Objects;
/**
* 支付上下文线程变量
* @author xxm
* @since 2023/12/22
*/
@UtilityClass
public final class PaymentContextLocal {
private static final ThreadLocal<PaymentContext> THREAD_LOCAL = new TransmittableThreadLocal<>();
/**
* 设置
*/
public static void set(PaymentContext paymentContext){
THREAD_LOCAL.set(paymentContext);
}
/**
* 获取
*/
public PaymentContext get(){
PaymentContext paymentContext = THREAD_LOCAL.get();
if (Objects.isNull(paymentContext)){
paymentContext = new PaymentContext();
THREAD_LOCAL.set(paymentContext);
}
return paymentContext;
}
/**
* 清除
*/
public static void clear() {
THREAD_LOCAL.remove();
}
}

View File

@@ -0,0 +1,18 @@
package cn.bootx.platform.daxpay.core.openapi.convert;
import cn.bootx.platform.daxpay.core.openapi.entity.PayOpenApiInfo;
import cn.bootx.platform.daxpay.param.openapi.PayOpenApiInfoParam;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 开放接口信息转换
* @author xxm
* @since 2023/12/22
*/
@Mapper
public interface PayOpenApiInfoConvert {
PayOpenApiInfoConvert CONVERT = Mappers.getMapper(PayOpenApiInfoConvert.class);
PayOpenApiInfo convert(PayOpenApiInfoParam in);
}

View File

@@ -0,0 +1,35 @@
package cn.bootx.platform.daxpay.core.openapi.dao;
import cn.bootx.platform.common.mybatisplus.impl.BaseManager;
import cn.bootx.platform.daxpay.core.openapi.entity.PayOpenApiInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* 支付开放接口管理
* @author xxm
* @since 2023/12/22
*/
@Slf4j
@Repository
@RequiredArgsConstructor
public class PayOpenApiInfoManager extends BaseManager<PayOpenApiInfoMapper, PayOpenApiInfo> {
/**
* 根据code查询
*/
public Optional<PayOpenApiInfo> findByCode(String code){
return findByField(PayOpenApiInfo::getCode,code);
}
/**
* 根据api查询
*/
public Optional<PayOpenApiInfo> findByApi(String api){
return findByField(PayOpenApiInfo::getApi,api);
}
}

View File

@@ -0,0 +1,14 @@
package cn.bootx.platform.daxpay.core.openapi.dao;
import cn.bootx.platform.daxpay.core.openapi.entity.PayOpenApiInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 开放接口信息
* @author xxm
* @since 2023/12/22
*/
@Mapper
public interface PayOpenApiInfoMapper extends BaseMapper<PayOpenApiInfo> {
}

View File

@@ -0,0 +1,21 @@
package cn.bootx.platform.daxpay.core.openapi.entity;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付接口调用记录
* @author xxm
* @since 2023/12/22
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@DbTable(comment = "支付接口调用记录")
@TableName("pay_api_call_record")
public class PayApiCallRecord extends MpCreateEntity {
}

View File

@@ -0,0 +1,68 @@
package cn.bootx.platform.daxpay.core.openapi.entity;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.core.openapi.convert.PayOpenApiInfoConvert;
import cn.bootx.platform.daxpay.param.openapi.PayOpenApiInfoParam;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付开放接口管理
* @author xxm
* @since 2023/12/22
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@DbTable(comment = "支付开放接口管理")
@TableName("pay_open_api_info")
public class PayOpenApiInfo extends MpBaseEntity {
@DbColumn(comment = "编码")
@TableField(updateStrategy = FieldStrategy.NEVER)
private String code;
@DbColumn(comment = "接口地址")
@TableField(updateStrategy = FieldStrategy.NEVER)
private String api;
@DbColumn(comment = "名称")
@TableField(updateStrategy = FieldStrategy.NEVER)
private String name;
@DbColumn(comment = "是否启用")
private boolean enable;
@DbColumn(comment = "是否开启回调通知")
private boolean notice;
@DbColumn(comment = "默认回调地址")
private String noticeUrl;
@DbColumn(comment = "请求参数是否签名")
private boolean reqSign;
@DbColumn(comment = "响应参数是否签名")
private boolean resSign;
@DbColumn(comment = "回调信息是否签名")
private boolean noticeSign;
@DbColumn(comment = "是否记录请求的信息")
private boolean record;
/**
* 初始化
*/
public static PayOpenApiInfo init(PayOpenApiInfoParam param){
return PayOpenApiInfoConvert.CONVERT.convert(param);
}
}

View File

@@ -0,0 +1,63 @@
package cn.bootx.platform.daxpay.core.openapi.handler;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.annotation.PaymentApi;
import cn.bootx.platform.daxpay.common.context.PaymentContext;
import cn.bootx.platform.daxpay.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.core.openapi.dao.PayOpenApiInfoManager;
import cn.bootx.platform.daxpay.core.openapi.entity.PayOpenApiInfo;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.starter.auth.service.RouterCheck;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import java.util.Objects;
/**
* 支付接口请求检查器, 用于判断请求的支付接口是否允许被外部访问.
* 同时如果检查的结果是放行, 同时初始化支付上下文线程对象
* @author xxm
* @since 2023/12/22
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class PayOpenApiCheckHandler implements RouterCheck {
private final PayOpenApiInfoManager openApiInfoManager;
@Override
public int sortNo() {
return -1000;
}
@Override
public boolean check(Object handler) {
// 如果请求的接口未启用
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
PaymentApi ignoreAuth = handlerMethod.getMethodAnnotation(PaymentApi.class);
if (Objects.isNull(ignoreAuth)){
return false;
}
String code = ignoreAuth.code();
PayOpenApiInfo openApiInfo = openApiInfoManager.findByCode(code)
.orElseThrow(() -> new DataNotExistException("未找到接口信息"));
if (!openApiInfo.isEnable()){
throw new PayFailureException("该接口权限未开放");
}
// 初始化支付上下文
PaymentContext paymentContext = new PaymentContext()
.setApiCode(code)
.setReqSign(openApiInfo.isReqSign())
.setResSign(openApiInfo.isResSign())
.setNotice(openApiInfo.isNotice())
.setNoticeSign(openApiInfo.isNoticeSign())
.setRecord(openApiInfo.isRecord());
PaymentContextLocal.set(paymentContext);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,42 @@
package cn.bootx.platform.daxpay.core.openapi.service;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.daxpay.core.openapi.dao.PayOpenApiInfoManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 开放接口信息
* @author xxm
* @since 2023/12/22
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PayOpenApiInfoService {
private final PayOpenApiInfoManager openApiInfoManager;
/**
* 编辑
*/
public void update(){
}
/**
* 分页
*/
public void page(PageParam pageParam){
}
/**
*
*/
public void findById(Long id){
}
}

View File

@@ -4,6 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderRefundableInfo;
import cn.bootx.platform.daxpay.core.payment.pay.local.AsyncPayInfo;
import cn.bootx.platform.daxpay.core.payment.pay.local.AsyncPayInfoLocal;
@@ -58,6 +59,27 @@ public class PaymentBuilder {
.setRefundableBalance(sumAmount);
}
/**
* 构建支付订单的额外信息
* @param payParam 支付参数
* @param paymentId 支付订单id
*/
public PayOrderExtra buildPayOrderExtra(PayParam payParam, Long paymentId) {
PayOrderExtra payOrderExtra = new PayOrderExtra()
.setClientIp(payParam.getClientIp())
.setDescription(payParam.getDescription())
.setNotReturn(payParam.isNotReturn())
.setReturnUrl(payParam.getReturnUrl())
.setNotNotify(payParam.isNotNotify())
.setNotifyUrl(payParam.getNotifyUrl())
.setSign(payParam.getSign())
.setSignType(payParam.getSignType())
.setSignType(payParam.getSign())
.setReqTime(payParam.getReqTime());
payOrderExtra.setId(paymentId);
return payOrderExtra;
}
/**
* 构建订单关联通道信息
*/

View File

@@ -8,6 +8,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 支付订单扩展信息
* @author xxm
@@ -27,6 +29,37 @@ public class PayOrderExtra extends MpBaseEntity {
@DbColumn(comment = "描述")
private String description;
@DbColumn(comment = "是否不进行同步通知的跳转")
private boolean notReturn;
/** 同步通知URL */
@DbColumn(comment = "同步通知URL")
private String returnUrl;
/** 是否不启用异步通知 */
@DbColumn(comment = "是否不启用异步通知")
private boolean notNotify;
/** 异步通知地址 */
@DbColumn(comment = "异步通知地址")
private String notifyUrl;
/** 签名类型 */
@DbColumn(comment = "签名类型")
private String signType;
/** 签名 */
@DbColumn(comment = "签名")
private String sign;
/** API版本号 */
@DbColumn(comment = "API版本号")
private String version;
/** 请求时间,时间戳转时间 */
@DbColumn(comment = "请求时间,传输时间戳")
private LocalDateTime reqTime;
/** 错误码 */
@DbColumn(comment = "错误码")
private String errorCode;

View File

@@ -61,6 +61,9 @@ public class PayService {
public PayResult pay(PayParam payParam) {
// 检验参数
ValidationUtil.validateParam(payParam);
// 验签
// 异步支付方式检查
PayWayUtil.validationAsyncPayMode(payParam);
// 获取并校验支付状态
@@ -80,23 +83,20 @@ public class PayService {
* 发起的第一次支付请求(同步/异步)
*/
private PayResult payFirst(PayParam payParam, PayOrder payOrder) {
// 0. 已经发起过支付情况直接返回支付结果
// 1. 已经发起过支付情况直接返回支付结果
if (Objects.nonNull(payOrder)) {
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
// 1. 价格检测
// 2. 价格检测
PayWayUtil.validationAmount(payParam.getPayWays());
// 2. 创建支付相关的记录并返回支付订单对象
// 3. 创建支付相关的记录并返回支付订单对象
payOrder = this.createPayOrder(payParam);
// 3. 调用支付方法进行发起支付
// 4. 调用支付方法进行发起支付
this.payFirstMethod(payParam, payOrder);
// 4. 获取支付记录信息
// payOrder = payOrderService.findById(payOrder.getId()).orElseThrow(PayNotExistedException::new);
// 5. 返回支付结果
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
@@ -166,10 +166,7 @@ public class PayService {
payOrderService.updateById(paymentObj);
});
// 5. 获取支付记录信息
// payOrder = payOrderService.findById(payOrder.getId()).orElseThrow(PayNotExistedException::new);
// 6. 组装返回参数
// 5. 组装返回参数
return PaymentBuilder.buildPayResultByPayOrder(payOrder);
}
@@ -220,10 +217,7 @@ public class PayService {
PayOrder payOrder = PaymentBuilder.buildPayOrder(payParam);
payOrderService.saveOder(payOrder);
// 构建支付订单扩展表并保存
PayOrderExtra payOrderExtra = new PayOrderExtra()
.setClientIp(payParam.getClientIp())
.setDescription(payParam.getDescription());
payOrderExtra.setId(payOrder.getId());
PayOrderExtra payOrderExtra = PaymentBuilder.buildPayOrderExtra(payParam, payOrder.getId());
payOrderExtraManager.save(payOrderExtra);
// 构建支付通道表并保存
List<PayOrderChannel> payOrderChannels = PaymentBuilder.buildPayChannel(payParam.getPayWays())

View File

@@ -0,0 +1,37 @@
package cn.bootx.platform.daxpay.param.openapi;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付开放接口管理
* @author xxm
* @since 2023/12/22
*/
@Data
@Accessors(chain = true)
@Schema(title = "支付开放接口管理")
public class PayOpenApiInfoParam {
@Schema(description = "是否启用")
private boolean enable;
@Schema(description = "是否开启回调通知")
private boolean notice;
@Schema(description = "默认回调地址")
private String noticeUrl;
@Schema(description = "请求参数是否签名")
private boolean reqSign;
@Schema(description = "响应参数是否签名")
private boolean resSign;
@Schema(description = "回调信息是否签名")
private boolean noticeSign;
@Schema(description = "是否记录请求的信息")
private boolean record;
}