mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-13 05:12:07 +08:00
1、软件购买接入新的渠道code基于二进制的解析;2、账户明细、销售管理、软件购买列表查询和回调数据调整;
This commit is contained in:
+27
-1
@@ -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;
|
||||
|
||||
|
||||
+9
-6
@@ -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;
|
||||
}
|
||||
|
||||
-3
@@ -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 = "订单总额")
|
||||
|
||||
+17
-2
@@ -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;
|
||||
}
|
||||
|
||||
+5
-12
@@ -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;
|
||||
|
||||
+5
-2
@@ -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;
|
||||
|
||||
+3
@@ -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 = "产品购买金额不能超过十万")
|
||||
|
||||
+10
-7
@@ -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;
|
||||
}
|
||||
|
||||
-2
@@ -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;
|
||||
|
||||
|
||||
+65
@@ -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;
|
||||
}
|
||||
-5
@@ -63,11 +63,6 @@ public class PayOrderDO extends BaseDO {
|
||||
*/
|
||||
private Long purchaseId;
|
||||
|
||||
/**
|
||||
* 支付订单号:P + 当前id + 时间戳(yyyyMMddHHmmss)
|
||||
*/
|
||||
private String payNo;
|
||||
|
||||
/**
|
||||
* 交易类型
|
||||
*/
|
||||
|
||||
+8
@@ -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;
|
||||
}
|
||||
|
||||
+4
-2
@@ -44,13 +44,14 @@ public interface PurchaseRecordMapper extends BaseMapperX<PurchaseRecordDO> {
|
||||
.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<PurchaseRecordDO> {
|
||||
*/
|
||||
default PageResult<PurchaseRecordDO> getSaleDetailsPage(SalesDetailsPageReqVO pageReqVO) {
|
||||
return selectPage(pageReqVO, new LambdaQueryWrapperX<PurchaseRecordDO>()
|
||||
.eqIfPresent(PurchaseRecordDO::getId, pageReqVO.getId())
|
||||
.betweenIfPresent(PurchaseRecordDO::getCreateTime, pageReqVO.getCreateTime())
|
||||
.eqIfPresent(PurchaseRecordDO::getTotalAmount, pageReqVO.getProductPrice())
|
||||
.eqIfPresent(PurchaseRecordDO::getOrganId, pageReqVO.getOrganId())
|
||||
|
||||
+17
-1
@@ -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<IncomeExpenseDet
|
||||
.likeIfPresent(IncomeExpenseDetailsDO::getBusinessNo, pageReqVO.getBusinessNo())
|
||||
.likeIfPresent(IncomeExpenseDetailsDO::getOrderNo, pageReqVO.getOrderNo())
|
||||
.betweenIfPresent(IncomeExpenseDetailsDO::getCreateTime, pageReqVO.getCreateTime())
|
||||
.eqIfPresent(IncomeExpenseDetailsDO::getIncomeExpenseType, pageReqVO.getIncomeExpenseType())
|
||||
.eqIfPresent(IncomeExpenseDetailsDO::getTradeType, pageReqVO.getTradeType())
|
||||
.eqIfPresent(IncomeExpenseDetailsDO::getCashAmountChange, pageReqVO.getRechargeAmount())
|
||||
.eqIfPresent(IncomeExpenseDetailsDO::getGiftAmountChange, pageReqVO.getGiftAmount())
|
||||
|
||||
.orderByDesc(IncomeExpenseDetailsDO::getId);
|
||||
Integer tradeType = pageReqVO.getTradeType();
|
||||
// 如果查的是交易,需要包含手工调账
|
||||
@@ -45,6 +52,15 @@ public interface IncomeExpenseDetailsMapper extends BaseMapperX<IncomeExpenseDet
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户明细分页查询
|
||||
*
|
||||
* @param page
|
||||
* @param reqVO
|
||||
* @return
|
||||
*/
|
||||
IPage<AccountDetailsDO> getAccountDetailsPage(@Param("page") IPage<AccountDetailsDO> page, @Param("reqVO") BalanceDetailsPageReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获取手工调账分页
|
||||
*
|
||||
|
||||
+5
-3
@@ -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()) {
|
||||
|
||||
-4
@@ -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格式
|
||||
|
||||
+128
@@ -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<PayProductChannelCodeEnumNew> parse(int code) {
|
||||
List<PayProductChannelCodeEnumNew> 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("+"));
|
||||
}
|
||||
}
|
||||
+18
-19
@@ -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<BalanceDetailsRespVO> getBalanceDetailsPage(BalanceDetailsPageReqVO pageReqVO) {
|
||||
PageResult<IncomeExpenseDetailsDO> pageResult = incomeExpenseDetailsMapper.selectDetailsPage(pageReqVO);
|
||||
List<IncomeExpenseDetailsDO> pageResultList = pageResult.getList();
|
||||
PageDTO<AccountDetailsDO> page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
|
||||
IPage<AccountDetailsDO> pageResult = incomeExpenseDetailsMapper.getAccountDetailsPage(page, pageReqVO);
|
||||
List<AccountDetailsDO> 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());
|
||||
|
||||
-2
@@ -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("手工调账");
|
||||
|
||||
+11
-3
@@ -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<PurchaseRecordRespVO> respVOS = BeanUtils.toBean(resultList, PurchaseRecordRespVO.class);
|
||||
List<PurchaseRecordRespVO> 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)) {
|
||||
|
||||
+9
@@ -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);
|
||||
|
||||
/**
|
||||
* 产品支付
|
||||
*
|
||||
|
||||
+28
-19
@@ -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<PayProductChannelCodeEnumNew> 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<PurchaseRecordDO> 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<PayOrderDO>()
|
||||
.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<PayOrderDO>()
|
||||
.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<PayOrderDO>()
|
||||
.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());
|
||||
|
||||
-989
@@ -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<ProductDelayRespVO> 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<ProductDelayRespVO> delayRecords) {
|
||||
// 1. 生成延期区间并打上备注
|
||||
List<DateIntervalDTO> intervals = buildIntervals(startTime, delayRecords);
|
||||
if (CollUtil.isEmpty(intervals)) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
// 2. 合并区间
|
||||
List<DateIntervalDTO> merged = mergeIntervals(intervals);
|
||||
|
||||
// 3. 计算天数
|
||||
return calculateTotalDays(merged);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据延期记录生成扣减区间,同时写入备注
|
||||
*/
|
||||
private static List<DateIntervalDTO> buildIntervals(LocalDate startTime, List<ProductDelayRespVO> delayRecords) {
|
||||
List<DateIntervalDTO> 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<DateIntervalDTO> mergeIntervals(List<DateIntervalDTO> intervals) {
|
||||
intervals.sort(Comparator.comparing(DateIntervalDTO::getStart));
|
||||
|
||||
List<DateIntervalDTO> 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<DateIntervalDTO> 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;
|
||||
}
|
||||
|
||||
}
|
||||
+593
@@ -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<ProductDelayRespVO> 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<ProductDelayRespVO> delayRecords) {
|
||||
// 1. 生成延期区间并打上备注
|
||||
List<DateIntervalDTO> intervals = buildIntervals(startTime, delayRecords);
|
||||
if (CollUtil.isEmpty(intervals)) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
// 2. 合并区间
|
||||
List<DateIntervalDTO> merged = mergeIntervals(intervals);
|
||||
|
||||
// 3. 计算天数
|
||||
return calculateTotalDays(merged);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据延期记录生成扣减区间,同时写入备注
|
||||
*/
|
||||
private static List<DateIntervalDTO> buildIntervals(LocalDate startTime, List<ProductDelayRespVO> delayRecords) {
|
||||
List<DateIntervalDTO> 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<DateIntervalDTO> mergeIntervals(List<DateIntervalDTO> intervals) {
|
||||
intervals.sort(Comparator.comparing(DateIntervalDTO::getStart));
|
||||
|
||||
List<DateIntervalDTO> 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<DateIntervalDTO> 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;
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.cf.imes.module.system.dal.mysql.incomeandexpense.IncomeExpenseDetailsMapper">
|
||||
<select id="getAccountDetailsPage" resultType="com.cf.imes.module.system.dal.dataobject.incomeandexpense.AccountDetailsDO">
|
||||
select i.id, i.business_no, i.order_no ,i.trade_type , i.income_expense_type, i.cash_amount_change, i.gift_amount_change, i.creator, i.create_time, i.organ_id ,
|
||||
p.total_amount, p.external_spent, p.account_balance_spent, p.gift_money_spent, p.payment_method, o.channel_code
|
||||
from income_expense_details i
|
||||
left join purchase_record p on i.purchase_id = p.id
|
||||
left join pay_order o on i.order_no = o.id
|
||||
where i.deleted = false
|
||||
<if test="reqVO.organId != null">
|
||||
AND i.organ_id = #{reqVO.organId}
|
||||
</if>
|
||||
<if test="reqVO.businessNo != null and reqVO.businessNo != ''">
|
||||
AND i.business_no = #{reqVO.businessNo}
|
||||
</if>
|
||||
<if test="reqVO.orderNo != null and reqVO.orderNo != ''">
|
||||
AND i.order_no = #{reqVO.orderNo}
|
||||
</if>
|
||||
<if test="reqVO.tradeType != null">
|
||||
<choose>
|
||||
<when test="reqVO.tradeType == @com.cf.imes.module.system.enums.pay.TradeTypeEnum@RECHARGE.code">
|
||||
AND i.trade_type IN (
|
||||
#{reqVO.tradeType},
|
||||
'${@com.cf.imes.module.system.enums.pay.TradeTypeEnum@MANUAL_ADJUST.code}'
|
||||
)
|
||||
</when>
|
||||
<otherwise>
|
||||
AND i.trade_type = #{reqVO.tradeType}
|
||||
</otherwise>
|
||||
</choose>
|
||||
</if>
|
||||
<if test="reqVO.incomeExpenseType != null">
|
||||
AND i.income_expense_type = #{reqVO.incomeExpenseType}
|
||||
</if>
|
||||
<if test="reqVO.createTime != null and reqVO.createTime.size() == 2">
|
||||
AND i.create_time between #{reqVo.createTime[0]} and #{reqVo.createTime[1]}
|
||||
</if>
|
||||
<if test="reqVO.productPrice != null">
|
||||
AND p.total_amount = #{reqVo.productPrice}
|
||||
</if>
|
||||
<if test="reqVO.rechargeAmount != null">
|
||||
AND i.cash_amount_change = #{reqVo.rechargeAmount}
|
||||
</if>
|
||||
<if test="reqVO.giftAmount != null">
|
||||
AND i.gift_amount_change = #{reqVo.giftAmount}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
+2
-1
@@ -2,10 +2,11 @@
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.cf.imes.module.system.dal.mysql.pay.PayOrderMapper">
|
||||
<select id="getRechargeDetailsPage" resultType="com.cf.imes.module.system.dal.dataobject.pay.PayOrderRechargeDetailDO">
|
||||
select o.id, o.organ_id, o.pay_no, o.price, o.purchase_id, o.creator, o.create_time, p.total_amount, p.account_balance_spent, p.gift_money_spent
|
||||
select o.id, o.organ_id, o.pay_no, o.price, o.purchase_id, o.channel_code, o.creator, o.create_time, p.total_amount, p.account_balance_spent, p.gift_money_spent, p.payment_method
|
||||
from pay_order o
|
||||
left join purchase_record p on o.purchase_id = p.id
|
||||
where o.status = '${@com.cf.imes.module.system.enums.pay.PayOrderStatusEnum@SUCCESS.status}'
|
||||
|
||||
<if test="reqVO.organId != null">
|
||||
AND o.organ_id = #{reqVO.organId}
|
||||
</if>
|
||||
|
||||
Reference in New Issue
Block a user