1、创建组织新增购买记录标记为首次购买;2、新增软件购买展示定价列表接口;3、完善软件购买以首次购买时计算的月均单价为基础计算总价流程;

This commit is contained in:
gaoqr
2025-09-17 17:09:52 +08:00
parent 5b60930f8f
commit fa24cdf1ee
15 changed files with 337 additions and 30 deletions
@@ -443,6 +443,7 @@ public class ErrorCodeConstants {
public static final ErrorCode ORG_INVOICE_AGREE_FILE_NULL_ERROR = new ErrorCode(1_002_043_030, "确认开票发票附件不能为空,请上传后再提交");
public static final ErrorCode INVOICE_TITLE_ORG_EXISTS_ERROR = new ErrorCode(1_002_043_031, "组织下已存在发票抬头,请刷新后编辑抬头内容");
public static final ErrorCode INVOICE_TITLE_TAXPAYERID_EXISTS_ERROR = new ErrorCode(1_002_043_032, "纳税人识别号已存在,请检查");
public static final ErrorCode PRODUCTS_NOT_NOLIST = new ErrorCode(1_002_043_033, "当前产品未上架");
//=========== 支付相关 1-002-048-000 ============
public static final ErrorCode PAY_CHANNEL_NOT_SUPPORT = new ErrorCode(1_002_048_000, "暂不支持的支付渠道,请使用其他支付渠道");
@@ -470,6 +471,8 @@ public class ErrorCodeConstants {
public static final ErrorCode PRODUCTS_DURATION_INT_ERR = new ErrorCode(1_002_045_008, "产品定价规则:时长必须为整数且大于 0");
public static final ErrorCode PRODUCTS_OUTPUT_NOT_SUPPORT = new ErrorCode(1_002_045_009, "暂不支持创建购买方式为【产量】的定价规则");
public static final ErrorCode RECHARGE_ACTIVE_NO_EXIST = new ErrorCode(1_002_045_010, "当前赠送金活动不存在");
public static final ErrorCode PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT = new ErrorCode(1_002_045_011, "不支持的时长单位,请检查该定价规则的时长单位");
public static final ErrorCode INITIAL_PRODUCTS_NOT_EXIST_ERROR = new ErrorCode(1_002_045_012, "产品首次购买记录获取失败,请检查组织数据");
//=========== 广告相关 1-002-046-000 ============
public static final ErrorCode ADVERTISEMENT_POSITION_NO_EXIST = new ErrorCode(1_002_046_001, "广告所选放置位置不存在");
@@ -26,13 +26,13 @@ public class OrganAmountRespVO {
* 正常时间,不加赠送时间
*/
@Schema(description = "产品时长")
private String duration;
private Integer duration;
@Schema(description = "产品时长单位")
private Integer durationUnit;
@Schema(description = "赠送时长")
private String giftDuration;
private Integer giftDuration;
@Schema(description = "赠送时长单位")
private Integer giftDurationUnit;
@@ -8,6 +8,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetai
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.service.funds.products.ProductsDetailService;
import com.cf.imes.module.system.service.funds.purchase.PurchaseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -17,6 +18,8 @@ import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.List;
import static com.cf.imes.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 产品明细管理")
@@ -28,6 +31,9 @@ public class ProductsDetailController {
@Resource
private ProductsDetailService productDetailsService;
@Resource
private PurchaseService purchaseService;
@PutMapping("")
@Operation(summary = "新建产品明细")
public CommonResult<Long> createProductsDetails(@Valid @RequestBody ProductDetailsSaveReqVO createReqVO) {
@@ -51,7 +57,7 @@ public class ProductsDetailController {
@GetMapping("/{id}")
@Operation(summary = "查询产品明细")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@Parameter(name = "id", description = "产品明细编号", required = true, example = "1024")
public CommonResult<ProductsDetailDO> getProductsDetail(@PathVariable("id") Long id) {
return success(productDetailsService.get(id));
}
@@ -63,4 +69,13 @@ public class ProductsDetailController {
return success(BeanUtils.toBean(pageResult, ProductDetailsRespVO.class));
}
@GetMapping("/organ/purchase")
@Operation(summary = "查询产品明细分页")
@Parameter(name = "organId", description = "组织编号", required = true, example = "1")
@Parameter(name = "productId", description = "产品编号", required = true, example = "1")
public CommonResult<List<ProductDetailsRespVO>> getOrgProductDetailList(@RequestParam("organId") Long organId, @RequestParam("productId") Long productId) {
List<ProductsDetailDO> pageResult = purchaseService.getOrgProductDetailList(organId, productId);
return success(BeanUtils.toBean(pageResult, ProductDetailsRespVO.class));
}
}
@@ -18,7 +18,7 @@ public class ProductDetailsRespVO {
private Long id;
@Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String productId;
private Long productId;
@Schema(description = "购买方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "购买方式:0时长;1产量")
@ProductType
@@ -13,6 +13,10 @@ import java.time.LocalDate;
@Schema(description = "管理后台 - 组织创建购买产品 Request VO")
@Data
public class OrganCreateProductPurchaseReqVO {
@Schema(description = "产品编号",example = "1")
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "定价规则编号",example = "1")
@NotNull(message = "定价规则编号不能为空")
private Long productDetailsId;
@@ -4,6 +4,7 @@ import com.cf.imes.framework.common.validation.Mobile;
import com.cf.imes.framework.common.validation.NumberValid;
import com.cf.imes.module.system.validation.common.CommonStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import lombok.Data;
import jakarta.validation.constraints.*;
@@ -112,6 +113,7 @@ public class OrganSaveReqVO {
private Integer grayStatus;
@Schema(description = "产品购买列表")
@Size(max = 10, message = "创建组织时最少购买1个产品,最多可购买10个产品")
@Size(max = 10, message = "创建组织时最少需要购买1个产品,最多可购买10个产品")
@Valid
private List<OrganCreateProductPurchaseReqVO> productPurchaseReqVOS;
}
@@ -43,7 +43,7 @@ public class ProductsDetailDO extends BaseDO {
/**
* 正常时间,不加赠送时间
*/
private String duration;
private Integer duration;
/**
* 产品时长单位
@@ -59,7 +59,7 @@ public class ProductsDetailDO extends BaseDO {
/**
* 赠送时间
*/
private String giftDuration;
private Integer giftDuration;
/**
* 赠送时间单位
@@ -64,7 +64,7 @@ public class PurchaseRecordDO extends BaseDO {
/**
* 购买时长
*/
private String purchaseDuration;
private Integer purchaseDuration;
/**
* 购买时长单位
@@ -74,7 +74,7 @@ public class PurchaseRecordDO extends BaseDO {
/**
* 赠送时长
*/
private String giftDuration;
private Integer giftDuration;
/**
* 赠送时间单位
@@ -105,6 +105,16 @@ public class PurchaseRecordDO extends BaseDO {
*/
private BigDecimal totalAmount;
/**
* 原始价格
*/
private BigDecimal originalAmount;
/**
* 月均单价
*/
private BigDecimal monthAverageAmount;
/**
* 现金余额支出
@@ -144,12 +154,13 @@ public class PurchaseRecordDO extends BaseDO {
*/
private String currencyUnit;
/**
* 是否开票
*/
private Integer isInvocing;
/**
* 是否首次订购
*/
private Boolean initial;
}
@@ -29,6 +29,7 @@ public interface ProductsDetailMapper extends BaseMapperX<ProductsDetailDO> {
.eqIfPresent(ProductsDetailDO::getDiscount, reqVO.getDiscount())
.eqIfPresent(ProductsDetailDO::getCurrentPrice, reqVO.getCurrentPrice())
.eqIfPresent(ProductsDetailDO::getIsDiscounted, reqVO.getIsDiscounted())
.eqIfPresent(ProductsDetailDO::getDeleted, false)
);
}
@@ -44,7 +44,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
public Long create(ProductDetailsSaveReqVO createReqVO) {
// 校验是否有这个产品
validateProductExist(createReqVO.getProductId());
validateProductExistAndNotOnList(createReqVO.getProductId());
if (ProductsPurchaseTypeEnum.DURATION.getStatus().equals(createReqVO.getPurchaseType())) {
// 校验时长
@@ -74,7 +74,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
@Override
public void update(ProductDetailsSaveReqVO updateReqVO) {
validateProductExist(updateReqVO.getProductId());
validateProductExistAndNotOnList(updateReqVO.getProductId());
validateProductDetailsExists(updateReqVO.getId());
@@ -105,7 +105,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
ProductsDetailDO productsDetailDO = validateProductDetailsExists(id);
validateProductExist(productsDetailDO.getProductId());
validateProductExistAndNotOnList(productsDetailDO.getProductId());
productDetailsMapper.deleteById(id);
@@ -164,7 +164,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
/**
* 产品是否存在和时效性校验
* 产品是否存在
*
* @param productId
*/
@@ -176,6 +176,26 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
AssertUtils.notEmpty(productsDO, PRODUCTS_NO_EXIST);
if (!productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())) {
throw exception(PRODUCTS_NOT_NOLIST);
}
return productsDO;
}
/**
* 校验产品存在并且未上架
*
* @param productId
* @return
*/
private ProductsDO validateProductExistAndNotOnList(Long productId) {
ProductsDO productsDO = productsMapper.selectOne(new LambdaQueryWrapperX<ProductsDO>()
.eq(ProductsDO::getId, productId)
.eq(ProductsDO::getDeleted, false));
AssertUtils.notEmpty(productsDO, PRODUCTS_NO_EXIST);
if (productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())) {
throw exception(PRODUCTS_DETAIL_NO_UPDATE);
}
@@ -8,6 +8,7 @@ import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseReco
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganCreateProductPurchaseReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import java.math.BigDecimal;
@@ -99,4 +100,20 @@ public interface PurchaseService {
* @return
*/
PageResult<SalesDetailRespVO> getManageSalesDetailsPage(SalesDetailsPageReqVO pageReqVO);
/**
* 获取组织初始产品购买记录
*
* @param organId
* @return
*/
PurchaseRecordDO getOrgInitialPurchaseRecord(Long organId, Long productId);
/**
* 获取组织续费定价规则列表
*
* @param organId
* @return
*/
List<ProductsDetailDO> getOrgProductDetailList(Long organId, Long productId);
}
@@ -5,10 +5,14 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageParam;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.TotalSalesAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordRespVO;
@@ -23,6 +27,7 @@ import com.cf.imes.module.system.dal.mysql.funds.products.ProductsDetailMapper;
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.ProductDurationUnitEnum;
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.funds.products.ProductsDetailService;
@@ -33,12 +38,16 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INITIAL_PRODUCTS_NOT_EXIST_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT;
/**
* @author 软件购买接口 实现类
*/
@@ -125,6 +134,53 @@ public class PurchaseServiceImpl implements PurchaseService {
ProductsDO productsDO = productsDetailService.validateProductExist(productId);
// 3、创建购买记录
// 输入的价格
BigDecimal price = purchaseReqVO.getPrice();
// 月均单价
BigDecimal monthAverageAmount;
// 计算后总价
BigDecimal totalAmount;
/**
* ①:首次订购满1年:
* 总价 = 输入金额 / 12 * (10 * duration)
* 月均单价 = 输入金额 / (10 * duration)
* ②:不满1年按月份计算
* 总价 = 输入金额
* 月均单价 = 输入金额 / duration
*/
Integer duration = productsDetailDO.getDuration();
switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
case YEAR -> {
totalAmount = price.divide(new BigDecimal(1.2), 2, RoundingMode.DOWN);
monthAverageAmount = price.divide(new BigDecimal(duration * 12), 2, RoundingMode.DOWN);
}
case MONTH -> {
if (duration >= 12) {
// 转换成年 + 剩余月
int years = duration / 12;
int remainMonths = duration % 12;
// 整年部分(走年折扣逻辑)
BigDecimal yearAmount = price
.multiply(new BigDecimal(years))
.divide(new BigDecimal("1.2"), 2, RoundingMode.DOWN);
// 剩余月份部分(走月逻辑)
BigDecimal monthAmount = remainMonths == 0
? BigDecimal.ZERO
: price.multiply(new BigDecimal(remainMonths))
.divide(new BigDecimal(12), 2, RoundingMode.DOWN);
totalAmount = yearAmount.add(monthAmount);
} else {
// 月份
totalAmount = price;
}
monthAverageAmount = price.divide(new BigDecimal(duration), 2, RoundingMode.DOWN);
}
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId();
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
.id(purchaseId)
@@ -132,15 +188,18 @@ public class PurchaseServiceImpl implements PurchaseService {
.productId(productId)
.productName(productsDO.getProductName())
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDuration(duration)
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(LocalDate.now())
.endTime(purchaseReqVO.getProductExpireDate())
.paymentMethod(PayProductChannelCodeEnum.ORGAN_CREATE.getCode())
.totalAmount(purchaseReqVO.getPrice())
.totalAmount(totalAmount)
.monthAverageAmount(monthAverageAmount)
.originalAmount(price)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
.initial(true)
.build();
createRecord(purchaseRecordDO);
}
@@ -183,4 +242,84 @@ public class PurchaseServiceImpl implements PurchaseService {
}
return new PageResult<>(resultList, salesDetailPage.getTotal());
}
@Override
public PurchaseRecordDO getOrgInitialPurchaseRecord(Long organId, Long productId) {
return purchaseRecordMapper.selectOne(new LambdaQueryWrapper<PurchaseRecordDO>()
.eq(PurchaseRecordDO::getOrganId, organId)
.eq(PurchaseRecordDO::getProductId, productId)
.eq(PurchaseRecordDO::getInitial, true));
}
@Override
public List<ProductsDetailDO> getOrgProductDetailList(Long organId, Long productId) {
// 校验产品
productsDetailService.validateProductExist(productId);
// 查询产品下的所有定价规则
ProductDetailsPageReqVO pageReqVO = new ProductDetailsPageReqVO();
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
pageReqVO.setProductId(productId);
PageResult<ProductsDetailDO> page = productsDetailService.getPage(pageReqVO);
// 查询组织下的首次订购产品记录
PurchaseRecordDO orgInitialPurchaseRecord = getOrgInitialPurchaseRecord(organId, productId);
AssertUtils.notEmpty(orgInitialPurchaseRecord, INITIAL_PRODUCTS_NOT_EXIST_ERROR);
List<ProductsDetailDO> resultList = new ArrayList<>();
for (ProductsDetailDO detailDO : page.getList()) {
ProductsDetailDO newDetailDO = BeanUtils.toBean(detailDO, ProductsDetailDO.class);
// 月均单价
BigDecimal monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount();
// 折扣
BigDecimal discount = detailDO.getDiscount();
// 需要支付的总价
BigDecimal totalAmount;
// 重新计算购买价格
switch (ProductDurationUnitEnum.fromStatus(detailDO.getDurationUnit())) {
case YEAR -> totalAmount = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(detailDO.getDuration()))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
case MONTH -> {
int months = detailDO.getDuration();
if (months >= 12) {
// 转换成年 + 剩余月
int years = months / 12;
int remainMonths = months % 12;
// 整年部分:用年逻辑(10 个月价)
BigDecimal yearPart = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(years))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
// 剩余月部分:用月逻辑
BigDecimal monthPart = remainMonths == 0
? BigDecimal.ZERO
: monthAverageAmount
.multiply(new BigDecimal(remainMonths))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
totalAmount = yearPart.add(monthPart);
} else {
// 小于 12 个月直接按月逻辑
totalAmount = monthAverageAmount
.multiply(new BigDecimal(months))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
}
}
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
newDetailDO.setCurrentPrice(totalAmount);
resultList.add(newDetailDO);
}
return resultList;
}
}
@@ -74,6 +74,7 @@ import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.util.json.JsonUtils.parseArray;
@@ -252,6 +253,8 @@ public class OrganServiceImpl implements OrganService {
// 创建产品购买记录
List<OrganCreateProductPurchaseReqVO> productPurchaseReqVOS = createReqVO.getProductPurchaseReqVOS();
AssertUtils.notEmpty(productPurchaseReqVOS, ORGAN_CREATE_PRODUCTLIST_EMPTY_ERROR);
// 根据productId去重,初次订购每个产品只能有一种订购
productPurchaseReqVOS = productPurchaseReqVOS.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(OrganCreateProductPurchaseReqVO::getProductId))), ArrayList::new));
purchaseService.createOrgInitProductPurchaseRecord(organId, productPurchaseReqVOS);
return tenant.getId();
@@ -155,8 +155,11 @@ public class PayOrderServiceImpl implements PayOrderService {
// 查询组织余额信息
OrganAmountDO organAmount = organAmountService.validOrganAmount(organId);
// 查询组织产品的首次购买记录
PurchaseRecordDO orgInitialPurchaseRecord = purchaseService.getOrgInitialPurchaseRecord(organId, productsDO.getId());
// 根据购买类型做价格处理
PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, organAmount, organId, payConfig, mybatisIdProperties);
PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, orgInitialPurchaseRecord, organAmount, organId, payConfig, mybatisIdProperties);
ProductProcessorRespVO productProcessorRespVO = payProductProcessor.processPayment(PayProductChannelCodeEnum.fromType(payProductOrderUnifiedReqVO.getProductChannelCode()));
// 记录入库,如果有外部支付发起请求二维码
@@ -3,6 +3,7 @@ package com.cf.imes.module.system.service.pay;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.pay.config.ChenfengPayConfig;
@@ -23,15 +24,17 @@ 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.TradeTypeEnum;
import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INITIAL_PRODUCTS_NOT_EXIST_ERROR;
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;
/**
* 产品购买类型处理器
@@ -45,6 +48,8 @@ public class PayProductProcessor {
private ProductsDetailDO productsDetailDO;
private PurchaseRecordDO orgInitialPurchaseRecord;
private OrganAmountDO organAmountDO;
private Long organId;
@@ -53,10 +58,14 @@ public class PayProductProcessor {
private MybatisIdProperties mybatisIdProperties;
public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, OrganAmountDO organAmountDO,
public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, OrganAmountDO organAmountDO,
Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties) {
this.productsDO = productsDO;
this.productsDetailDO = productsDetailDO;
if (ObjectUtil.isNull(orgInitialPurchaseRecord)) {
throw new ServiceException(INITIAL_PRODUCTS_NOT_EXIST_ERROR);
}
this.orgInitialPurchaseRecord = orgInitialPurchaseRecord;
this.organAmountDO = organAmountDO;
this.organId = organId;
this.payConfig = payConfig;
@@ -70,7 +79,7 @@ public class PayProductProcessor {
*/
public ProductProcessorRespVO processPayment(PayProductChannelCodeEnum payType) {
// 需要支付的价格
BigDecimal price = productsDetailDO.getCurrentPrice();
BigDecimal price = computePrice(orgInitialPurchaseRecord, productsDetailDO);
// 当前组织的余额
BigDecimal balance = organAmountDO.getAmount();
// 当前组织的赠送金
@@ -129,6 +138,8 @@ public class PayProductProcessor {
.startTime(LocalDate.now())
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.accountBalanceSpent(price)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
.build();
@@ -183,6 +194,8 @@ public class PayProductProcessor {
.startTime(LocalDate.now())
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.accountBalanceSpent(price)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
.build();
@@ -235,6 +248,8 @@ public class PayProductProcessor {
.startTime(LocalDate.now())
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.alipaySpent(price)
.build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
@@ -268,6 +283,8 @@ public class PayProductProcessor {
.startTime(LocalDate.now())
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.wechatSpent(price)
.build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
@@ -306,6 +323,8 @@ public class PayProductProcessor {
.startTime(LocalDate.now())
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
.build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
@@ -378,8 +397,10 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(LocalDate.now())
.paymentMethod(payChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode())
.paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
@@ -450,8 +471,10 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(LocalDate.now())
.paymentMethod(payChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode())
.paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
@@ -527,6 +550,8 @@ public class PayProductProcessor {
.startTime(LocalDate.now())
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount())
.build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
@@ -618,17 +643,17 @@ public class PayProductProcessor {
* @param giftDurationUnit
* @return
*/
private static LocalDate calculateEndTime(LocalDate startTime, String duration, Integer durationUnit,
String giftDuration, Integer giftDurationUnit) {
private static LocalDate calculateEndTime(LocalDate startTime, Integer duration, Integer durationUnit,
Integer giftDuration, Integer giftDurationUnit) {
// 加正常时间
if (StringUtils.isNotEmpty(duration) && durationUnit != null) {
startTime = addTime(startTime, Integer.parseInt(duration), durationUnit);
if (ObjectUtil.isNotNull(duration) && durationUnit != null) {
startTime = addTime(startTime, duration, durationUnit);
}
// 加赠送时间
if (StringUtils.isNotEmpty(giftDuration) && giftDurationUnit != null) {
startTime = addTime(startTime, Integer.parseInt(giftDuration), giftDurationUnit);
if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) {
startTime = addTime(startTime, giftDuration, giftDurationUnit);
}
return startTime;
@@ -653,4 +678,68 @@ public class PayProductProcessor {
}
}
/**
* 计算产品价格
*
* @param orgInitialPurchaseRecord 首次购买记录
* @param productsDetailDO 本次购买产品的定价规则
* @return
*/
private BigDecimal computePrice(PurchaseRecordDO orgInitialPurchaseRecord, ProductsDetailDO productsDetailDO) {
// 月均单价
BigDecimal monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount();
// 定价规则的折扣
BigDecimal discount = productsDetailDO.getDiscount();
BigDecimal totalAmount;
/**
* ①:续费订购满1年:
* 总价 = 月均单价 * 10 * duration * 折扣 /100,保留两位小数
* ②:不满1年按月份计算
* 总价 = 月均单价 * duration * 折扣 /100,保留两位小数
*/
switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
case YEAR -> totalAmount = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(productsDetailDO.getDuration()))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
case MONTH -> {
int months = productsDetailDO.getDuration();
if (months >= 12) {
// 转换成年 + 剩余月
int years = months / 12;
int remainMonths = months % 12;
// 整年部分:用年逻辑(10 个月价)
BigDecimal yearPart = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(years))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
// 剩余月部分:用月逻辑
BigDecimal monthPart = remainMonths == 0
? BigDecimal.ZERO
: monthAverageAmount
.multiply(new BigDecimal(remainMonths))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
totalAmount = yearPart.add(monthPart);
} else {
// 小于 12 个月直接按月逻辑
totalAmount = monthAverageAmount
.multiply(new BigDecimal(months))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
}
}
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
return totalAmount;
}
}