From 439db39fa3fbd7e939186b99ac3ef3fbb0be71fa Mon Sep 17 00:00:00 2001 From: gaoqr <13665037151@163.com> Date: Fri, 17 Oct 2025 15:29:27 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=BD=AF=E4=BB=B6=E8=B4=AD=E4=B9=B0?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E6=96=B0=E7=9A=84=E6=B8=A0=E9=81=93code?= =?UTF-8?q?=E5=9F=BA=E4=BA=8E=E4=BA=8C=E8=BF=9B=E5=88=B6=E7=9A=84=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=EF=BC=9B2=E3=80=81=E8=B4=A6=E6=88=B7=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E3=80=81=E9=94=80=E5=94=AE=E7=AE=A1=E7=90=86=E3=80=81?= =?UTF-8?q?=E8=BD=AF=E4=BB=B6=E8=B4=AD=E4=B9=B0=E5=88=97=E8=A1=A8=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=92=8C=E5=9B=9E=E8=B0=83=E6=95=B0=E6=8D=AE=E8=B0=83?= =?UTF-8?q?=E6=95=B4=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/BalanceDetailsPageReqVO.java | 28 +- .../vo/BalanceDetailsRespVO.java | 15 +- .../vo/ManagePurchaseRecordRespVO.java | 3 - .../purchase/vo/PurchaseRecordPageReqVO.java | 19 +- .../purchase/vo/PurchaseRecordRespVO.java | 17 +- .../funds/purchase/vo/SalesDetailRespVO.java | 7 +- .../purchase/vo/SalesDetailsPageReqVO.java | 3 + .../recharge/vo/RechargeDetailRespVO.java | 17 +- .../pay/vo/PayProductOrderUnifiedReqVO.java | 2 - .../incomeandexpense/AccountDetailsDO.java | 65 ++ .../system/dal/dataobject/pay/PayOrderDO.java | 5 - .../pay/PayOrderRechargeDetailDO.java | 8 + .../funds/purchase/PurchaseRecordMapper.java | 6 +- .../IncomeExpenseDetailsMapper.java | 18 +- .../system/enums/pay/PayChannelCodeEnum.java | 8 +- .../module/system/enums/pay/PayConstants.java | 4 - .../pay/PayProductChannelCodeEnumNew.java | 128 +++ .../IncomeExpenseServiceImpl.java | 37 +- ...ManualAdjustAccountBalanceServiceImpl.java | 2 - .../funds/purchase/PurchaseServiceImpl.java | 14 +- .../system/service/pay/PayOrderService.java | 9 + .../service/pay/PayOrderServiceImpl.java | 47 +- .../service/pay/PayProductProcessor.java | 989 ------------------ .../service/pay/PayProductProcessorNew.java | 593 +++++++++++ .../IncomeExpenseDetailsMapper.xml | 49 + .../mapper/funds/pay/PayOrderMapper.xml | 3 +- 26 files changed, 1013 insertions(+), 1083 deletions(-) create mode 100644 cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/AccountDetailsDO.java create mode 100644 cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnumNew.java delete mode 100644 cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java create mode 100644 cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessorNew.java create mode 100644 cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/incomeandexpense/IncomeExpenseDetailsMapper.xml diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsPageReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsPageReqVO.java index 0c06439c6..5049285ce 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsPageReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsPageReqVO.java @@ -1,15 +1,19 @@ package com.cf.imes.module.system.controller.admin.funds.balancedetails.vo; - import com.cf.imes.framework.common.pojo.PageParam; +import com.cf.imes.module.system.validation.pay.IncomeExpenseTypeEnumValid; import com.cf.imes.module.system.validation.pay.TradeTypeEnumValid; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; +import java.math.BigDecimal; import java.time.LocalDate; import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; @@ -34,10 +38,32 @@ public class BalanceDetailsPageReqVO extends PageParam { @TradeTypeEnumValid private Integer tradeType; + @Schema(description = "收支类型,0收入 1支出") + @IncomeExpenseTypeEnumValid + private Integer incomeExpenseType; + @Schema(description = "创建时间", example = "[2022-07-01 ,2022-07-01]") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) private LocalDate[] createTime; + @Schema(description = "充值金额") + @DecimalMin(value = "0.01", message = "充值金额必须大于零") + @DecimalMax(value = "100000", message = "充值金额不能超过十万") + @Digits(integer = 6, fraction = 2, message = "支付金额格式不正确") + private BigDecimal rechargeAmount; + + @Schema(description = "赠送金额") + @DecimalMin(value = "0", message = "赠送金额不能小于零") + @DecimalMax(value = "100000", message = "赠送金额不能超过十万") + @Digits(integer = 6, fraction = 2, message = "赠送金额格式不正确") + private BigDecimal giftAmount; + + @Schema(description = "产品金额") + @DecimalMin(value = "0.01", message = "产品购买金额必须大于零") + @DecimalMax(value = "100000", message = "产品购买金额不能超过十万") + @Digits(integer = 6, fraction = 2, message = "产品购买金额格式不正确") + private BigDecimal productPrice; + @Schema(description = "组织ID") private Long organId; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsRespVO.java index 54e1484fb..de714cd32 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/balancedetails/vo/BalanceDetailsRespVO.java @@ -41,20 +41,20 @@ public class BalanceDetailsRespVO { @ExcelProperty("充值金额") private BigDecimal rechargeAmount; - @Schema(description = "充值赠送金金额") - @ExcelProperty("充值赠送金金额") + @Schema(description = "赠送金金额") + @ExcelProperty("赠送金金额") private BigDecimal rechargeGiftAmount; @Schema(description = "产品金额") @ExcelProperty("产品金额") private BigDecimal productPrice; - @Schema(description = "账户充值支付") - @ExcelProperty("账户充值支付") + @Schema(description = "账户余额支付") + @ExcelProperty("账户余额支付") private BigDecimal balancePayAmount; - @Schema(description = "支付宝和微信支付") - @ExcelProperty("支付宝和微信支付") + @Schema(description = "扫码支付") + @ExcelProperty("扫码支付") private BigDecimal rechargePayAmount; @Schema(description = "账户赠送金支付") @@ -68,4 +68,7 @@ public class BalanceDetailsRespVO { @Schema(description = "操作人") @ExcelProperty("操作人") private String creator; + + @Schema(description = "支付方式") + private String paymentMethod; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/ManagePurchaseRecordRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/ManagePurchaseRecordRespVO.java index fface2660..b6ce7cf08 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/ManagePurchaseRecordRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/ManagePurchaseRecordRespVO.java @@ -3,7 +3,6 @@ package com.cf.imes.module.system.controller.admin.funds.purchase.vo; import com.alibaba.excel.annotation.ExcelProperty; import com.cf.imes.framework.excel.core.annotations.EnumFormat; import com.cf.imes.framework.excel.core.convert.EnumConvert; -import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum; import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -42,8 +41,6 @@ public class ManagePurchaseRecordRespVO { private Integer giftDurationUnit; @Schema(description = "支付方式") - @ExcelProperty(value = "支付方式", converter = EnumConvert.class) - @EnumFormat(value = PayProductChannelCodeEnum.class) private Integer paymentMethod; @Schema(description = "订单总额") diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordPageReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordPageReqVO.java index f02777ae8..d561e2fad 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordPageReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordPageReqVO.java @@ -3,14 +3,20 @@ package com.cf.imes.module.system.controller.admin.funds.purchase.vo; import com.cf.imes.framework.common.pojo.PageParam; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; +import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; +import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @Schema(description = "管理后台 - 组织购买记录明细 Request VO") @@ -41,7 +47,16 @@ public class PurchaseRecordPageReqVO extends PageParam { private String creator; @Schema(description = "订单号") - @Size(max = 64, message = "订单号长度不能超过64个字符") - private String orderNo; + @Size(max = 20, message = "订单号长度不能超过20个字符") + private String id; + @Schema(description = "有效时间", example = "[2022-07-01 01:01:01 ,2022-07-01 01:01:01]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate[] endTime; + + @Schema(description = "产品金额") + @DecimalMin(value = "0.01", message = "产品购买金额必须大于零") + @DecimalMax(value = "100000", message = "产品购买金额不能超过十万") + @Digits(integer = 6, fraction = 2, message = "产品购买金额格式不正确") + private BigDecimal productPrice; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordRespVO.java index 6058b4da0..6b94d834a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/PurchaseRecordRespVO.java @@ -5,7 +5,6 @@ import com.alibaba.excel.annotation.ExcelProperty; import com.cf.imes.framework.excel.core.annotations.EnumFormat; import com.cf.imes.framework.excel.core.convert.EnumConvert; import com.cf.imes.framework.jackson.core.databind.LocalDateSerializer; -import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum; import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.v3.oas.annotations.media.Schema; @@ -59,9 +58,7 @@ public class PurchaseRecordRespVO { private Integer giftDurationUnit; @Schema(description = "支付方式") - @ExcelProperty(value = "支付方式", converter = EnumConvert.class) - @EnumFormat(value = PayProductChannelCodeEnum.class) - private Integer paymentMethod; + private String paymentMethod; @Schema(description = "订单总额") @ExcelProperty("订单总额") @@ -71,18 +68,14 @@ public class PurchaseRecordRespVO { @ExcelProperty("现金余额支出") private BigDecimal accountBalanceSpent; - @Schema(description = "支付宝支出") - @ExcelProperty("支付宝支出") - private BigDecimal alipaySpent; - - @Schema(description = "微信支出") - @ExcelProperty("微信支出") - private BigDecimal wechatSpent; - @Schema(description = "赠送金") @ExcelProperty("赠送金") private BigDecimal giftMoneySpent; + @Schema(description = "扫码支付") + @ExcelProperty("扫码支付") + private BigDecimal externalSpent; + @Schema(description = "购买时间") @ExcelProperty("购买时间") private LocalDateTime createTime; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailRespVO.java index 3bce6022e..44231bf24 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailRespVO.java @@ -31,8 +31,8 @@ public class SalesDetailRespVO { @ExcelProperty("产品金额") private BigDecimal totalAmount; - @Schema(description = "账户充值支付") - @ExcelProperty("账户充值支付") + @Schema(description = "扫码支付") + @ExcelProperty("扫码支付") private BigDecimal rechargeSpent; @Schema(description = "现金支付") @@ -51,6 +51,9 @@ public class SalesDetailRespVO { @ExcelProperty("组织名称") private String organName; + @Schema(description = "支付方式") + private String paymentMethod; + @Schema(description = "交易时间") @ExcelProperty("交易时间") private LocalDateTime createTime; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailsPageReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailsPageReqVO.java index be3d3f862..2ab1e9c3d 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailsPageReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/SalesDetailsPageReqVO.java @@ -28,6 +28,9 @@ public class SalesDetailsPageReqVO extends PageParam { @Schema(description = "组织ID") private Long organId; + @Schema(description = "订单号") + private Long id; + @Schema(description = "产品金额") @DecimalMin(value = "0.01", message = "产品购买金额必须大于零") @DecimalMax(value = "100000", message = "产品购买金额不能超过十万") diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/recharge/vo/RechargeDetailRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/recharge/vo/RechargeDetailRespVO.java index 27767c555..de2524019 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/recharge/vo/RechargeDetailRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/recharge/vo/RechargeDetailRespVO.java @@ -28,13 +28,13 @@ public class RechargeDetailRespVO { @EnumFormat(value = TradeTypeEnum.class) private Integer tradeType; - @Schema(description = "订单号") - @ExcelProperty("订单号") - private String payNo; - @Schema(description = "充值金额") @ExcelProperty("充值金额") - private BigDecimal price; + private BigDecimal rechargeAmount; + + @Schema(description = "赠送金金额") + @ExcelProperty("赠送金金额") + private BigDecimal rechargeGiftAmount; @ExcelIgnore private Long purchaseId; @@ -43,8 +43,8 @@ public class RechargeDetailRespVO { @ExcelProperty("产品金额") private BigDecimal totalAmount; - @Schema(description = "账户充值支付") - @ExcelProperty("账户充值支付") + @Schema(description = "扫码支付") + @ExcelProperty("扫码支付") private BigDecimal rechargeSpent; @Schema(description = "现金支付") @@ -66,4 +66,7 @@ public class RechargeDetailRespVO { @Schema(description = "交易时间") @ExcelProperty("交易时间") private LocalDateTime createTime; + + @Schema(description = "支付方式") + private String paymentMethod; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java index b6fd76757..d361eede1 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java @@ -1,6 +1,5 @@ package com.cf.imes.module.system.controller.admin.pay.vo; -import com.cf.imes.module.system.validation.pay.PayProductChannelCodeValid; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -27,7 +26,6 @@ public class PayProductOrderUnifiedReqVO { private Long productId; @Schema(description = "产品支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - @PayProductChannelCodeValid @NotNull(message = "产品支付渠道编码不能为空", groups = PayProductGroup.class) private Integer productChannelCode; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/AccountDetailsDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/AccountDetailsDO.java new file mode 100644 index 000000000..296737350 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/AccountDetailsDO.java @@ -0,0 +1,65 @@ +package com.cf.imes.module.system.dal.dataobject.incomeandexpense; + +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * @author Gqr + * @since 2025/10/17 11:48 + */ +@Data +public class AccountDetailsDO { + /** 收支明细ID */ + private Long id; + + /** 流水号 */ + private String businessNo; + + /** 支付订单号 */ + private String orderNo; + + /** 软件购买id */ + private Long purchaseId; + + /** 交易类型 */ + private Integer tradeType; + + /** 收支类型 */ + private Integer incomeExpenseType; + + /** 现金金额变化 */ + private BigDecimal cashAmountChange; + + /** 赠送金金额变化 */ + private BigDecimal giftAmountChange; + + /** 创建人 */ + private String creator; + + /** 创建时间 */ + private LocalDateTime createTime; + + /** 机构ID */ + private Long organId; + + /** 订单总金额 */ + private BigDecimal totalAmount; + + /** 外部支付金额 */ + private BigDecimal externalSpent; + + /** 账户余额支付金额 */ + private BigDecimal accountBalanceSpent; + + /** 赠送金支付金额 */ + private BigDecimal giftMoneySpent; + + /** 支付方式 */ + private Integer paymentMethod; + + /** 渠道编码 */ + private PayChannelCodeEnum channelCode; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java index b83b72fb0..d9c9dd083 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderDO.java @@ -63,11 +63,6 @@ public class PayOrderDO extends BaseDO { */ private Long purchaseId; - /** - * 支付订单号:P + 当前id + 时间戳(yyyyMMddHHmmss) - */ - private String payNo; - /** * 交易类型 */ diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderRechargeDetailDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderRechargeDetailDO.java index 254f75ef7..af80f51ef 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderRechargeDetailDO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/pay/PayOrderRechargeDetailDO.java @@ -1,5 +1,6 @@ package com.cf.imes.module.system.dal.dataobject.pay; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; import lombok.Data; import java.math.BigDecimal; @@ -62,4 +63,11 @@ public class PayOrderRechargeDetailDO { * 交易时间 */ private LocalDateTime createTime; + + /** + * 渠道编号 + */ + private PayChannelCodeEnum channelCode; + + private Integer payMethod; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java index fb66db0ed..1caa66015 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java @@ -44,13 +44,14 @@ public interface PurchaseRecordMapper extends BaseMapperX { .eq(PurchaseRecordDO::getDeleted, false) .eqIfPresent(PurchaseRecordDO::getOrganId, pageReqVO.getOrganId()) .likeIfPresent(PurchaseRecordDO::getProductName, pageReqVO.getProductName()) - .likeIfPresent(PurchaseRecordDO::getOrderNo, pageReqVO.getOrderNo()) + .likeIfPresent(PurchaseRecordDO::getId, pageReqVO.getId()) .eqIfPresent(PurchaseRecordDO::getPurchaseDuration, pageReqVO.getPurchaseDuration()) .betweenIfPresent(PurchaseRecordDO::getCreateTime, pageReqVO.getCreateTime()) .likeIfPresent(PurchaseRecordDO::getCreator, pageReqVO.getCreator()) + .eqIfPresent(PurchaseRecordDO::getTotalAmount, pageReqVO.getProductPrice()) + .betweenIfPresent(PurchaseRecordDO::getEndTime, pageReqVO.getEndTime()) .ne(PurchaseRecordDO::getInitial, true) .orderByDesc(PurchaseRecordDO::getCreateTime)); - } /** @@ -115,6 +116,7 @@ public interface PurchaseRecordMapper extends BaseMapperX { */ default PageResult getSaleDetailsPage(SalesDetailsPageReqVO pageReqVO) { return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eqIfPresent(PurchaseRecordDO::getId, pageReqVO.getId()) .betweenIfPresent(PurchaseRecordDO::getCreateTime, pageReqVO.getCreateTime()) .eqIfPresent(PurchaseRecordDO::getTotalAmount, pageReqVO.getProductPrice()) .eqIfPresent(PurchaseRecordDO::getOrganId, pageReqVO.getOrganId()) diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/incomeandexpense/IncomeExpenseDetailsMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/incomeandexpense/IncomeExpenseDetailsMapper.java index 0cd413680..c7441d3bc 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/incomeandexpense/IncomeExpenseDetailsMapper.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/incomeandexpense/IncomeExpenseDetailsMapper.java @@ -2,16 +2,18 @@ package com.cf.imes.module.system.dal.mysql.incomeandexpense; import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsPageReqVO; import com.cf.imes.module.system.controller.admin.funds.manualadjust.vo.ManualAdjustAccountBalancePageReqVO; +import com.cf.imes.module.system.dal.dataobject.incomeandexpense.AccountDetailsDO; import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; import com.cf.imes.module.system.enums.pay.AdjustTypeEnum; import com.cf.imes.module.system.enums.pay.TradeTypeEnum; import org.apache.ibatis.annotations.Mapper; - +import org.apache.ibatis.annotations.Param; /** * 收支明细 Mapper * @@ -33,6 +35,11 @@ public interface IncomeExpenseDetailsMapper extends BaseMapperX getAccountDetailsPage(@Param("page") IPage page, @Param("reqVO") BalanceDetailsPageReqVO reqVO); + /** * 获取手工调账分页 * diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java index b121f710f..58d24e26b 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayChannelCodeEnum.java @@ -17,20 +17,22 @@ public enum PayChannelCodeEnum { /** * 支付宝 */ - ALIPAY_PC(0), + ALIPAY_PC(0, "支付宝"), /** * 微信 */ - WECHAT_NATIVE(1), + WECHAT_NATIVE(1, "微信"), /** * 手工调账 */ - MANUAL_ADJUST(2); + MANUAL_ADJUST(2, "手工调账"); @EnumValue private final Integer code; + private final String desc; + @JsonCreator public static PayChannelCodeEnum fromType(Integer type) { for (PayChannelCodeEnum value : values()) { diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java index 21a834cfa..722067169 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java @@ -7,10 +7,6 @@ package com.cf.imes.module.system.enums.pay; * @since 2025/8/12 17:49 */ public class PayConstants { - public static final String PAY_ORDER_PREFIX_FORMAT = "PO%s"; // 第三方支付流水号格式 - - public static final String PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT = "PD%s"; // 产品购买流水号格式 - public static final String PAY_PRODUCT_DETAIL_AUTO_GENERATE_MONTH_ID_FORMAT = "1759029544%d"; // 1~12月定价规则自动生成ID格式 public static final String PAY_PRODUCT_DETAIL_AUTO_GENERATE_YEAR_ID_FORMAT = "1759993429%d"; // 1年定价规则自动生成ID格式 diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnumNew.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnumNew.java new file mode 100644 index 000000000..3ed55d9a5 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnumNew.java @@ -0,0 +1,128 @@ +package com.cf.imes.module.system.enums.pay; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author Gqr + * @since 2025/10/16 11:58 + */ +@Getter +public enum PayProductChannelCodeEnumNew { + /** + * 现金 + */ + BALANCE(1, "现金"), // 二进制 0001 + + /** + * 赠送金 + */ + GIFT(1 << 1, "赠送金"), // 二进制 0010 + + /** + * 支付宝 + */ + ALIPAY(1 << 2, "支付宝"), // 二进制 0100 + + /** + * 微信 + */ + WECHAT(1 << 3, "微信"); // 二进制 1000 + + private final int code; + private final String label; + + PayProductChannelCodeEnumNew(int code, String label) { + this.code = code; + this.label = label; + } + + /** + * 把多个支付方式组合成一个整数 + * + * @param types + */ + public static int combine(PayProductChannelCodeEnumNew... types) { + int result = 0; + for (PayProductChannelCodeEnumNew type : types) { + result |= type.code; + } + return result; + } + + /** + * 是否包含现金 + */ + public static boolean hasBalance(int code) { + return (code & PayProductChannelCodeEnumNew.BALANCE.getCode()) != 0; + } + + /** + * 是否只有现金 + */ + public static boolean isBalanceOnly(int code) { + return code == BALANCE.code; + } + + /** + * 是否是现金+赠送金 + */ + public static boolean isBalanceAndGift(int code) { + return code == combine(BALANCE, GIFT); + } + + /** + * 是否包含赠送金 + */ + public static boolean hasGift(int code) { + return (code & PayProductChannelCodeEnumNew.GIFT.getCode()) != 0; + } + + /** + * 是否只有赠送金 + */ + public static boolean isGiftOnly(int code) { + return code == GIFT.code; + } + + /** + * 是否包含支付宝 + */ + public static boolean hasAlipay(int code) { + return (code & PayProductChannelCodeEnumNew.ALIPAY.getCode()) != 0; + } + + /** + * 是否包含微信 + */ + public static boolean hasWechat(int code) { + return (code & PayProductChannelCodeEnumNew.WECHAT.getCode()) != 0; + } + + /** + * 获取所有包含的支付方式列表 + */ + public static List parse(int code) { + List list = new ArrayList<>(); + for (PayProductChannelCodeEnumNew type : PayProductChannelCodeEnumNew.values()) { + if ((code & type.getCode()) != 0) { + list.add(type); + } + } + return list; + } + + /** + * 返回可读描述,比如“现金+微信” + * + * @param code + */ + public static String describe(int code) { + return parse(code).stream() + .map(PayProductChannelCodeEnumNew::getLabel) + .collect(Collectors.joining("+")); + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/incomeandexpense/IncomeExpenseServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/incomeandexpense/IncomeExpenseServiceImpl.java index 434054eb6..0c8a0a078 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/incomeandexpense/IncomeExpenseServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/incomeandexpense/IncomeExpenseServiceImpl.java @@ -2,15 +2,17 @@ package com.cf.imes.module.system.service.funds.incomeandexpense; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsPageReqVO; import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsRespVO; -import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; +import com.cf.imes.module.system.dal.dataobject.incomeandexpense.AccountDetailsDO; import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; import com.cf.imes.module.system.dal.mysql.incomeandexpense.IncomeExpenseDetailsMapper; +import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnumNew; import com.cf.imes.module.system.enums.pay.TradeTypeEnum; -import com.cf.imes.module.system.service.funds.purchase.PurchaseService; import com.cf.imes.module.system.service.organ.OrganService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -29,9 +31,6 @@ public class IncomeExpenseServiceImpl implements IncomeExpenseService { @Resource private IncomeExpenseDetailsMapper incomeExpenseDetailsMapper; - @Resource - private PurchaseService purchaseService; - @Resource private OrganService organService; @@ -43,8 +42,9 @@ public class IncomeExpenseServiceImpl implements IncomeExpenseService { @Override public PageResult getBalanceDetailsPage(BalanceDetailsPageReqVO pageReqVO) { - PageResult pageResult = incomeExpenseDetailsMapper.selectDetailsPage(pageReqVO); - List pageResultList = pageResult.getList(); + PageDTO page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()); + IPage pageResult = incomeExpenseDetailsMapper.getAccountDetailsPage(page, pageReqVO); + List pageResultList = pageResult.getRecords(); if(CollUtil.isEmpty(pageResultList)){ return new PageResult<>(); } @@ -54,7 +54,6 @@ public class IncomeExpenseServiceImpl implements IncomeExpenseService { pageResultList.forEach(i -> { BalanceDetailsRespVO respVO = new BalanceDetailsRespVO(); respVO.setBusinessNo(i.getBusinessNo()); - respVO.setOrderNo(i.getOrderNo()); respVO.setCreator(i.getCreator()); respVO.setCreateTime(i.getCreateTime()); respVO.setIncomeExpenseType(i.getIncomeExpenseType()); @@ -65,23 +64,23 @@ public class IncomeExpenseServiceImpl implements IncomeExpenseService { if (TradeTypeEnum.RECHARGE.getCode().equals(tradeType)) { respVO.setRechargeAmount(i.getCashAmountChange()); respVO.setRechargeGiftAmount(i.getGiftAmountChange()); + respVO.setPaymentMethod(i.getChannelCode().getDesc()); + respVO.setOrderNo(i.getOrderNo()); + } else if (TradeTypeEnum.PRODUCT.getCode().equals(tradeType)) { - Long purchaseId = i.getPurchaseId(); - if (ObjectUtil.isNotNull(purchaseId)) { - // 查询对应的购买记录 - PurchaseRecordDO record = purchaseService.getRecord(purchaseId); - if (ObjectUtil.isNotNull(record)) { - respVO.setProductPrice(record.getTotalAmount()); - respVO.setBalancePayAmount(record.getAccountBalanceSpent()); - respVO.setRechargePayAmount(record.getExternalSpent()); - respVO.setGiftPayAmount(record.getGiftMoneySpent()); - } - } + respVO.setProductPrice(i.getTotalAmount()); + respVO.setBalancePayAmount(i.getAccountBalanceSpent()); + respVO.setRechargePayAmount(i.getExternalSpent()); + respVO.setGiftPayAmount(i.getGiftMoneySpent()); + respVO.setPaymentMethod(PayProductChannelCodeEnumNew.describe(i.getPaymentMethod())); + respVO.setOrderNo(String.valueOf(i.getPurchaseId())); + } else if (TradeTypeEnum.MANUAL_ADJUST.getCode().equals(tradeType)) { respVO.setRechargeAmount(i.getCashAmountChange()); respVO.setRechargeGiftAmount(i.getGiftAmountChange()); // 手工调账算到充值 respVO.setTradeType(TradeTypeEnum.RECHARGE.getCode()); + respVO.setPaymentMethod(TradeTypeEnum.MANUAL_ADJUST.getDesc()); } // 查询操作组织名 OrganizationDO organ = organService.getOrganWithDeleted(i.getOrganId()); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/manualadjust/ManualAdjustAccountBalanceServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/manualadjust/ManualAdjustAccountBalanceServiceImpl.java index 514a13a5e..19310154f 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/manualadjust/ManualAdjustAccountBalanceServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/manualadjust/ManualAdjustAccountBalanceServiceImpl.java @@ -127,7 +127,6 @@ public class ManualAdjustAccountBalanceServiceImpl implements ManualAdjustAccoun // 新增收支明细 IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() .organId(organId) - .orderNo(payOrderDO.getPayNo()) .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) .tradeType(TradeTypeEnum.MANUAL_ADJUST.getCode()) .incomeExpenseType(incomeExpenseType) @@ -152,7 +151,6 @@ public class ManualAdjustAccountBalanceServiceImpl implements ManualAdjustAccoun Long payOrderId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); payOrderDO.setId(payOrderId); - payOrderDO.setPayNo(String.format(PayConstants.PAY_ORDER_PREFIX_FORMAT, payOrderId)); payOrderDO.setStatus(PayOrderStatusEnum.SUCCESS); payOrderDO.setPrice(recharge); payOrderDO.setSubject("手工调账"); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java index 2b8c636c1..3221daa56 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java @@ -1,5 +1,6 @@ package com.cf.imes.module.system.service.funds.purchase; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; @@ -28,7 +29,7 @@ import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper; import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum; -import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum; +import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnumNew; import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum; import com.cf.imes.module.system.enums.pay.TradeTypeEnum; @@ -107,7 +108,12 @@ public class PurchaseServiceImpl implements PurchaseService { if (CollUtil.isEmpty(resultList)) { return new PageResult<>(); } - List respVOS = BeanUtils.toBean(resultList, PurchaseRecordRespVO.class); + List respVOS = new ArrayList<>(); + for (PurchaseRecordDO purchaseRecordDO : resultList) { + PurchaseRecordRespVO respVO = BeanUtil.copyProperties(purchaseRecordDO, PurchaseRecordRespVO.class, "paymentMethod"); + respVO.setPaymentMethod(PayProductChannelCodeEnumNew.describe(purchaseRecordDO.getPaymentMethod())); + respVOS.add(respVO); + } respVOS.forEach(respVO -> { OrganizationDO organ = organService.getOrganWithDeleted(respVO.getOrganId()); if (ObjectUtil.isNotNull(organ)) { @@ -184,7 +190,7 @@ public class PurchaseServiceImpl implements PurchaseService { .startTime(now) // 组织创建时的套餐购买默认过期 .endTime(now.minusDays(1)) - .paymentMethod(PayProductChannelCodeEnum.ORGAN_CREATE.getCode()) + .paymentMethod(PayProductChannelCodeEnumNew.BALANCE.getCode()) .totalAmount(price) .monthAverageAmount(monthAverageAmount) .originalAmount(price) @@ -297,6 +303,8 @@ public class PurchaseServiceImpl implements PurchaseService { respVO.setTradeType(TradeTypeEnum.PRODUCT.getCode()); // 充值支付=支付宝/微信 respVO.setRechargeSpent(detailDO.getExternalSpent()); + // 支付方式 + respVO.setPaymentMethod(PayProductChannelCodeEnumNew.describe(detailDO.getPaymentMethod())); // 查询组织名称 OrganizationDO organ = organService.getOrganWithDeleted(detailDO.getOrganId()); if (ObjectUtil.isNotNull(organ)) { diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java index d09875372..287eebea1 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java @@ -6,6 +6,7 @@ import com.cf.imes.module.system.controller.admin.funds.recharge.vo.RechargeDeta import com.cf.imes.module.system.controller.admin.funds.recharge.vo.RechargeDetailsPageReqVO; import com.cf.imes.module.system.controller.admin.pay.vo.PayProductOrderUnifiedReqVO; import com.cf.imes.module.system.controller.admin.pay.vo.PayRechargeOrderReqVO; +import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO; import java.math.BigDecimal; import java.util.Map; @@ -17,6 +18,14 @@ import java.util.Map; * @since 2025/4/17 17:29 */ public interface PayOrderService { + /** + * 根据id查询支付订单 + * + * @param id + * @return + */ + PayOrderDO get(Long id); + /** * 产品支付 * diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java index 6c742b21c..f3a2f9006 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java @@ -39,6 +39,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.product.Prod import com.cf.imes.module.system.dal.dataobject.funds.delay.ProductDelayRecordDO; import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; import com.cf.imes.module.system.dal.dataobject.pay.PayOrderRechargeDetailDO; +import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnumNew; import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; import com.cf.imes.module.system.service.funds.delay.ProductDelayService; import com.cf.imes.module.system.service.funds.purchase.PurchaseService; @@ -52,8 +53,6 @@ import com.cf.imes.module.system.dal.dataobject.funds.recharge.GiftMoneyDetailsD import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; import com.cf.imes.module.system.enums.pay.IncomeExpenseTypeEnum; import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; -import com.cf.imes.module.system.enums.pay.PayConstants; -import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum; import com.cf.imes.module.system.enums.pay.TradeTypeEnum; import com.cf.imes.module.system.enums.recharge.RechargeGiftTypeEnum; import com.cf.imes.module.system.enums.recharge.RechargeTypeEnum; @@ -90,6 +89,7 @@ import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.e import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_PRODUCT_PURCHASE_LOCK_ERROR; import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_PRODUCT_PURCHASE_WAIT_PAY_ERROR; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_CHANNEL_NOT_SUPPORT; import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_NOTIFY_PASSBACK_TRADETYPE_NOT_SUPPORT_ERROR; 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; @@ -169,6 +169,11 @@ public class PayOrderServiceImpl implements PayOrderService { @Resource private SnowflakeSerialNoWorker3rd snowflakeSerialNoWorker3rd; + @Override + public PayOrderDO get(Long id) { + return payOrderMapper.selectById(id); + } + @Override @Transactional(rollbackFor = Exception.class) public PayOrderRespDTO productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) { @@ -178,6 +183,10 @@ public class PayOrderServiceImpl implements PayOrderService { // 主动校验一次入参 ValidationUtils.validate(validator, payProductOrderUnifiedReqVO); + // 校验组合渠道 + List parse = PayProductChannelCodeEnumNew.parse(payProductOrderUnifiedReqVO.getProductChannelCode()); + AssertUtils.notEmpty(parse, PAY_CHANNEL_NOT_SUPPORT); + // 校验产品和产品明细 Long productId = payProductOrderUnifiedReqVO.getProductId(); ProductsDO productsDO = productsDetailService.validateProductExist(productId); @@ -194,7 +203,7 @@ public class PayOrderServiceImpl implements PayOrderService { PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = null; // 获取渠道方处理器 - Integer channelCode = PayProductChannelCodeEnum.isAliPay(payProductOrderUnifiedReqVO.getProductChannelCode()) ? PayChannelCodeEnum.ALIPAY_PC.getCode() : PayChannelCodeEnum.WECHAT_NATIVE.getCode(); + Integer channelCode = PayProductChannelCodeEnumNew.hasAlipay(payProductOrderUnifiedReqVO.getProductChannelCode()) ? PayChannelCodeEnum.ALIPAY_PC.getCode() : PayChannelCodeEnum.WECHAT_NATIVE.getCode(); try { // 检查机构产品是否有待支付的购买记录 List pendingPurchaseList = purchaseService.getOrgProductPendingPurchaseList(organId, productId); @@ -212,8 +221,9 @@ public class PayOrderServiceImpl implements PayOrderService { PreviouProductDelayRespVO previouProductDelayRespVO = productDelayService.getActiveRecordsByProductId(productId, organId); // 根据购买类型做价格处理 - PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, orgInitialPurchaseRecord, previouProductDelayRespVO, organAmount, organId, payConfig, mybatisIdProperties, snowflakeSerialNoWorker3rd); - ProductProcessorRespVO productProcessorRespVO = payProductProcessor.processPayment(PayProductChannelCodeEnum.fromType(payProductOrderUnifiedReqVO.getProductChannelCode())); + PayProductProcessorNew payProductProcessorNew = new PayProductProcessorNew(productsDO, productsDetailDO, orgInitialPurchaseRecord, previouProductDelayRespVO, organAmount, organId, payConfig, mybatisIdProperties, snowflakeSerialNoWorker3rd); + ProductProcessorRespVO productProcessorRespVO = payProductProcessorNew.processPayment(payProductOrderUnifiedReqVO.getProductChannelCode()); + PayOrderDO payOrder = productProcessorRespVO.getPayOrderDO(); // 处理前置的购买记录 @@ -288,7 +298,7 @@ public class PayOrderServiceImpl implements PayOrderService { PreviouProductDelayRespVO previouProductDelayRespVO = productDelayService.getActiveRecordsByProductId(productId, organId); // 根据购买类型做价格处理 - PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, orgInitialPurchaseRecord, previouProductDelayRespVO, null, organId, null, null, null); + PayProductProcessorNew payProductProcessor = new PayProductProcessorNew(productsDO, productsDetailDO, orgInitialPurchaseRecord, previouProductDelayRespVO, null, organId, null, null, null); return LocalDateTimeUtil.format(payProductProcessor.getProductPayExpireTime(), FORMAT_YEAR_MONTH_DAY); } @@ -477,7 +487,6 @@ public class PayOrderServiceImpl implements PayOrderService { Long organId = SecurityFrameworkUtils.getUserOrganId(); payOrderDO.setId(payOrderId); - payOrderDO.setPayNo(String.format(PayConstants.PAY_ORDER_PREFIX_FORMAT, payOrderId)); payOrderDO.setStatus(PayOrderStatusEnum.WAITING); payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime())); payOrderDO.setPrice(price); @@ -500,7 +509,6 @@ public class PayOrderServiceImpl implements PayOrderService { String subject = payOrderDO.getSubject(); payOrderUnifiedReqDTO.setSubject(subject); payOrderUnifiedReqDTO.setBody(subject); - payOrderUnifiedReqDTO.setOutTradeNo(payOrderDO.getPayNo()); payOrderUnifiedReqDTO.setPrice(payOrderDO.getPrice()); payOrderUnifiedReqDTO.setExpireTime(payOrderDO.getExpireTime()); payOrderUnifiedReqDTO.setNotifyUrl(String.format(alipayConfig.getNotifyUrl(), payOrderDO.getChannelCode().getCode())); @@ -512,7 +520,7 @@ public class PayOrderServiceImpl implements PayOrderService { public void queryPayOrderStatus(String outTradeNo, int channelCode) { // 校验支付订单是否存在 PayOrderDO payOrderDO = payOrderMapper.selectOne(new LambdaQueryWrapper() - .eq(PayOrderDO::getPayNo, outTradeNo) + .eq(PayOrderDO::getId, outTradeNo) .eq(PayOrderDO::getOrganId, validOrganId()) .eq(PayOrderDO::getDeleted, false) ); @@ -601,7 +609,7 @@ public class PayOrderServiceImpl implements PayOrderService { private void notifyOrderSuccess(PayOrderRespDTO notify, String outTradeNo) { // 1. 判断 PayOrderDO 是否处于待支付 PayOrderDO order = payOrderMapper.selectOne(new LambdaQueryWrapper() - .eq(PayOrderDO::getPayNo, outTradeNo)); + .eq(PayOrderDO::getId, outTradeNo)); Long organId = order.getOrganId(); if (ObjectUtil.isNull(order)) { throw exception(PAY_ORDER_NOT_EXIST, outTradeNo); @@ -653,7 +661,7 @@ public class PayOrderServiceImpl implements PayOrderService { // 支付订单的金额 BigDecimal price = payOrderDO.getPrice(); // 订单流水号 - String payNo = payOrderDO.getPayNo(); + Long payOrderId = payOrderDO.getId(); // 1.更新组织资金 // 检查赠送金是否存在已经是否在有效期,有效的话计入组织充值赠送金 @@ -665,7 +673,7 @@ public class PayOrderServiceImpl implements PayOrderService { // 2.新增收支明细 IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() .organId(organId) - .orderNo(payNo) + .orderNo(String.valueOf(payOrderId)) .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) .tradeType(TradeTypeEnum.RECHARGE.getCode()) .incomeExpenseType(IncomeExpenseTypeEnum.INCOME.getCode()) @@ -710,10 +718,11 @@ public class PayOrderServiceImpl implements PayOrderService { // 5.如果是纯第三方支付需要新增收支明细 Integer paymentMethod = record.getPaymentMethod(); - if (PayProductChannelCodeEnum.ALIPAY_PC.getCode().equals(paymentMethod) || PayProductChannelCodeEnum.WECHAT_NATIVE.getCode().equals(paymentMethod)) { + if (PayProductChannelCodeEnumNew.hasAlipay(paymentMethod) || PayProductChannelCodeEnumNew.hasWechat(paymentMethod)) { IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() .organId(organId) - .orderNo(String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId)) + .orderNo(String.valueOf(payOrderDO.getId())) + .purchaseId(purchaseId) .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) .tradeType(TradeTypeEnum.PRODUCT.getCode()) .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) @@ -735,7 +744,7 @@ public class PayOrderServiceImpl implements PayOrderService { private void notifyOrderClosed(PayOrderRespDTO notify, String outTradeNo) { // 1. 查询 PayOrderDO PayOrderDO order = payOrderMapper.selectOne(new LambdaQueryWrapper() - .eq(PayOrderDO::getPayNo, outTradeNo)); + .eq(PayOrderDO::getId, outTradeNo)); Long organId = order.getOrganId(); if (ObjectUtil.isNull(order)) { throw exception(PAY_ORDER_NOT_EXIST, outTradeNo); @@ -818,7 +827,7 @@ public class PayOrderServiceImpl implements PayOrderService { // 3、新增退回的收支记录 IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() .organId(organId) - .orderNo(String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId)) + .purchaseId(purchaseId) .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) .tradeType(TradeTypeEnum.PRODUCT.getCode()) .incomeExpenseType(IncomeExpenseTypeEnum.INCOME.getCode()) @@ -883,7 +892,7 @@ public class PayOrderServiceImpl implements PayOrderService { AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode.getCode()); // 2、调用三方接口,查询支付单状态,是不是已支付 - PayOrderRespDTO payOrderRespDTO = payHandler.queryPayOrder(order.getPayNo()); + PayOrderRespDTO payOrderRespDTO = payHandler.queryPayOrder(String.valueOf(order.getId())); if (PayOrderStatusEnum.isSuccess(payOrderRespDTO.getStatus()) || PayOrderStatusEnum.isClosed(payOrderRespDTO.getStatus())) { notifyOrderWithTx(payOrderRespDTO); log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId()); @@ -892,9 +901,9 @@ public class PayOrderServiceImpl implements PayOrderService { // 3、系统内超期未支付状态是NOTPAY,主动关单后再次查询状态 if (PayOrderStatusEnum.WAITING.getStatus().equals(payOrderRespDTO.getStatus())) { - boolean closeOrderResult = payHandler.closeOrder(order.getPayNo()); + boolean closeOrderResult = payHandler.closeOrder(String.valueOf(order.getId())); if (closeOrderResult) { - payOrderRespDTO = payHandler.queryPayOrder(order.getPayNo()); + payOrderRespDTO = payHandler.queryPayOrder(String.valueOf(order.getId())); if (PayOrderStatusEnum.isSuccess(payOrderRespDTO.getStatus()) || PayOrderStatusEnum.isClosed(payOrderRespDTO.getStatus())) { notifyOrderWithTx(payOrderRespDTO); log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId()); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java deleted file mode 100644 index ebf70e7df..000000000 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java +++ /dev/null @@ -1,989 +0,0 @@ -package com.cf.imes.module.system.service.pay; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.ObjectUtil; -import com.cf.imes.framework.common.exception.ServiceException; -import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil; -import com.cf.imes.framework.id.core.util.SnowflakeSerialNoWorker3rd; -import com.cf.imes.framework.pay.config.ChenfengPayConfig; -import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; -import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO; -import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; -import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO; -import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO; -import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; -import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO; -import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; -import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; -import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO; -import com.cf.imes.module.system.enums.pay.IncomeExpenseTypeEnum; -import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum; -import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; -import com.cf.imes.module.system.enums.pay.PayConstants; -import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum; -import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum; -import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; -import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum; -import com.cf.imes.module.system.enums.pay.TradeTypeEnum; -import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties; -import com.cf.imes.module.system.service.pay.dto.DateIntervalDTO; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_AVAILABLE_BALANCE_INSUFFICIENT; -import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_BALANCE_INSUFFICIENT; -import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT; -import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT; -import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR; - -/** - * 产品购买类型处理器 - * - * @author Gqr - * @since 2025/8/14 10:54 - */ -public class PayProductProcessor { - - // 当次订购的产品 - private ProductsDO productsDO; - - // 当次订购的定价规则 - private ProductsDetailDO productsDetailDO; - - // 组织产品的首次订购记录 - private PurchaseRecordDO orgInitialPurchaseRecord; - - // 当前组织资金信息 - private OrganAmountDO organAmountDO; - - // 组织id - private Long organId; - - // 支付配置 - private ChenfengPayConfig payConfig; - - // id生成规则配置 - private MybatisIdProperties mybatisIdProperties; - - // id生成器 - private SnowflakeSerialNoWorker3rd snowflakeSerialNoWorker3rd; - - // 标记本次订购是否首次 - private boolean initialized = false; - - // 前序购买记录的延期记录 - private List delayRespVOList; - - // 前序购买记录 - private PurchaseRecordDO previousPurchaseRecord; - - // 本地订购产品的开始时间 - private LocalDate startTime = LocalDate.now(); - - // 产品结束时间 - private LocalDate endTime; - - // 本地订购产品的月均单价 - private BigDecimal monthAverageAmount = BigDecimal.ZERO; - - // 原始价格 - private BigDecimal originalPrice; - - public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, PreviouProductDelayRespVO previouProductDelayRespVO, - OrganAmountDO organAmountDO, Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties, SnowflakeSerialNoWorker3rd snowflakeSerialNoWorker3rd) { - this.productsDO = productsDO; - this.productsDetailDO = productsDetailDO; - this.orgInitialPurchaseRecord = orgInitialPurchaseRecord; - if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) { - delayRespVOList = previouProductDelayRespVO.getDelayRespVOList(); - previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); - // 上一次订购产品的结束时间进行计算 - endTime = previousPurchaseRecord.getEndTime(); - endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit()); - } else { - // 首次订购正常从今天开始累加算世界时间 - endTime = addTime(startTime, productsDetailDO.getDuration(), productsDetailDO.getDurationUnit()); - } - this.organAmountDO = organAmountDO; - this.organId = organId; - this.payConfig = payConfig; - this.mybatisIdProperties = mybatisIdProperties; - this.snowflakeSerialNoWorker3rd = snowflakeSerialNoWorker3rd; - } - - /** - * 根据产品支付类型解析出对应的信息, - * - * @param payType - */ - public ProductProcessorRespVO processPayment(PayProductChannelCodeEnum payType) { - // 首次购买记录存在则需要做计算,否则直接用定价规则的现价 - boolean initialPurchaseExist = ObjectUtil.isNotNull(orgInitialPurchaseRecord); - BigDecimal price; - if (initialPurchaseExist) { - price = computePrice(); - } else { - price = computeInitialProductPayAmount(); - // 首次购买记录是空,那么当前就是首次订购 - initialized = true; - } - // 当前组织的余额 - BigDecimal balance = organAmountDO.getAmount(); - // 当前组织的赠送金 - BigDecimal gift = organAmountDO.getGiftAmount(); - - switch (payType) { - case BALANCE_ONLY: - return payByBalance(price, balance); - case GIFT_ONLY: - return payByGift(price, gift); - case ALIPAY_PC: - return payByAlipay(price); - case WECHAT_NATIVE: - return payByWechat(price); - case BALANCE_AND_GIFT: - return payByBalanceAndGift(price, gift, balance); - case BALANCE_AND_ALIPAY: - return payByBalanceAndExternal(price, balance, PayChannelCodeEnum.ALIPAY_PC); - case BALANCE_AND_WECHAT: - return payByBalanceAndExternal(price, balance, PayChannelCodeEnum.WECHAT_NATIVE); - case GIFT_AND_ALIPAY: - return payByGiftAndExternal(price, gift, PayChannelCodeEnum.ALIPAY_PC); - case GIFT_AND_WECHAT: - return payByGiftAndExternal(price, gift, PayChannelCodeEnum.WECHAT_NATIVE); - case BALANCE_AND_GIFT_AND_ALIPAY: - return payByBalanceAndGiftAndExternal(price, gift, balance, PayChannelCodeEnum.ALIPAY_PC); - case BALANCE_AND_GIFT_AND_WECHAT: - return payByBalanceAndGiftAndExternal(price, gift, balance, PayChannelCodeEnum.WECHAT_NATIVE); - default: - return null; - } - } - - /** - * 获取订购产品的到期时间 - * - * @return - */ - public LocalDate getProductPayExpireTime() { - return endTime; - } - - /** - * 使用现金余额支付 - * - * @param price - * @param balance - */ - private ProductProcessorRespVO payByBalance(BigDecimal price, BigDecimal balance) { - if (balance.compareTo(price) < 0) { - throw new ServiceException(PAY_PRODUCT_BALANCE_INSUFFICIENT); - } - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - String orderNo = String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .orderNo(orderNo) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .accountBalanceSpent(price) - .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) - .initial(initialized) - .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - // 新增收支明细 - // 现金余额减去购买金额 - BigDecimal newBalance = balance.subtract(price); - BigDecimal giftAmount = organAmountDO.getGiftAmount(); - IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() - .organId(organId) - .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) - .orderNo(orderNo) - .purchaseId(purchaseRecordDO.getId()) - .tradeType(TradeTypeEnum.PRODUCT.getCode()) - .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) - .cashAmountChange(price) - .availableAmount(newBalance.add(giftAmount)) - .amount(newBalance) - .giftAmount(giftAmount) - .build(); - - // 更新余额 - organAmountDO.setAmount(newBalance); - organAmountDO.setGiftAmount(giftAmount); - return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO, delayRespVOList); - } - - /** - * 使用赠送金余额支付 - * - * @param price - * @param gift - */ - private ProductProcessorRespVO payByGift(BigDecimal price, BigDecimal gift) { - if (gift.compareTo(price) < 0) { - throw new ServiceException(PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT); - } - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - String orderNo = String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .orderNo(orderNo) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayProductChannelCodeEnum.GIFT_ONLY.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .accountBalanceSpent(price) - .isInvocing(null) - .initial(initialized) - .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - // 新增收支明细 - // 赠送金余额减去购买金额 - BigDecimal newGift = gift.subtract(price); - BigDecimal amount = organAmountDO.getAmount(); - IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() - .organId(organId) - .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) - .orderNo(orderNo) - .purchaseId(purchaseRecordDO.getId()) - .tradeType(TradeTypeEnum.PRODUCT.getCode()) - .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) - .giftAmountChange(price) - .availableAmount(amount.add(newGift)) - .amount(amount) - .giftAmount(newGift) - .build(); - - // 更新余额 - organAmountDO.setAmount(amount); - organAmountDO.setGiftAmount(newGift); - return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO, delayRespVOList); - } - - /** - * 使用支付宝支付 - * - * @param price - * @return - */ - private ProductProcessorRespVO payByAlipay(BigDecimal price) { - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - // 新增购买记录 - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayProductChannelCodeEnum.ALIPAY_PC.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .externalSpent(price) - .initial(initialized) - .status(PurchaseRecordStatusEnum.PENDING.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - - // 新增第三方支付订单 - PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.ALIPAY_PC); - return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null, delayRespVOList); - } - - /** - * 使用微信支付 - * - * @param price - * @return - */ - private ProductProcessorRespVO payByWechat(BigDecimal price) { - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayProductChannelCodeEnum.WECHAT_NATIVE.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .externalSpent(price) - .initial(initialized) - .status(PurchaseRecordStatusEnum.PENDING.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - - // 新增第三方支付订单 - PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.WECHAT_NATIVE); - return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null, delayRespVOList); - } - - /** - * 使用现在余额+赠送金余额支付 - * - * @param price - * @param gift - * @param balance - */ - private ProductProcessorRespVO payByBalanceAndGift(BigDecimal price, BigDecimal gift, BigDecimal balance) { - BigDecimal remaining = price; - BigDecimal usedGift = BigDecimal.ZERO; - BigDecimal usedBalance = BigDecimal.ZERO; - - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - String orderNo = String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .orderNo(orderNo) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) - .initial(initialized) - .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - - // 是否只用了赠送金 - boolean justPayGift = true; - // 先用赠送金 - if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { - usedGift = remaining.min(gift); - remaining = remaining.subtract(usedGift); - purchaseRecordDO.setGiftMoneySpent(usedGift); - } - - // 再用余额 - if (remaining.compareTo(BigDecimal.ZERO) > 0 && balance != null && balance.compareTo(BigDecimal.ZERO) > 0) { - usedBalance = remaining.min(balance); - remaining = remaining.subtract(usedBalance); - purchaseRecordDO.setAccountBalanceSpent(usedBalance); - justPayGift = false; - } - - // 剩下走第三方 - if (remaining.compareTo(BigDecimal.ZERO) > 0) { - throw new ServiceException(PAY_PRODUCT_AVAILABLE_BALANCE_INSUFFICIENT); - } - - // 新增收支明细 - // 减去用掉的得到剩下的 - BigDecimal newAmounta = balance.subtract(usedBalance); - BigDecimal newGift = gift.subtract(usedGift); - IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() - .organId(organId) - .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) - .orderNo(orderNo) - .purchaseId(purchaseRecordDO.getId()) - .tradeType(TradeTypeEnum.PRODUCT.getCode()) - .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) - .cashAmountChange(usedBalance) - .giftAmountChange(usedGift) - .availableAmount(newAmounta.add(newGift)) - .amount(newAmounta) - .giftAmount(newGift) - .build(); - - // 如果只用赠送金那么不可开票 - if (justPayGift) { - purchaseRecordDO.setIsInvocing(null); - } - - // 更新余额 - organAmountDO.setAmount(newAmounta); - organAmountDO.setGiftAmount(newGift); - return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO, delayRespVOList); - } - - /** - * 使用现金余额+第三方 - * - * @param price - * @param balance - */ - private ProductProcessorRespVO payByBalanceAndExternal(BigDecimal price, BigDecimal balance, PayChannelCodeEnum payChannelCodeEnum) { - BigDecimal remaining = price; - BigDecimal usedBalance = BigDecimal.ZERO; - - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - String orderNo = String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .orderNo(orderNo) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_WECHAT.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .initial(initialized) - .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - - // 先用余额 - if (balance != null && balance.compareTo(BigDecimal.ZERO) > 0) { - usedBalance = remaining.min(balance); - remaining = remaining.subtract(usedBalance); - purchaseRecordDO.setAccountBalanceSpent(usedBalance); - } - - // 新增收支明细 - // 减去用掉的得到剩下的 - BigDecimal newAmounta = balance.subtract(usedBalance); - BigDecimal giftAmount = organAmountDO.getGiftAmount(); - IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() - .organId(organId) - .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) - .orderNo(orderNo) - .purchaseId(purchaseRecordDO.getId()) - .tradeType(TradeTypeEnum.PRODUCT.getCode()) - .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) - .cashAmountChange(usedBalance) - .availableAmount(newAmounta.add(giftAmount)) - .amount(newAmounta) - .giftAmount(giftAmount) - .build(); - - // 剩下走第三方 - PayOrderDO payOrderDO = null; - if (remaining.compareTo(BigDecimal.ZERO) > 0) { - payOrderDO = goExternalPay(purchaseId, remaining, payChannelCodeEnum); - purchaseRecordDO.setExternalSpent(remaining); - purchaseRecordDO.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); - } - - // 更新余额 - organAmountDO.setAmount(newAmounta); - organAmountDO.setGiftAmount(giftAmount); - return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList); - } - - /** - * 使用赠送金余额+第三方 - * - * @param price - * @param gift - */ - private ProductProcessorRespVO payByGiftAndExternal(BigDecimal price, BigDecimal gift, PayChannelCodeEnum payChannelCodeEnum) { - BigDecimal remaining = price; - BigDecimal usedGift = BigDecimal.ZERO; - - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - String orderNo = String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .orderNo(orderNo) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .initial(initialized) - .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - - // 是否只用赠送金 - boolean justPayGift = true; - // 先用赠送金 - if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { - usedGift = remaining.min(gift); - remaining = remaining.subtract(usedGift); - purchaseRecordDO.setGiftMoneySpent(usedGift); - } - - // 新增收支明细 - // 减去用掉的得到剩下的 - BigDecimal newGift = gift.subtract(usedGift); - BigDecimal amount = organAmountDO.getAmount(); - IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() - .organId(organId) - .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) - .orderNo(orderNo) - .purchaseId(purchaseId) - .tradeType(TradeTypeEnum.PRODUCT.getCode()) - .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) - .giftAmountChange(usedGift) - .availableAmount(amount.add(newGift)) - .amount(amount) - .giftAmount(newGift) - .build(); - - // 剩下走第三方 - PayOrderDO payOrderDO = null; - if (remaining.compareTo(BigDecimal.ZERO) > 0) { - payOrderDO = goExternalPay(purchaseId, remaining, payChannelCodeEnum); - purchaseRecordDO.setExternalSpent(remaining); - purchaseRecordDO.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); - justPayGift = false; - } - - // 如果只用赠送金那么不可开票 - if (justPayGift) { - purchaseRecordDO.setIsInvocing(null); - } - - // 更新余额 - organAmountDO.setAmount(amount); - organAmountDO.setGiftAmount(newGift); - return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList); - } - - /** - * 使用现在余额+赠送金余额支付 - * - * @param price - * @param gift - * @param balance - */ - private ProductProcessorRespVO payByBalanceAndGiftAndExternal(BigDecimal price, BigDecimal gift, BigDecimal balance, PayChannelCodeEnum payChannelCodeEnum) { - BigDecimal remaining = price; - BigDecimal usedGift = BigDecimal.ZERO; - BigDecimal usedBalance = BigDecimal.ZERO; - - // 新增购买记录 - long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - String orderNo = String.format(PayConstants.PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT, purchaseId); - PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() - .id(purchaseId) - .organId(organId) - .productId(productsDO.getId()) - .productName(productsDO.getProductName()) - .orderNo(orderNo) - .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) - .purchaseDurationUnit(productsDetailDO.getDurationUnit()) - .startTime(startTime) - .endTime(endTime) - .paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode()) - .totalAmount(price) - .originalAmount(originalPrice) - .monthAverageAmount(monthAverageAmount) - .initial(initialized) - .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) - .build(); - // 记录前序购买记录id - if (ObjectUtil.isNotNull(previousPurchaseRecord)) { - purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); - } - - // 是否只用了赠送金 - boolean justPayGift = true; - // 先用赠送金 - if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { - usedGift = remaining.min(gift); - remaining = remaining.subtract(usedGift); - purchaseRecordDO.setGiftMoneySpent(usedGift); - } - - // 再用余额 - if (remaining.compareTo(BigDecimal.ZERO) > 0 && balance != null && balance.compareTo(BigDecimal.ZERO) > 0) { - usedBalance = remaining.min(balance); - remaining = remaining.subtract(usedBalance); - purchaseRecordDO.setAccountBalanceSpent(usedBalance); - justPayGift = false; - } - - // 新增收支明细 - // 减去用掉的得到剩下的 - BigDecimal newAmounta = balance.subtract(usedBalance); - BigDecimal newGift = gift.subtract(usedGift); - IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() - .organId(organId) - .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) - .orderNo(orderNo) - .purchaseId(purchaseId) - .tradeType(TradeTypeEnum.PRODUCT.getCode()) - .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) - .cashAmountChange(usedBalance) - .giftAmountChange(usedGift) - .availableAmount(newAmounta.add(newGift)) - .amount(newAmounta) - .giftAmount(newGift) - .build(); - - // 剩下走第三方 - PayOrderDO payOrderDO = null; - if (remaining.compareTo(BigDecimal.ZERO) > 0) { - payOrderDO = goExternalPay(purchaseId, remaining, payChannelCodeEnum); - purchaseRecordDO.setExternalSpent(remaining); - purchaseRecordDO.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); - justPayGift = false; - } - - // 如果只用赠送金那么不可开票 - if (justPayGift) { - purchaseRecordDO.setIsInvocing(null); - } - - // 更新余额 - organAmountDO.setAmount(newAmounta); - organAmountDO.setGiftAmount(newGift); - return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList); - } - - /** - * 外部支付 - * - * @param purchaseId - * @param price - * @param payChannelCodeEnum - * @return - */ - private PayOrderDO goExternalPay(Long purchaseId, BigDecimal price, PayChannelCodeEnum payChannelCodeEnum) { - PayOrderDO payOrderDO = new PayOrderDO(); - - Long payOrderId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); - Long organId = SecurityFrameworkUtils.getUserOrganId(); - - payOrderDO.setId(payOrderId); - payOrderDO.setPayNo(String.format(PayConstants.PAY_ORDER_PREFIX_FORMAT, payOrderId)); - payOrderDO.setStatus(PayOrderStatusEnum.WAITING); - payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime())); - payOrderDO.setPrice(price); - payOrderDO.setSubject(productsDO.getProductName()); - payOrderDO.setChannelCode(payChannelCodeEnum); - payOrderDO.setOrganId(organId); - payOrderDO.setPurchaseId(purchaseId); - payOrderDO.setTradeType(TradeTypeEnum.PRODUCT.getCode()); - return payOrderDO; - } - - /** - * 计算结束时间 - * - * @param duration - * @param durationUnit - * @return - */ - private LocalDate calculateEndTime(Integer duration, Integer durationUnit) { - LocalDate today = LocalDate.now(); - // 1、 计算延期后的实际有效期 - LocalDate maxDelayEnd = endTime; - if (CollUtil.isNotEmpty(delayRespVOList)) { - // 找到延期记录中最大的 delayTime 作为最终有效期 - maxDelayEnd = delayRespVOList.stream() - .map(ProductDelayRespVO::getDelayTime) - .max(LocalDate::compareTo) - .orElse(endTime); - } - - // 2、 计算有效期叠加的起点 - LocalDate purchaseStart; - if (today.isAfter(maxDelayEnd)) { - // 已过期 → 从今天开始累加 - purchaseStart = today; - } else { - // 未过期 → 从 maxDelayEnd 的下一天开始累加 - purchaseStart = maxDelayEnd; - } - - // 3、加正常时间 - if (ObjectUtil.isNotNull(duration) && durationUnit != null) { - endTime = addTime(purchaseStart, duration, durationUnit); - } - - // 4、扣掉延期天数 - long delayDays = 0L; - if (CollUtil.isNotEmpty(delayRespVOList)) { - delayDays = calculateDelayDays(startTime, delayRespVOList); - } - - if (delayDays > 0) { - endTime = endTime.minusDays(delayDays); - } - - if (endTime.isBefore(today)) { - throw ServiceExceptionUtil.exception(PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR, LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN)); - } - - return endTime; - } - - /** - * 计算总延期天数(合并区间,避免重叠/不连续) - */ - private static long calculateDelayDays(LocalDate startTime, List delayRecords) { - // 1. 生成延期区间并打上备注 - List intervals = buildIntervals(startTime, delayRecords); - if (CollUtil.isEmpty(intervals)) { - return 0L; - } - - // 2. 合并区间 - List merged = mergeIntervals(intervals); - - // 3. 计算天数 - return calculateTotalDays(merged); - } - - - /** - * 根据延期记录生成扣减区间,同时写入备注 - */ - private static List buildIntervals(LocalDate startTime, List delayRecords) { - List intervals = new ArrayList<>(); - - for (ProductDelayRespVO record : delayRecords) { - LocalDate delayStart = record.getEndTime().plusDays(1); - LocalDate delayEnd = record.getDelayTime(); - - if (delayEnd.isBefore(delayStart)) { - record.setRemark("无扣减"); - continue; // 异常数据,忽略 - } - - LocalDate intervalEnd; - if (!delayEnd.isAfter(startTime)) { - // 情况1:延期结束在计算结束之前 → 整段都扣 - intervalEnd = delayEnd; - } else { - // 情况2:延期结束晚于计算结束 → 扣到 startTime 为止 - intervalEnd = startTime; - } - - long deductDays = ChronoUnit.DAYS.between(delayStart, intervalEnd) + 1; - if (deductDays > 0) { - record.setRemark("扣减" + deductDays + "天"); - intervals.add(new DateIntervalDTO(delayStart, intervalEnd)); - } else { - record.setRemark("无扣减"); - } - } - - return intervals; - } - - /** - * 合并区间(避免重叠/相邻) - */ - private static List mergeIntervals(List intervals) { - intervals.sort(Comparator.comparing(DateIntervalDTO::getStart)); - - List merged = new ArrayList<>(); - DateIntervalDTO current = intervals.get(0); - - for (int i = 1; i < intervals.size(); i++) { - DateIntervalDTO next = intervals.get(i); - if (!next.getStart().isAfter(current.getEnd().plusDays(1))) { - // 重叠或相邻 → 合并 - current.setEnd(current.getEnd().isAfter(next.getEnd()) ? current.getEnd() : next.getEnd()); - } else { - merged.add(current); - current = next; - } - } - merged.add(current); - - return merged; - } - - /** - * 计算合并区间的总天数 - */ - private static long calculateTotalDays(List merged) { - long totalDays = 0L; - for (DateIntervalDTO interval : merged) { - totalDays += ChronoUnit.DAYS.between(interval.getStart(), interval.getEnd()) + 1; - } - return totalDays; - } - - /** - * 基于时长和单位累加time - * - * @param time - * @param duration - * @param unit - * @return - */ - private static LocalDate addTime(LocalDate time, int duration, int unit) { - switch (ProductDurationUnitEnum.fromStatus(unit)) { - case YEAR: - return time.plusYears(duration); - case MONTH: - return time.plusMonths(duration); - default: - return time; - } - } - - /** - * 计算有首购记录的产品价格 - * - * @return - */ - private BigDecimal computePrice() { - // 月均单价 - monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount(); - - // 首购的定价 - BigDecimal initialPurchaseRecordTotalAmount = orgInitialPurchaseRecord.getTotalAmount(); - - // 定价规则的折扣 - BigDecimal discount = productsDetailDO.getDiscount(); - if (ObjectUtil.isNull(discount)) { - discount = BigDecimal.valueOf(100); - } - - BigDecimal totalAmount; - /** - * ①:续费订购满1年: - * 原始价格 = 订购总价 * duration - * 现价 = 原始价格 * 折扣 /100,保留两位小数 - * ②:不满1年按月份计算 - * 原始价格 = 月均单价 * duration - * 现价 = 原始价格 * 折扣 /100,保留两位小数 - */ - switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) { - case YEAR -> { - originalPrice = initialPurchaseRecordTotalAmount - .multiply(BigDecimal.valueOf(productsDetailDO.getDuration())); - totalAmount = originalPrice - .multiply(discount) - .divide(new BigDecimal(100), 2, RoundingMode.DOWN); - } - case MONTH -> { - originalPrice = monthAverageAmount - .multiply(BigDecimal.valueOf(productsDetailDO.getDuration())); - totalAmount = originalPrice - .multiply(discount) - .divide(new BigDecimal(100), 2, RoundingMode.DOWN); - } - default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); - } - - return totalAmount; - } - - /** - * 计算首次订购的价格 - */ - private BigDecimal computeInitialProductPayAmount() { - // 订购数量 - Integer duration = productsDetailDO.getDuration(); - // 产品订购价格 - BigDecimal orgPurchasePrice = productsDO.getPrice(); - // 折扣 - BigDecimal discount = productsDetailDO.getDiscount(); - if (ObjectUtil.isNull(discount)) { - discount = BigDecimal.valueOf(100); - } - - BigDecimal totalAmount; - switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) { - case YEAR -> { - // 原价 - originalPrice = orgPurchasePrice.multiply(BigDecimal.valueOf(duration)); - // 折扣价格 = 原价 * 折扣 - totalAmount = originalPrice - .multiply(discount) - .divide(new BigDecimal(100), 2, RoundingMode.DOWN); - // 月均单价 = 折扣价格 / 10 - monthAverageAmount = originalPrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN); - } - case MONTH -> { - // 月均单价 = 订购价格 / 10 - monthAverageAmount = orgPurchasePrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN); - // 原价 - originalPrice = monthAverageAmount.multiply(BigDecimal.valueOf(duration)); - // 折扣价格 = 原价 * 折扣 - totalAmount = originalPrice.multiply(discount) - .divide(new BigDecimal(100), 2, RoundingMode.DOWN); - } - default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); - } - return totalAmount; - } - -} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessorNew.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessorNew.java new file mode 100644 index 000000000..2e776e3e4 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessorNew.java @@ -0,0 +1,593 @@ +package com.cf.imes.module.system.service.pay; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil; +import com.cf.imes.framework.id.core.util.SnowflakeSerialNoWorker3rd; +import com.cf.imes.framework.pay.config.ChenfengPayConfig; +import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO; +import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; +import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO; +import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO; +import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; +import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO; +import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; +import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; +import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO; +import com.cf.imes.module.system.enums.pay.IncomeExpenseTypeEnum; +import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; +import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum; +import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnumNew; +import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; +import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum; +import com.cf.imes.module.system.enums.pay.TradeTypeEnum; +import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties; +import com.cf.imes.module.system.service.pay.dto.DateIntervalDTO; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_BALANCE_INSUFFICIENT; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR; + +/** + * 产品购买类型处理器 + * + * @author Gqr + * @since 2025/8/14 10:54 + */ +public class PayProductProcessorNew { + + // 当次订购的产品 + private ProductsDO productsDO; + + // 当次订购的定价规则 + private ProductsDetailDO productsDetailDO; + + // 组织产品的首次订购记录 + private PurchaseRecordDO orgInitialPurchaseRecord; + + // 当前组织资金信息 + private OrganAmountDO organAmountDO; + + // 组织id + private Long organId; + + // 支付配置 + private ChenfengPayConfig payConfig; + + // id生成规则配置 + private MybatisIdProperties mybatisIdProperties; + + // id生成器 + private SnowflakeSerialNoWorker3rd snowflakeSerialNoWorker3rd; + + // 标记本次订购是否首次 + private boolean initialized = false; + + // 前序购买记录的延期记录 + private List delayRespVOList; + + // 前序购买记录 + private PurchaseRecordDO previousPurchaseRecord; + + // 本地订购产品的开始时间 + private LocalDate startTime = LocalDate.now(); + + // 产品结束时间 + private LocalDate endTime; + + // 本地订购产品的月均单价 + private BigDecimal monthAverageAmount = BigDecimal.ZERO; + + // 原始价格 + private BigDecimal originalPrice; + + // 前序购买记录的id + private Long previousPurchaseRecordId; + + // 产品支付价格 + private BigDecimal price; + + // 组织赠送金 + private BigDecimal gift; + + // 组织现金 + private BigDecimal balance; + + // 使用赠送金 + private BigDecimal usedGift; + + // 使用余额 + private BigDecimal usedBalance; + + // 支付类型 + private Integer payType; + + // 当次购买记录DO + private PurchaseRecordDO purchaseRecordDO; + + // 当次收支记录DO + private IncomeExpenseDetailsDO incomeExpenseDetailsDO; + + // 当次支付订单DO + private PayOrderDO payOrderDO; + + // 是否只用了赠送金 + private boolean justPayGift = false; + + public PayProductProcessorNew(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, PreviouProductDelayRespVO previouProductDelayRespVO, + OrganAmountDO organAmountDO, Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties, SnowflakeSerialNoWorker3rd snowflakeSerialNoWorker3rd) { + this.productsDO = productsDO; + this.productsDetailDO = productsDetailDO; + this.orgInitialPurchaseRecord = orgInitialPurchaseRecord; + if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) { + delayRespVOList = previouProductDelayRespVO.getDelayRespVOList(); + previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); + previousPurchaseRecordId = previousPurchaseRecord.getId(); + // 上一次订购产品的结束时间进行计算 + endTime = previousPurchaseRecord.getEndTime(); + endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit()); + } else { + // 首次订购正常从今天开始累加算世界时间 + endTime = addTime(startTime, productsDetailDO.getDuration(), productsDetailDO.getDurationUnit()); + } + this.organAmountDO = organAmountDO; + this.organId = organId; + this.payConfig = payConfig; + this.mybatisIdProperties = mybatisIdProperties; + this.snowflakeSerialNoWorker3rd = snowflakeSerialNoWorker3rd; + } + + /** + * 根据产品支付类型解析出对应的信息, + * + * @param payType + */ + public ProductProcessorRespVO processPayment(Integer payType) { + // 首次购买记录存在则需要做计算,否则直接用定价规则的现价 + boolean initialPurchaseExist = ObjectUtil.isNotNull(orgInitialPurchaseRecord); + if (initialPurchaseExist) { + price = computePrice(); + } else { + price = computeInitialProductPayAmount(); + // 首次购买记录是空,那么当前就是首次订购 + initialized = true; + } + // 当前组织的余额 + balance = organAmountDO.getAmount(); + // 当前组织的赠送金 + gift = organAmountDO.getGiftAmount(); + this.payType = payType; + + // 按序扣款 + deduction(); + + // 根据扣款情况生成购买记录和收支记录 + generatePurchaseRecordAndIncomeRecord(); + + return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList); + } + + /** + * 按顺序扣款 + */ + private void deduction(){ + // 有赠送金先扣赠送金 + if (PayProductChannelCodeEnumNew.hasGift(payType)) { + payByGift(); + if (price.compareTo(BigDecimal.ZERO) == 0) { + // 赠送金直接扣完了,开出的购买记录为不可开票 + justPayGift = true; + return; + } + } + + // 有现金扣现金 + if(PayProductChannelCodeEnumNew.hasBalance(payType)) { + payByBalance(); + if (price.compareTo(BigDecimal.ZERO) == 0) { + return; + } + } + + // 有第三方支付走第三方 + if (PayProductChannelCodeEnumNew.hasAlipay(payType) || PayProductChannelCodeEnumNew.hasWechat(payType)) { + payByExternal(); + } + } + + /** + * 赠送金支付 + */ + private void payByGift() { + // 赠送金余额减去购买金额 + usedGift = price.min(gift); + + price = price.subtract(usedGift); + + // 更新余额 + organAmountDO.setAmount(organAmountDO.getAmount()); + organAmountDO.setGiftAmount(gift.subtract(usedGift)); + } + + /** + * 余额支付 + */ + private void payByBalance() { + //现金余额减去购买金额 + usedBalance = price.min(balance); + + price = price.subtract(usedBalance); + + // 更新余额 + organAmountDO.setAmount(balance.subtract(usedBalance)); + organAmountDO.setGiftAmount(organAmountDO.getGiftAmount()); + } + + /** + * 根据扣款情况生成购买记录和收支记录 + */ + private void generatePurchaseRecordAndIncomeRecord() { + // 扣款价格大于0 + if (price.compareTo(BigDecimal.ZERO) > 0) { + // 只有赠送金 + if (PayProductChannelCodeEnumNew.isGiftOnly(payType)) { + throw new ServiceException(PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT); + } + + // 只有现金/现金+赠送金 + if (PayProductChannelCodeEnumNew.isBalanceOnly(payType) || PayProductChannelCodeEnumNew.isBalanceAndGift(payType)) { + throw new ServiceException(PAY_PRODUCT_BALANCE_INSUFFICIENT); + } + + } + // 扣款完成,生成记录 + // 新增购买记录 + long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); + purchaseRecordDO = PurchaseRecordDO.builder() + .id(purchaseId) + .organId(organId) + .productId(productsDO.getId()) + .productName(productsDO.getProductName()) + .productDetailsId(productsDetailDO.getId()) + .purchaseDuration(productsDetailDO.getDuration()) + .purchaseDurationUnit(productsDetailDO.getDurationUnit()) + .startTime(startTime) + .endTime(endTime) + .paymentMethod(payType) + .totalAmount(price) + .originalAmount(originalPrice) + .monthAverageAmount(monthAverageAmount) + .isInvocing(justPayGift ? null : InvoiceStatusEnum.INVOICABLE.getCode()) + .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) + .previousId(previousPurchaseRecordId) + .build(); + + // 以上检查都通过,走的是第三方支付 + if(ObjectUtil.isNotNull(payOrderDO)) { + // 第三方支付在异步回调后创建收支记录 + + payOrderDO.setPurchaseId(purchaseId); + if (PayProductChannelCodeEnumNew.hasAlipay(payType)) { + payOrderDO.setChannelCode(PayChannelCodeEnum.ALIPAY_PC); + } else if (PayProductChannelCodeEnumNew.hasWechat(payType)) { + payOrderDO.setChannelCode(PayChannelCodeEnum.WECHAT_NATIVE); + } + + // 记录第三方支付金额 + purchaseRecordDO.setExternalSpent(price); + // 购买记录为等待支付 + purchaseRecordDO.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + // 更新为不可开票,等异步回调后修改为可开票 + purchaseRecordDO.setIsInvocing(null); + } else { + BigDecimal amount = organAmountDO.getAmount(); + BigDecimal giftAmount = organAmountDO.getGiftAmount(); + + incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() + .organId(organId) + .businessNo(snowflakeSerialNoWorker3rd.nextSerialNo()) + .purchaseId(purchaseRecordDO.getId()) + .tradeType(TradeTypeEnum.PRODUCT.getCode()) + .incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getCode()) + .cashAmountChange(usedBalance) + .giftAmountChange(usedGift) + .availableAmount(amount.add(giftAmount)) + .amount(amount) + .giftAmount(giftAmount) + .build(); + } + + } + + /** + * 创建第三方支付订单 + */ + private void payByExternal() { + payOrderDO = new PayOrderDO(); + + Long payOrderId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); + Long organId = SecurityFrameworkUtils.getUserOrganId(); + + payOrderDO.setId(payOrderId); + payOrderDO.setStatus(PayOrderStatusEnum.WAITING); + payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime())); + payOrderDO.setPrice(price); + payOrderDO.setSubject(productsDO.getProductName()); + payOrderDO.setOrganId(organId); + payOrderDO.setTradeType(TradeTypeEnum.PRODUCT.getCode()); + } + + /** + * 获取订购产品的到期时间 + * + * @return + */ + public LocalDate getProductPayExpireTime() { + return endTime; + } + + /** + * 计算结束时间 + * + * @param duration + * @param durationUnit + * @return + */ + private LocalDate calculateEndTime(Integer duration, Integer durationUnit) { + LocalDate today = LocalDate.now(); + // 1、 计算延期后的实际有效期 + LocalDate maxDelayEnd = endTime; + if (CollUtil.isNotEmpty(delayRespVOList)) { + // 找到延期记录中最大的 delayTime 作为最终有效期 + maxDelayEnd = delayRespVOList.stream() + .map(ProductDelayRespVO::getDelayTime) + .max(LocalDate::compareTo) + .orElse(endTime); + } + + // 2、 计算有效期叠加的起点 + LocalDate purchaseStart; + if (today.isAfter(maxDelayEnd)) { + // 已过期 → 从今天开始累加 + purchaseStart = today; + } else { + // 未过期 → 从 maxDelayEnd 的下一天开始累加 + purchaseStart = maxDelayEnd; + } + + // 3、加正常时间 + if (ObjectUtil.isNotNull(duration) && durationUnit != null) { + endTime = addTime(purchaseStart, duration, durationUnit); + } + + // 4、扣掉延期天数 + long delayDays = 0L; + if (CollUtil.isNotEmpty(delayRespVOList)) { + delayDays = calculateDelayDays(startTime, delayRespVOList); + } + + if (delayDays > 0) { + endTime = endTime.minusDays(delayDays); + } + + if (endTime.isBefore(today)) { + throw ServiceExceptionUtil.exception(PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR, LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN)); + } + + return endTime; + } + + /** + * 计算总延期天数(合并区间,避免重叠/不连续) + */ + private static long calculateDelayDays(LocalDate startTime, List delayRecords) { + // 1. 生成延期区间并打上备注 + List intervals = buildIntervals(startTime, delayRecords); + if (CollUtil.isEmpty(intervals)) { + return 0L; + } + + // 2. 合并区间 + List merged = mergeIntervals(intervals); + + // 3. 计算天数 + return calculateTotalDays(merged); + } + + + /** + * 根据延期记录生成扣减区间,同时写入备注 + */ + private static List buildIntervals(LocalDate startTime, List delayRecords) { + List intervals = new ArrayList<>(); + + for (ProductDelayRespVO record : delayRecords) { + LocalDate delayStart = record.getEndTime().plusDays(1); + LocalDate delayEnd = record.getDelayTime(); + + if (delayEnd.isBefore(delayStart)) { + record.setRemark("无扣减"); + continue; // 异常数据,忽略 + } + + LocalDate intervalEnd; + if (!delayEnd.isAfter(startTime)) { + // 情况1:延期结束在计算结束之前 → 整段都扣 + intervalEnd = delayEnd; + } else { + // 情况2:延期结束晚于计算结束 → 扣到 startTime 为止 + intervalEnd = startTime; + } + + long deductDays = ChronoUnit.DAYS.between(delayStart, intervalEnd) + 1; + if (deductDays > 0) { + record.setRemark("扣减" + deductDays + "天"); + intervals.add(new DateIntervalDTO(delayStart, intervalEnd)); + } else { + record.setRemark("无扣减"); + } + } + + return intervals; + } + + /** + * 合并区间(避免重叠/相邻) + */ + private static List mergeIntervals(List intervals) { + intervals.sort(Comparator.comparing(DateIntervalDTO::getStart)); + + List merged = new ArrayList<>(); + DateIntervalDTO current = intervals.get(0); + + for (int i = 1; i < intervals.size(); i++) { + DateIntervalDTO next = intervals.get(i); + if (!next.getStart().isAfter(current.getEnd().plusDays(1))) { + // 重叠或相邻 → 合并 + current.setEnd(current.getEnd().isAfter(next.getEnd()) ? current.getEnd() : next.getEnd()); + } else { + merged.add(current); + current = next; + } + } + merged.add(current); + + return merged; + } + + /** + * 计算合并区间的总天数 + */ + private static long calculateTotalDays(List merged) { + long totalDays = 0L; + for (DateIntervalDTO interval : merged) { + totalDays += ChronoUnit.DAYS.between(interval.getStart(), interval.getEnd()) + 1; + } + return totalDays; + } + + /** + * 基于时长和单位累加time + * + * @param time + * @param duration + * @param unit + * @return + */ + private static LocalDate addTime(LocalDate time, int duration, int unit) { + switch (ProductDurationUnitEnum.fromStatus(unit)) { + case YEAR: + return time.plusYears(duration); + case MONTH: + return time.plusMonths(duration); + default: + return time; + } + } + + /** + * 计算有首购记录的产品价格 + * + * @return + */ + private BigDecimal computePrice() { + // 月均单价 + monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount(); + + // 首购的定价 + BigDecimal initialPurchaseRecordTotalAmount = orgInitialPurchaseRecord.getTotalAmount(); + + // 定价规则的折扣 + BigDecimal discount = productsDetailDO.getDiscount(); + if (ObjectUtil.isNull(discount)) { + discount = BigDecimal.valueOf(100); + } + + BigDecimal totalAmount; + /** + * ①:续费订购满1年: + * 原始价格 = 订购总价 * duration + * 现价 = 原始价格 * 折扣 /100,保留两位小数 + * ②:不满1年按月份计算 + * 原始价格 = 月均单价 * duration + * 现价 = 原始价格 * 折扣 /100,保留两位小数 + */ + switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) { + case YEAR -> { + originalPrice = initialPurchaseRecordTotalAmount + .multiply(BigDecimal.valueOf(productsDetailDO.getDuration())); + totalAmount = originalPrice + .multiply(discount) + .divide(new BigDecimal(100), 2, RoundingMode.DOWN); + } + case MONTH -> { + originalPrice = monthAverageAmount + .multiply(BigDecimal.valueOf(productsDetailDO.getDuration())); + totalAmount = originalPrice + .multiply(discount) + .divide(new BigDecimal(100), 2, RoundingMode.DOWN); + } + default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); + } + + return totalAmount; + } + + /** + * 计算首次订购的价格 + */ + private BigDecimal computeInitialProductPayAmount() { + // 订购数量 + Integer duration = productsDetailDO.getDuration(); + // 产品订购价格 + BigDecimal orgPurchasePrice = productsDO.getPrice(); + // 折扣 + BigDecimal discount = productsDetailDO.getDiscount(); + if (ObjectUtil.isNull(discount)) { + discount = BigDecimal.valueOf(100); + } + + BigDecimal totalAmount; + switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) { + case YEAR -> { + // 原价 + originalPrice = orgPurchasePrice.multiply(BigDecimal.valueOf(duration)); + // 折扣价格 = 原价 * 折扣 + totalAmount = originalPrice + .multiply(discount) + .divide(new BigDecimal(100), 2, RoundingMode.DOWN); + // 月均单价 = 折扣价格 / 10 + monthAverageAmount = originalPrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN); + } + case MONTH -> { + // 月均单价 = 订购价格 / 10 + monthAverageAmount = orgPurchasePrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN); + // 原价 + originalPrice = monthAverageAmount.multiply(BigDecimal.valueOf(duration)); + // 折扣价格 = 原价 * 折扣 + totalAmount = originalPrice.multiply(discount) + .divide(new BigDecimal(100), 2, RoundingMode.DOWN); + } + default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); + } + return totalAmount; + } + +} diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/incomeandexpense/IncomeExpenseDetailsMapper.xml b/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/incomeandexpense/IncomeExpenseDetailsMapper.xml new file mode 100644 index 000000000..8c220cd1d --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/incomeandexpense/IncomeExpenseDetailsMapper.xml @@ -0,0 +1,49 @@ + + + + + \ No newline at end of file diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/pay/PayOrderMapper.xml b/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/pay/PayOrderMapper.xml index 213ae69e6..7ed52957e 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/pay/PayOrderMapper.xml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/pay/PayOrderMapper.xml @@ -2,10 +2,11 @@