From c842447d2fda01aca5c3d15827f3e0a26dcabb87 Mon Sep 17 00:00:00 2001 From: DaxPay Date: Tue, 26 Nov 2024 20:26:54 +0800 Subject: [PATCH] =?UTF-8?q?(cashfeatier):=20=E6=B7=BB=E5=8A=A0=E6=94=B6?= =?UTF-8?q?=E9=93=B6=E5=8F=B0=E6=94=AF=E4=BB=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增收银台类型枚举和相关参数类 - 实现收银台订单创建和支付逻辑 - 添加收银台配置管理和查询功能- 优化支付辅助服务,增加订单状态检查和限额验证 --- .../daxpay/core/enums/CheckoutTypeEnum.java | 34 ++++ .../core/param/assist/QueryAuthParam.java | 23 --- .../core/param/cashier/CheckoutParam.java | 92 +++++++++++ .../core/param/cashier/CheckoutPayParam.java | 28 ++++ .../result/cashier/CheckoutInfoResult.java | 21 +++ .../result/cashier/CheckoutUrlResult.java | 20 +++ .../controller/unipay/CheckoutController.java | 48 ++++++ .../checkout/CheckoutItemConfigManager.java | 18 +++ .../checkout/CheckoutItemConfigMapper.java | 14 ++ .../config/checkout/CheckoutGroupConfig.java | 18 ++- .../config/checkout/CheckoutItemConfig.java | 30 +++- .../service/result/order/pay/PayOrderVo.java | 2 +- .../cashier/CheckoutAssistService.java | 151 ++++++++++++++++++ .../service/cashier/CheckoutService.java | 105 ++++++++++++ .../service/config/CheckoutConfigService.java | 26 +++ .../service/trade/pay/PayAssistService.java | 1 + 16 files changed, 605 insertions(+), 26 deletions(-) create mode 100644 daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/enums/CheckoutTypeEnum.java delete mode 100644 daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/assist/QueryAuthParam.java create mode 100644 daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutParam.java create mode 100644 daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutPayParam.java create mode 100644 daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutInfoResult.java create mode 100644 daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutUrlResult.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/controller/unipay/CheckoutController.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigManager.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigMapper.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutAssistService.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutService.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/config/CheckoutConfigService.java diff --git a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/enums/CheckoutTypeEnum.java b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/enums/CheckoutTypeEnum.java new file mode 100644 index 00000000..9cfd3250 --- /dev/null +++ b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/enums/CheckoutTypeEnum.java @@ -0,0 +1,34 @@ +package org.dromara.daxpay.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.dromara.daxpay.core.exception.ConfigNotExistException; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 收银台类型 + * @author xxm + * @since 2024/11/26 + */ +@Getter +@AllArgsConstructor +public enum CheckoutTypeEnum { + + H5("h5", "H5"), + PC("pc", "PC"), + MINI_APP("app", "APP"), + ; + + private final String code; + private final String name; + + public static CheckoutTypeEnum findBuyCode(String code){ + return Arrays.stream(CheckoutTypeEnum.values()) + .filter(value -> Objects.equals(value.getCode(), code)) + .findFirst() + .orElseThrow(() -> new ConfigNotExistException("不支持的收银台类型")); + + } +} diff --git a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/assist/QueryAuthParam.java b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/assist/QueryAuthParam.java deleted file mode 100644 index f7b932c6..00000000 --- a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/assist/QueryAuthParam.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.dromara.daxpay.core.param.assist; - -import org.dromara.daxpay.core.param.PaymentCommonParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.experimental.Accessors; - -/** - * 查询OpenId参数 - * @author xxm - * @since 2024/9/19 - */ -@EqualsAndHashCode(callSuper = true) -@Data -@Accessors(chain = true) -@Schema(title = "查询OpenId参数") -public class QueryAuthParam extends PaymentCommonParam { - - @Schema(description = "标识码") - private String queryCode; - -} diff --git a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutParam.java b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutParam.java new file mode 100644 index 00000000..fb4933d0 --- /dev/null +++ b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutParam.java @@ -0,0 +1,92 @@ +package org.dromara.daxpay.core.param.cashier; + +import cn.hutool.core.date.DatePattern; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.dromara.daxpay.core.enums.CheckoutTypeEnum; +import org.dromara.daxpay.core.param.PaymentCommonParam; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 收银台创建参数 + * @author xxm + * @since 2024/11/26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "收银台创建参数") +public class CheckoutParam extends PaymentCommonParam { + + /** + * 收银台类型 + * @see CheckoutTypeEnum + */ + @NotBlank(message = "收银台类型不可为空") + private String checkoutType; + + /** 商户订单号 */ + @Schema(description = "商户订单号") + @NotBlank(message = "商户订单号不可为空") + @Size(max = 100, message = "商户订单号不可超过100位") + private String bizOrderNo; + + /** 支付标题 */ + @Schema(description = "支付标题") + @NotBlank(message = "支付标题不可为空") + @Size(max = 100, message = "支付标题不可超过100位") + private String title; + + /** 支付描述 */ + @Schema(description = "支付描述") + @Size(max = 500, message = "支付描述不可超过500位") + private String description; + + /** 是否开启分账 */ + @Schema(description = "是否开启分账") + private Boolean allocation; + + /** 自动分账 */ + @Schema(description = "自动分账") + private Boolean autoAllocation; + + /** 过期时间 */ + @Schema(description = "过期时间") + @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + private LocalDateTime expiredTime; + + /** 支付金额 */ + @Schema(description = "支付金额") + @NotNull(message = "支付金额不可为空") + @DecimalMin(value = "0.01", message = "支付金额不可小于0.01元") + @Digits(integer = 8, fraction = 2, message = "支付金额精度到分, 且要小于一亿元") + private BigDecimal amount; + + /** + * 支付扩展参数 + */ + @Schema(description = "支付扩展参数") + @Size(max = 2048, message = "支付扩展参数不可超过2048位") + private String extraParam; + + /** 商户扩展参数,回调时会原样返回 */ + @Schema(description = "商户扩展参数") + @Size(max = 500, message = "商户扩展参数不可超过500位") + private String attach; + + /** 同步跳转地址, 支付完毕后用户浏览器返回到该地址, 不传输跳转到默认地址 */ + @Schema(description = "同步通知URL") + @Size(max = 200, message = "同步通知URL不可超过200位") + private String returnUrl; + + /** 异步通知地址 */ + @Schema(description = "异步通知地址") + @Size(max = 200, message = "异步通知地址不可超过200位") + private String notifyUrl; +} diff --git a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutPayParam.java b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutPayParam.java new file mode 100644 index 00000000..655032ac --- /dev/null +++ b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/param/cashier/CheckoutPayParam.java @@ -0,0 +1,28 @@ +package org.dromara.daxpay.core.param.cashier; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 收银台支付参数 + * @author xxm + * @since 2024/11/26 + */ +@Data +@Accessors(chain = true) +@Schema(title = "收银台支付参数") +public class CheckoutPayParam { + + @Schema(description = "要支付的订单号") + private String bizOrderNo; + + @Schema(description = "支付配置项ID") + private Long itemId; + + @Schema(description = "唯一标识") + private String openId; + + @Schema(description = "描述") + private String description; +} diff --git a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutInfoResult.java b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutInfoResult.java new file mode 100644 index 00000000..026e27a4 --- /dev/null +++ b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutInfoResult.java @@ -0,0 +1,21 @@ +package org.dromara.daxpay.core.result.cashier; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 收银台订单结算和配置信息 + * @author xxm + * @since 2024/11/26 + */ +@Data +@Accessors(chain = true) +@Schema(title = "收银台订单结算和配置信息") +public class CheckoutInfoResult { + /** 订单信息 */ + + /** 配置信息 */ + + +} diff --git a/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutUrlResult.java b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutUrlResult.java new file mode 100644 index 00000000..880a6230 --- /dev/null +++ b/daxpay-single/daxpay-single-core/src/main/java/org/dromara/daxpay/core/result/cashier/CheckoutUrlResult.java @@ -0,0 +1,20 @@ +package org.dromara.daxpay.core.result.cashier; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 收银台响应参数 + * @author xxm + * @since 2024/11/26 + */ +@Data +@Accessors(chain = true) +@Schema(title = "收银台响应参数") +public class CheckoutUrlResult { + + @Schema(description = "收银台链接") + private String url; + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/controller/unipay/CheckoutController.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/controller/unipay/CheckoutController.java new file mode 100644 index 00000000..1027ff7c --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/controller/unipay/CheckoutController.java @@ -0,0 +1,48 @@ +package org.dromara.daxpay.service.controller.unipay; + +import cn.bootx.platform.core.rest.Res; +import cn.bootx.platform.core.rest.result.Result; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.dromara.daxpay.core.param.cashier.CheckoutParam; +import org.dromara.daxpay.core.param.cashier.CheckoutPayParam; +import org.dromara.daxpay.core.result.DaxResult; +import org.dromara.daxpay.core.result.cashier.CheckoutUrlResult; +import org.dromara.daxpay.core.util.DaxRes; +import org.dromara.daxpay.service.common.anno.PaymentVerify; +import org.dromara.daxpay.service.service.cashier.CheckoutService; +import org.springframework.web.bind.annotation.*; + +/** + * 收银台服务 + * @author xxm + * @since 2024/11/26 + */ +@Tag(name = "收银台服务") +@RestController +@RequestMapping("/unipay/checkout") +@RequiredArgsConstructor +public class CheckoutController { + private final CheckoutService checkoutService; + + @PaymentVerify + @Operation(summary = "创建一个收银台链接") + @PostMapping("/creat") + public DaxResult creat(@RequestBody CheckoutParam checkoutParam){ + return DaxRes.ok(checkoutService.creat(checkoutParam)); + } + + @Operation(summary = "获取收银台订单信息") + @GetMapping("/info") + public Result getInfo(){ + return Res.ok(); + } + + @Operation(summary = "发起支付") + @PostMapping("/pay") + public Result pay(@RequestBody CheckoutPayParam checkoutParam){ + return Res.ok(); + } + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigManager.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigManager.java new file mode 100644 index 00000000..0779eabc --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigManager.java @@ -0,0 +1,18 @@ +package org.dromara.daxpay.service.dao.config.checkout; + +import cn.bootx.platform.common.mybatisplus.impl.BaseManager; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.daxpay.service.entity.config.checkout.CheckoutItemConfig; +import org.springframework.stereotype.Repository; + +/** + * 收银台配置项 + * @author xxm + * @since 2024/11/26 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class CheckoutItemConfigManager extends BaseManager { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigMapper.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigMapper.java new file mode 100644 index 00000000..3240df3d --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/dao/config/checkout/CheckoutItemConfigMapper.java @@ -0,0 +1,14 @@ +package org.dromara.daxpay.service.dao.config.checkout; + +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.dromara.daxpay.service.entity.config.checkout.CheckoutItemConfig; + +/** + * 收银台配置项 + * @author xxm + * @since 2024/11/26 + */ +@Mapper +public interface CheckoutItemConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutGroupConfig.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutGroupConfig.java index 4cad0e81..0cc19499 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutGroupConfig.java +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutGroupConfig.java @@ -1,9 +1,13 @@ package org.dromara.daxpay.service.entity.config.checkout; +import cn.bootx.platform.common.mybatisplus.function.ToResult; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import org.dromara.daxpay.service.common.entity.MchAppBaseEntity; +import org.dromara.daxpay.service.convert.config.CheckoutGroupConfigConvert; +import org.dromara.daxpay.service.param.config.checkout.CheckoutGroupConfigParam; +import org.dromara.daxpay.service.result.config.checkout.CheckoutGroupConfigResult; /** * 收银台类目配置 @@ -13,7 +17,7 @@ import org.dromara.daxpay.service.common.entity.MchAppBaseEntity; @EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) -public class CheckoutGroupConfig extends MchAppBaseEntity { +public class CheckoutGroupConfig extends MchAppBaseEntity implements ToResult { /** 类型 web/h5/小程序 */ private String type; @@ -23,4 +27,16 @@ public class CheckoutGroupConfig extends MchAppBaseEntity { /** 排序 */ private Double sort; + + public static CheckoutGroupConfig init(CheckoutGroupConfigParam param){ + return CheckoutGroupConfigConvert.CONVERT.toEntity(param); + } + + /** + * 转换 + */ + @Override + public CheckoutGroupConfigResult toResult() { + return CheckoutGroupConfigConvert.CONVERT.toResult(this); + } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutItemConfig.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutItemConfig.java index 2f526495..1c012feb 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutItemConfig.java +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/entity/config/checkout/CheckoutItemConfig.java @@ -1,11 +1,15 @@ package org.dromara.daxpay.service.entity.config.checkout; +import cn.bootx.platform.common.mybatisplus.function.ToResult; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import org.dromara.daxpay.core.enums.ChannelEnum; import org.dromara.daxpay.core.enums.PayMethodEnum; import org.dromara.daxpay.service.common.entity.MchAppBaseEntity; +import org.dromara.daxpay.service.convert.config.CheckoutItemConfigConvert; +import org.dromara.daxpay.service.param.config.checkout.CheckoutItemConfigParam; +import org.dromara.daxpay.service.result.config.checkout.CheckoutItemConfigResult; /** * 收银台配置项 @@ -15,7 +19,7 @@ import org.dromara.daxpay.service.common.entity.MchAppBaseEntity; @EqualsAndHashCode(callSuper = true) @Data @Accessors(chain = true) -public class CheckoutItemConfig extends MchAppBaseEntity { +public class CheckoutItemConfig extends MchAppBaseEntity implements ToResult { /** 类目配置Id */ private Long classifyId; @@ -38,4 +42,28 @@ public class CheckoutItemConfig extends MchAppBaseEntity { /** 是否开启分账 */ private boolean allocation; + /** + * 类型 + * 1. 扫码支付 + * 2. 条码支付 + * 3. 跳转链接 + * 4. 小程序支付 + * 5. 聚合支付 + */ + private String type; + + /** + * 构造 + */ + public static CheckoutItemConfig init(CheckoutItemConfigParam param) { + return CheckoutItemConfigConvert.CONVERT.toEntity(param); + } + + /** + * 转换 + */ + @Override + public CheckoutItemConfigResult toResult() { + return CheckoutItemConfigConvert.CONVERT.toResult(this); + } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/result/order/pay/PayOrderVo.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/result/order/pay/PayOrderVo.java index c7fc6a9b..d25245ef 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/result/order/pay/PayOrderVo.java +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/result/order/pay/PayOrderVo.java @@ -14,7 +14,7 @@ import java.math.BigDecimal; import java.time.LocalDateTime; /** - * 支付订单 + * 支付订单显示对象, 不与 PayOrderResult重名 * @author xxm * @since 2021/2/25 */ diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutAssistService.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutAssistService.java new file mode 100644 index 00000000..bf228d2a --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutAssistService.java @@ -0,0 +1,151 @@ +package org.dromara.daxpay.service.service.cashier; + +import cn.bootx.platform.core.exception.ValidationFailedException; +import cn.bootx.platform.core.util.BigDecimalUtil; +import cn.bootx.platform.core.util.DateTimeUtil; +import cn.bootx.platform.starter.redis.delay.service.DelayJobService; +import cn.hutool.core.bean.BeanUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.daxpay.core.enums.PayAllocStatusEnum; +import org.dromara.daxpay.core.enums.PayRefundStatusEnum; +import org.dromara.daxpay.core.enums.PayStatusEnum; +import org.dromara.daxpay.core.exception.AmountExceedLimitException; +import org.dromara.daxpay.core.exception.TradeStatusErrorException; +import org.dromara.daxpay.core.param.cashier.CheckoutParam; +import org.dromara.daxpay.core.util.PayUtil; +import org.dromara.daxpay.core.util.TradeNoGenerateUtil; +import org.dromara.daxpay.service.code.DaxPayCode; +import org.dromara.daxpay.service.common.context.MchAppLocal; +import org.dromara.daxpay.service.common.local.PaymentContextLocal; +import org.dromara.daxpay.service.dao.order.pay.PayOrderManager; +import org.dromara.daxpay.service.entity.order.pay.PayOrder; +import org.dromara.daxpay.service.service.order.pay.PayOrderQueryService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +/** + * 收银台支撑服务 + * @author xxm + * @since 2024/11/26 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CheckoutAssistService { + private final DelayJobService delayJobService; + private final PayOrderManager payOrderManager; + private final PayOrderQueryService payOrderQueryService; + + /** + * 校验支付状态,支付成功则返回,支付失败则抛出对应的异常 + */ + public PayOrder getOrderAndCheck(String orderNo) { + PayOrder payOrder = payOrderQueryService.findByOrderNo(orderNo).orElse(null); + return getOrderAndCheck(payOrder); + } + + /** + * 校验支付状态,支付成功则返回,支付失败则抛出对应的异常 + */ + public PayOrder getOrderAndCheck(CheckoutParam param) { + // 根据订单查询支付记录 + PayOrder payOrder = payOrderQueryService.findByBizOrderNo(param.getBizOrderNo(), param.getAppId()).orElse(null); + return getOrderAndCheck(payOrder); + }/** + * 校验支付状态,支付成功则返回,支付失败则抛出对应的异常 + */ + public PayOrder getOrderAndCheck(PayOrder payOrder) { + if (Objects.nonNull(payOrder)) { + // 已经支付状态 + if (PayStatusEnum.SUCCESS.getCode() + .equals(payOrder.getStatus())) { + throw new TradeStatusErrorException("已经支付成功,请勿重新支付"); + } + // 支付失败类型状态 + List tradesStatus = List.of( + PayStatusEnum.FAIL.getCode(), + PayStatusEnum.CLOSE.getCode(), + PayStatusEnum.CANCEL.getCode()); + if (tradesStatus.contains(payOrder.getStatus())) { + throw new TradeStatusErrorException("支付失败或已经被关闭"); + } + // 退款类型状态 + if (Objects.equals(payOrder.getRefundStatus(), PayRefundStatusEnum.REFUNDING.getCode())) { + throw new TradeStatusErrorException("该订单处于退款状态"); + } + // 其他状态直接抛出兜底异常 + throw new TradeStatusErrorException("订单不是待支付状态,请重新确认订单状态"); + } + return null; + } + + /** + * 检验订单是否超过限额 + */ + public void validationLimitAmount(CheckoutParam checkoutParam) { + MchAppLocal mchAppInfo = PaymentContextLocal.get() + .getMchAppInfo(); + // 总额校验 + if (BigDecimalUtil.isGreaterThan(checkoutParam.getAmount(),mchAppInfo.getLimitAmount())) { + throw new AmountExceedLimitException("支付金额超过限额"); + } + } + + + /** + * 校验订单超时时间是否正常 + */ + public void validationExpiredTime(CheckoutParam payParam) { + LocalDateTime expiredTime = this.getExpiredTime(payParam); + if (Objects.nonNull(expiredTime) && DateTimeUtil.lt(expiredTime,LocalDateTime.now())) { + throw new ValidationFailedException("支付超时时间设置有误, 请检查!"); + } + } + + + + /** + * 创建支付订单并保存, 返回支付订单 + */ + @Transactional(rollbackFor = Exception.class) + public PayOrder createPayOrder(CheckoutParam checkoutParam) { + // 订单超时时间 + LocalDateTime expiredTime = this.getExpiredTime(checkoutParam); + // 构建支付订单对象 + PayOrder order = new PayOrder(); + BeanUtil.copyProperties(checkoutParam, order); + order.setOrderNo(TradeNoGenerateUtil.pay()) + .setStatus(PayStatusEnum.WAIT.getCode()) + .setRefundStatus(PayRefundStatusEnum.NO_REFUND.getCode()) + .setExpiredTime(expiredTime) + .setRefundableBalance(checkoutParam.getAmount()); + // 如果支持分账, 设置分账状态为待分账 + if (order.getAllocation()) { + order.setAllocStatus(PayAllocStatusEnum.WAITING.getCode()); + } + payOrderManager.save(order); + // 注册支付超时任务 + delayJobService.registerByTransaction(order.getId(), DaxPayCode.Event.MERCHANT_PAY_TIMEOUT, order.getExpiredTime()); + return order; + } + + + /** + * 获取支付订单超时时间 + */ + private LocalDateTime getExpiredTime(CheckoutParam payParam) { + MchAppLocal mchAppLocal = PaymentContextLocal.get().getMchAppInfo(); + // 支付参数传入 + if (Objects.nonNull(payParam.getExpiredTime())) { + return payParam.getExpiredTime(); + } + // 根据商户应用配置计算出时间 + return PayUtil.getPaymentExpiredTime(mchAppLocal.getOrderTimeout()); + } + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutService.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutService.java new file mode 100644 index 00000000..9e1d1960 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/cashier/CheckoutService.java @@ -0,0 +1,105 @@ +package org.dromara.daxpay.service.service.cashier; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.lock.LockInfo; +import com.baomidou.lock.LockTemplate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.daxpay.core.enums.CheckoutTypeEnum; +import org.dromara.daxpay.core.exception.TradeProcessingException; +import org.dromara.daxpay.core.exception.UnsupportedAbilityException; +import org.dromara.daxpay.core.param.cashier.CheckoutParam; +import org.dromara.daxpay.core.result.cashier.CheckoutUrlResult; +import org.dromara.daxpay.service.entity.config.PlatformConfig; +import org.dromara.daxpay.service.entity.order.pay.PayOrder; +import org.dromara.daxpay.service.service.assist.PaymentAssistService; +import org.dromara.daxpay.service.service.config.CheckoutConfigService; +import org.dromara.daxpay.service.service.config.PlatformConfigService; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +/** + * 收银台服务 + * @author xxm + * @since 2024/11/26 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CheckoutService { + private final CheckoutAssistService payAssistService; + private final LockTemplate lockTemplate; + private final PlatformConfigService platformConfigService; + private final PaymentAssistService paymentAssistService; + private final CheckoutConfigService checkoutConfigService; + + /** + * 生成收银台链接 + */ + public CheckoutUrlResult creat(CheckoutParam checkoutParam){ + // 校验支付限额 + payAssistService.validationLimitAmount(checkoutParam); + // 校验超时时间, 不可早于当前 + payAssistService.validationExpiredTime(checkoutParam); + // 获取商户订单号 + String bizOrderNo = checkoutParam.getBizOrderNo(); + // 加锁 + LockInfo lock = lockTemplate.lock("payment:pay:" + bizOrderNo,10000,200); + if (Objects.isNull(lock)){ + log.warn("正在发起调起收银台中,请勿重复操作"); + throw new TradeProcessingException("正在发起调起收银台中,请勿重复操作"); + } + try { + // 查询并检查订单 + PayOrder payOrder = payAssistService.getOrderAndCheck(checkoutParam); + // 订单已经存在直接返回链接, 不存在创建订单后返回链接 + if (Objects.isNull(payOrder)){ + // 执行支付前的保存动作, 保存支付订单和扩展记录 + payOrder = payAssistService.createPayOrder(checkoutParam); + String checkoutUrl = this.getCheckoutUrl(payOrder.getOrderNo(), checkoutParam.getCheckoutType()); + return new CheckoutUrlResult().setUrl(checkoutUrl); + } else { + // 直接返回收银台链接 + String checkoutUrl = this.getCheckoutUrl(payOrder.getOrderNo(), checkoutParam.getCheckoutType()); + return new CheckoutUrlResult().setUrl(checkoutUrl); + } + } finally { + lockTemplate.releaseLock(lock); + } + } + + /** + * 获取收银台链接 + */ + public String getCheckoutUrl(String code, String checkoutType){ + CheckoutTypeEnum checkoutTypeEnum = CheckoutTypeEnum.findBuyCode(checkoutType); + + PlatformConfig config = platformConfigService.getConfig(); + + switch (checkoutTypeEnum) { + case H5 -> { + return StrUtil.format("{}/checkout/{}",config.getGatewayMobileUrl(), code); + } + case PC -> { + return StrUtil.format("{}/checkout/{}",config.getGatewayPcUrl(), code); + } + case MINI_APP -> { + throw new UnsupportedAbilityException("暂不支持小程序收银台"); + } + default -> throw new UnsupportedAbilityException("不支持的收银台类型"); + } + } + + /** + * 获取收银台相关信息, 不需要签名和鉴权 + */ + public void info(String orderNo){ + // 订单信息 + PayOrder orderAndCheck = payAssistService.getOrderAndCheck(orderNo); + // 配置信息 + paymentAssistService.initMchApp(orderAndCheck.getAppId()); + // 获取相关配置 + } + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/config/CheckoutConfigService.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/config/CheckoutConfigService.java new file mode 100644 index 00000000..dd3e3923 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/config/CheckoutConfigService.java @@ -0,0 +1,26 @@ +package org.dromara.daxpay.service.service.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.daxpay.service.dao.config.checkout.CheckoutConfigManager; +import org.dromara.daxpay.service.dao.config.checkout.CheckoutGroupConfigManager; +import org.dromara.daxpay.service.dao.config.checkout.CheckoutItemConfigManager; +import org.springframework.stereotype.Service; + +/** + * 收银台配置 + * @author xxm + * @since 2024/11/26 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CheckoutConfigService { + private final CheckoutConfigManager checkoutConfigManager; + private final CheckoutGroupConfigManager checkoutGroupConfigManager; + private final CheckoutItemConfigManager checkoutItemConfigManager; + + + + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/trade/pay/PayAssistService.java b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/trade/pay/PayAssistService.java index ee460ecf..64656fb5 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/trade/pay/PayAssistService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/org/dromara/daxpay/service/service/trade/pay/PayAssistService.java @@ -85,6 +85,7 @@ public class PayAssistService { .setReqTime(payParam.getReqTime()) .setChannel(payParam.getChannel()) .setMethod(payParam.getMethod()) + .setStatus(PayStatusEnum.PROGRESS.getCode()) .setExtraParam(payParam.getExtraParam()); if (!order.getAllocation()) { order.setAllocStatus(null);