diff --git a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/config/ChenfengCacheAutoConfiguration.java b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/config/ChenfengCacheAutoConfiguration.java index 5ea00716b..336747216 100644 --- a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/config/ChenfengCacheAutoConfiguration.java +++ b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/config/ChenfengCacheAutoConfiguration.java @@ -3,6 +3,7 @@ package com.cf.imes.framework.redis.config; import cn.hutool.core.text.StrPool; import com.cf.imes.framework.redis.core.TimeoutRedisCacheManager; import com.cf.imes.framework.redis.util.RedisLockUtil; +import org.redisson.api.RedissonClient; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.cache.CacheProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -81,7 +82,7 @@ public class ChenfengCacheAutoConfiguration { } @Bean - public RedisLockUtil lockUtil(RedisTemplate redisTemplate) { - return new RedisLockUtil(redisTemplate); + public RedisLockUtil lockUtil(RedisTemplate redisTemplate, RedissonClient client) { + return new RedisLockUtil(redisTemplate, client); } } diff --git a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java index da25a288a..fc712ea03 100644 --- a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java +++ b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/constants/RedisKeyConstants.java @@ -19,7 +19,7 @@ public class RedisKeyConstants { /** - * + * 软件购买 */ - public static final String PAY_NOTIFY_ORDER_LOCK_KEY = "pay_recharge_order:%s%s"; + public static final String PRODUCT_PURCHASE_LOCK_KEY = "product_purchase:%s%s"; } diff --git a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/util/RedisLockUtil.java b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/util/RedisLockUtil.java index 6cac73ca4..7e25915fe 100644 --- a/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/util/RedisLockUtil.java +++ b/cf-framework/cf-spring-boot-starter-redis/src/main/java/com/cf/imes/framework/redis/util/RedisLockUtil.java @@ -3,6 +3,8 @@ package com.cf.imes.framework.redis.util; import cn.hutool.core.util.ObjectUtil; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.data.redis.core.RedisTemplate; import java.util.concurrent.TimeUnit; @@ -19,6 +21,42 @@ public class RedisLockUtil { private RedisTemplate redisTemplate; + private RedissonClient redissonClient; + + /** + * 加锁 + * + * @param key rediskey + * @param timeout 锁的超时时间,传空则按照默认配置 + * @param waitTime 等待获取锁的时间,传空则按照默认配置 + * @return true拿到锁,反之没有做对应的业务提醒 + */ + public boolean redissonLock(String key, Integer waitTime, Integer timeout) { + boolean getLock = false; + try { + RLock rLock = redissonClient.getLock(key); + return rLock.tryLock(waitTime, timeout, TimeUnit.MILLISECONDS); + } catch (Exception e) { + log.error("[RedisLockUtil][lock]加锁失败", e); + return getLock; + } + } + + /** + * 解锁 + * 使用注意:配合以上redisson的lock只能在当前线程中unlock,否则失败!!! + * + * @param key rediskey + */ + public void redissonReleaseLock(String key) { + try { + RLock rLock = redissonClient.getLock(key); + rLock.unlock(); + } catch (Exception e) { + log.error("[RedisLockUtil][unlock]解锁失败", e); + } + } + /** * 手动set nx ex加锁 * 使用注意:可用于跨服务 diff --git a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java index 213b893f4..28e5a16e0 100644 --- a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java +++ b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java @@ -444,6 +444,7 @@ public class ErrorCodeConstants { 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, "当前产品未上架"); + public static final ErrorCode ORG_PRODUCT_PURCHASE_LOCK_ERROR = new ErrorCode(1_002_043_034, "组织下存在购买中的产品操作,请稍后再试"); //=========== 支付相关 1-002-048-000 ============ public static final ErrorCode PAY_CHANNEL_NOT_SUPPORT = new ErrorCode(1_002_048_000, "暂不支持的支付渠道,请使用其他支付渠道"); @@ -489,6 +490,8 @@ public class ErrorCodeConstants { //=========== 产品延期管理 1-002-049-000 ============ public static final ErrorCode PRODUCT_PURCHASE_NO_EXIST = new ErrorCode(1_002_049_001, "当前购买记录不存在,请检查"); + public static final ErrorCode PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR = new ErrorCode(1_002_049_002, "当前订购的产品延期时间{}超过续费时间{},请调整续费时长"); + public static final ErrorCode PRODUCT_PURCHASE_ENDTIME_NOT_ENOUGH_ERROR = new ErrorCode(1_002_049_003, "本次订购的产品结束时间{}早于当前有效产品结束时间{},请勿重复订购"); //=========== 手工调账管理 1-002-050-000 ============ public static final ErrorCode RECHARGE_GIFT_MEANWHILE_EMPTY_ERROR = new ErrorCode(1_002_050_001, "充值金额、赠送金额不能同时为空"); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/PreviouProductDelayRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/PreviouProductDelayRespVO.java new file mode 100644 index 000000000..aa1349b22 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/PreviouProductDelayRespVO.java @@ -0,0 +1,25 @@ +package com.cf.imes.module.system.controller.admin.funds.delay.vo; + +import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; +import lombok.Data; + +import java.util.List; + +/** + * 前序购买记留和延期记录Resp VO + * + * @author Gqr + * @since 2025/9/19 15:29 + */ +@Data +public class PreviouProductDelayRespVO { + /** + * 前序购买记录 + */ + private PurchaseRecordDO previouPurchaseRecord; + + /** + * 前序购买记录下的延期记录 + */ + private List delayRespVOList; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java index 086e67d2c..6eb0763e4 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java @@ -1,5 +1,6 @@ package com.cf.imes.module.system.controller.admin.funds.delay.vo; +import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.cf.imes.framework.excel.core.annotations.EnumFormat; import com.cf.imes.framework.excel.core.convert.EnumConvert; @@ -38,6 +39,11 @@ public class ProductDelayRespVO { @EnumFormat(value = DurationUnitEnum.class) private Integer delayDurationUnit; + @Schema(description = "产品结束时间") + @ExcelIgnore + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY, timezone = TIME_ZONE_DEFAULT) + private LocalDate endTime; + @Schema(description = "产品有效时间") @ExcelProperty("产品有效时间") @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY, timezone = TIME_ZONE_DEFAULT) diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/delay/ProductDelayRecordDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/delay/ProductDelayRecordDO.java index 0eb1d2045..517924665 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/delay/ProductDelayRecordDO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/delay/ProductDelayRecordDO.java @@ -42,6 +42,11 @@ public class ProductDelayRecordDO extends BaseDO { */ private String organName; + /** + * 产品id + */ + private Long productId; + /** * 产品名称 */ diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java index 0a559151a..3c8cbf885 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java @@ -43,6 +43,11 @@ public class PurchaseRecordDO extends BaseDO { private Long organId; + /** + * 上次订购记录ID + */ + private Long previousId; + /** * 产品ID */ @@ -163,4 +168,9 @@ public class PurchaseRecordDO extends BaseDO { * 是否首次订购 */ private Boolean initial; + + /** + * 状态 + */ + private Integer status; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PurchaseRecordStatusEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PurchaseRecordStatusEnum.java new file mode 100644 index 000000000..485eeb2d3 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PurchaseRecordStatusEnum.java @@ -0,0 +1,31 @@ +package com.cf.imes.module.system.enums.pay; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 产品购买记录状态枚举 + * + * @author Gqr + * @since 2025/9/18 15:50 + */ +@Getter +@AllArgsConstructor +public enum PurchaseRecordStatusEnum { + /** + * 有效 + */ + ACTIVE(1), + + /** + * 等待支付 + */ + PENDING(2), + + /** + * 作废 + */ + INACTIVE(3); + + private final Integer status; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java index bf3c15eb7..8444175a3 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java @@ -1,6 +1,7 @@ package com.cf.imes.module.system.service.funds.delay; import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelaySaveReqVO; @@ -37,4 +38,13 @@ public interface ProductDelayService { * @return */ List getRecordsByPurchaseId(Long purchaseId, Long organId); + + /** + * 查询组织产品下有效的延期记录 + * + * @param productId + * @param organId + * @return + */ + PreviouProductDelayRespVO getActiveRecordsByProductId(Long productId, Long organId); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java index 4213930a3..9e3b91a6e 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java @@ -1,11 +1,14 @@ package com.cf.imes.module.system.service.funds.delay; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.common.util.Assert.AssertUtils; import com.cf.imes.framework.common.util.object.BeanUtils; +import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelaySaveReqVO; @@ -13,7 +16,9 @@ import com.cf.imes.module.system.dal.dataobject.funds.delay.ProductDelayRecordDO 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.delay.ProductDelayRecordMapper; +import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper; import com.cf.imes.module.system.enums.pay.DurationUnitEnum; +import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum; import com.cf.imes.module.system.service.funds.products.ProductsDetailService; import com.cf.imes.module.system.service.funds.purchase.PurchaseService; import com.cf.imes.module.system.service.organ.OrganService; @@ -45,6 +50,9 @@ public class ProductDelayServiceImpl implements ProductDelayService { @Resource private ProductDelayRecordMapper productDelayRecordMapper; + @Resource + private PurchaseRecordMapper purchaseRecordMapper; + @Override public Long createProductDelay(ProductDelaySaveReqVO productDelaySaveReqVO) { Long organId = productDelaySaveReqVO.getOrganId(); @@ -85,6 +93,7 @@ public class ProductDelayServiceImpl implements ProductDelayService { ProductDelayRecordDO productDelayRecordDO = ProductDelayRecordDO.builder() .organId(organId) .organName(organizationDO.getName()) + .productId(purchaseRecordDO.getProductId()) .productName(purchaseRecordDO.getProductName()) .delayDuration(delayDuration) .delayDurationUnit(delayDurationUnit) @@ -147,4 +156,30 @@ public class ProductDelayServiceImpl implements ProductDelayService { .orderByDesc(ProductDelayRecordDO::getId) .select(ProductDelayRecordDO::getDelayDuration, ProductDelayRecordDO::getDelayDurationUnit, ProductDelayRecordDO::getDelayTime)), ProductDelayRespVO.class); } + + @Override + public PreviouProductDelayRespVO getActiveRecordsByProductId(Long productId, Long organId) { + PreviouProductDelayRespVO previouProductDelayRespVO = new PreviouProductDelayRespVO(); + PurchaseRecordDO purchaseRecordDO = purchaseRecordMapper.selectOne(new LambdaQueryWrapperX() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getProductId, productId) + .eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus()) + ); + List resultList; + if(ObjectUtil.isNotNull(purchaseRecordDO)) { + previouProductDelayRespVO.setPreviouPurchaseRecord(purchaseRecordDO); + List recordDOS = productDelayRecordMapper.selectList(new LambdaQueryWrapper() + .eq(ProductDelayRecordDO::getProductId, productId) + .eq(ProductDelayRecordDO::getOrganId, organId) + .eq(ProductDelayRecordDO::getPurchaseId, purchaseRecordDO.getId()) + .eq(ProductDelayRecordDO::getDeleted, false) + .select(ProductDelayRecordDO::getDelayDuration, ProductDelayRecordDO::getDelayDurationUnit, ProductDelayRecordDO::getDelayTime, ProductDelayRecordDO::getEndTime) + ); + if(CollUtil.isNotEmpty(recordDOS)) { + resultList = BeanUtil.copyToList(recordDOS, ProductDelayRespVO.class); + previouProductDelayRespVO.setDelayRespVOList(resultList); + } + } + return previouProductDelayRespVO; + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java index aed950873..35f8dc71a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java @@ -27,6 +27,13 @@ public interface PurchaseService { */ PurchaseRecordDO getRecord(Long id); + /** + * 删除购买记录 + * + * @param id + */ + void deleteRecord(Long id); + /** * 查询购买记录分页 * @@ -116,4 +123,28 @@ public interface PurchaseService { * @return */ List getOrgProductDetailList(Long organId, Long productId); + + /** + * 作废历史组织产品购买记录 + * + * @param organId + * @param purchaseRecordId + */ + void pendingHistoryOrganProductPurchaseRecord(Long organId, Long purchaseRecordId); + + /** + * 作废历史组织产品购买记录 + * + * @param organId + * @param purchaseRecordId + */ + void invalidHistoryOrganProductPurchaseRecord(Long organId, Long purchaseRecordId); + + /** + * 激活历史组织产品购买记录 + * + * @param organId + * @param purchaseRecordId + */ + void activateHistoryOrganProductPurchaseRecord(Long organId, Long purchaseRecordId); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java index 634997ee0..66f2b240a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java @@ -8,7 +8,6 @@ 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; @@ -28,6 +27,7 @@ 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.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.funds.products.ProductsDetailService; @@ -75,6 +75,11 @@ public class PurchaseServiceImpl implements PurchaseService { return purchaseRecordMapper.selectById(id); } + @Override + public void deleteRecord(Long id) { + purchaseRecordMapper.deleteById(id); + } + @Override public PageResult getPurchaseRecord(PurchaseRecordPageReqVO pageReqVO) { // 生成起时间0点和止时间12点的区间 @@ -199,6 +204,7 @@ public class PurchaseServiceImpl implements PurchaseService { .originalAmount(price) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .initial(true) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); createRecord(purchaseRecordDO); } @@ -206,7 +212,10 @@ public class PurchaseServiceImpl implements PurchaseService { @Override public List getOrgPurchaseList(Set organIds) { - return purchaseRecordMapper.selectList(new LambdaQueryWrapper().in(PurchaseRecordDO::getOrganId, organIds)); + return purchaseRecordMapper.selectList(new LambdaQueryWrapper() + .in(PurchaseRecordDO::getOrganId, organIds) + .eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus()) + .eq(PurchaseRecordDO::getDeleted, false)); } @Override @@ -323,4 +332,34 @@ public class PurchaseServiceImpl implements PurchaseService { return resultList; } + + @Override + public void pendingHistoryOrganProductPurchaseRecord(Long organId, Long purchaseRecordId) { + purchaseRecordMapper.update(new LambdaUpdateWrapper() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getId, purchaseRecordId) + .eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus()) + .eq(PurchaseRecordDO::getDeleted, false) + .set(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.PENDING.getStatus())); + } + + @Override + public void invalidHistoryOrganProductPurchaseRecord(Long organId, Long purchaseRecordId) { + purchaseRecordMapper.update(new LambdaUpdateWrapper() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getId, purchaseRecordId) + .eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus()) + .eq(PurchaseRecordDO::getDeleted, false) + .set(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.INACTIVE.getStatus())); + } + + @Override + public void activateHistoryOrganProductPurchaseRecord(Long organId, Long purchaseRecordId) { + purchaseRecordMapper.update(new LambdaUpdateWrapper() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getId, purchaseRecordId) + .eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.PENDING.getStatus()) + .eq(PurchaseRecordDO::getDeleted, false) + .set(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus())); + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java index 47888c082..6cda0b639 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java @@ -22,6 +22,10 @@ import com.cf.imes.framework.organ.core.context.OrganContextHolder; import com.cf.imes.framework.pay.config.ChenfengAlipayConfig; import com.cf.imes.framework.pay.config.ChenfengPayConfig; import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO; +import com.cf.imes.framework.redis.config.ChenfengCacheProperties; +import com.cf.imes.framework.redis.constants.RedisKeyConstants; +import com.cf.imes.framework.redis.util.RedisLockUtil; +import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.recharge.vo.RechargeDetailRespVO; import com.cf.imes.module.system.controller.admin.funds.recharge.vo.RechargeDetailsPageReqVO; import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqDTO; @@ -32,6 +36,7 @@ import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganRech import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO; 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.service.funds.delay.ProductDelayService; import com.cf.imes.module.system.service.funds.purchase.PurchaseService; import com.cf.imes.module.system.controller.admin.pay.vo.PayProductOrderUnifiedReqVO; import com.cf.imes.module.system.controller.admin.pay.vo.PayRechargeOrderReqVO; @@ -78,6 +83,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_PRODUCT_PURCHASE_LOCK_ERROR; import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_NOTIFY_PASSBACK_NOT_EXISTS_ERROR; 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; @@ -139,6 +145,15 @@ public class PayOrderServiceImpl implements PayOrderService { @Resource private OrganService organService; + @Resource + private ProductDelayService productDelayService; + + @Resource + private RedisLockUtil redisLockUtil; + + @Resource + private ChenfengCacheProperties chenfengCacheProperties; + @Override @Transactional(rollbackFor = Exception.class) public PayOrderRespDTO productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) { @@ -150,61 +165,107 @@ public class PayOrderServiceImpl implements PayOrderService { // 校验产品和产品明细 ProductsDetailDO productsDetailDO = productsDetailService.validateProductDetailsExists(payProductOrderUnifiedReqVO.getProductDetailsId()); - ProductsDO productsDO = productsDetailService.validateProductExist(productsDetailDO.getProductId()); + Long productId = productsDetailDO.getProductId(); + ProductsDO productsDO = productsDetailService.validateProductExist(productId); - // 查询组织余额信息 - OrganAmountDO organAmount = organAmountService.validOrganAmount(organId); - - // 查询组织产品的首次购买记录 - PurchaseRecordDO orgInitialPurchaseRecord = purchaseService.getOrgInitialPurchaseRecord(organId, productsDO.getId()); - - // 根据购买类型做价格处理 - PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, orgInitialPurchaseRecord, organAmount, organId, payConfig, mybatisIdProperties); - ProductProcessorRespVO productProcessorRespVO = payProductProcessor.processPayment(PayProductChannelCodeEnum.fromType(payProductOrderUnifiedReqVO.getProductChannelCode())); - - // 记录入库,如果有外部支付发起请求二维码 - // 购买记录入库 - purchaseService.createRecord(productProcessorRespVO.getPurchaseRecordDO()); - // 收支明细入库 - IncomeExpenseDetailsDO incomeExpenseDetailsDO = productProcessorRespVO.getIncomeExpenseDetailsDO(); - if (ObjectUtil.isNotNull(incomeExpenseDetailsDO)) { - incomeExpenseService.createDetail(incomeExpenseDetailsDO); + // 检查机构产品购买锁 + String purchaseLockKey = String.format(RedisKeyConstants.PRODUCT_PURCHASE_LOCK_KEY, organId, productId); + boolean lockResult = redisLockUtil.redissonLock(purchaseLockKey, chenfengCacheProperties.getLockWaitTime(), chenfengCacheProperties.getLockTimeout()); + if (!lockResult) { + throw new ServiceException(ORG_PRODUCT_PURCHASE_LOCK_ERROR); } - // 更新组织资金,纯第三方支付不需要更新余额 - OrganAmountDO finalOrganAmountDO = productProcessorRespVO.getOrganAmountDO(); - if (ObjectUtil.isNotNull(finalOrganAmountDO)) { - organAmountService.productPay(finalOrganAmountDO); - } + PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = null; + // 获取渠道方处理器 + Integer channelCode = PayProductChannelCodeEnum.isAliPay(payProductOrderUnifiedReqVO.getProductChannelCode()) ? PayChannelCodeEnum.ALIPAY_PC.getCode() : PayChannelCodeEnum.WECHAT_NATIVE.getCode(); + try { + // 查询组织余额信息 + OrganAmountDO organAmount = organAmountService.validOrganAmount(organId); - // 如果有外部支付,发起请求 - PayOrderDO payOrder = productProcessorRespVO.getPayOrderDO(); - if(ObjectUtil.isNotNull(payOrder)) { - payOrderMapper.insert(payOrder); + // 查询组织产品的首次购买记录 + PurchaseRecordDO orgInitialPurchaseRecord = purchaseService.getOrgInitialPurchaseRecord(organId, productsDO.getId()); - // 拼接充值个性化参数 - Map passbackParams = new HashMap<>(); - passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_TRADE_TYPE_KEY, TradeTypeEnum.PRODUCT.getCode());// 交易类型 - passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY, productProcessorRespVO.getPurchaseRecordDO().getId());// 购买记录id,用于更新开票状态 + // 查询组织有效产品购买记录下的延期记录 + PreviouProductDelayRespVO previouProductDelayRespVO = productDelayService.getActiveRecordsByProductId(productId, organId); - // 发起渠道方支付 - PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = null; - Integer channelCode = PayProductChannelCodeEnum.isAliPay(payProductOrderUnifiedReqVO.getProductChannelCode()) ? PayChannelCodeEnum.ALIPAY_PC.getCode() : PayChannelCodeEnum.WECHAT_NATIVE.getCode(); - try { - // 获取渠道方处理器 + // 根据购买类型做价格处理 + PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, orgInitialPurchaseRecord, previouProductDelayRespVO, organAmount, organId, payConfig, mybatisIdProperties); + ProductProcessorRespVO productProcessorRespVO = payProductProcessor.processPayment(PayProductChannelCodeEnum.fromType(payProductOrderUnifiedReqVO.getProductChannelCode())); + PayOrderDO payOrder = productProcessorRespVO.getPayOrderDO(); + + // 处理前置的购买记录 + processPreviosPurchaseRecord(previouProductDelayRespVO, payOrder); + + // 记录入库,如果有外部支付发起请求二维码 + // 购买记录入库 + purchaseService.createRecord(productProcessorRespVO.getPurchaseRecordDO()); + // 收支明细入库 + IncomeExpenseDetailsDO incomeExpenseDetailsDO = productProcessorRespVO.getIncomeExpenseDetailsDO(); + if (ObjectUtil.isNotNull(incomeExpenseDetailsDO)) { + incomeExpenseService.createDetail(incomeExpenseDetailsDO); + } + + // 更新组织资金,纯第三方支付不需要更新余额 + OrganAmountDO finalOrganAmountDO = productProcessorRespVO.getOrganAmountDO(); + if (ObjectUtil.isNotNull(finalOrganAmountDO)) { + organAmountService.productPay(finalOrganAmountDO); + } + + // 如果有外部支付,发起请求 + if (ObjectUtil.isNotNull(payOrder)) { + payOrderMapper.insert(payOrder); + + // 拼接充值个性化参数 + Map passbackParams = new HashMap<>(); + passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_TRADE_TYPE_KEY, TradeTypeEnum.PRODUCT.getCode());// 交易类型 + passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY, productProcessorRespVO.getPurchaseRecordDO().getId());// 购买记录id,用于更新开票状态 + + // 发起渠道方支付 AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode); payOrderUnifiedReqDTO = createPayOrderUnifiedReqDTO(payOrder, passbackParams); return payHandler.unifiedOrder(payOrderUnifiedReqDTO); - } catch (Throwable e) { - // 系统异常,则包装成 PayException 异常抛出 - log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]", - channelCode, JSON.toJSONString(payOrderUnifiedReqDTO), e); + } + // 软件购买锁解锁 + redisLockUtil.redissonReleaseLock(purchaseLockKey); + return null; + } catch (Throwable e) { + // 系统异常,则包装成 PayException 异常抛出 + log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]", + channelCode, JSON.toJSONString(payOrderUnifiedReqDTO), e); + if(e instanceof ServiceException) { + throw (ServiceException) e; + } else { throw new PayException(PAY_ORDER_SUBMIT_CHANNEL_ERROR); } + } finally { + // 软件购买锁解锁 + redisLockUtil.redissonReleaseLock(purchaseLockKey); } + } - return null; + /** + * 处理前序购买记录: + * 1、需要第三方支付,更新为等待支付,第三方支付回调后作废 + * 2、不需要第三方支付,直接作废 + * + * @param previouProductDelayRespVO + * @param payOrder + */ + private void processPreviosPurchaseRecord(PreviouProductDelayRespVO previouProductDelayRespVO, PayOrderDO payOrder) { + // 前序购买记录 + PurchaseRecordDO previouPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); + if (ObjectUtil.isNotNull(previouPurchaseRecord)) { + Long previousId = previouPurchaseRecord.getId(); + Long organId = previouPurchaseRecord.getOrganId(); + if (ObjectUtil.isNotNull(payOrder)) { + // 历史的购买记录更新为等待支付,需要第三方支付回调后作废前序 + purchaseService.pendingHistoryOrganProductPurchaseRecord(organId, previousId); + } else { + // 作废历史的购买记录更新为等待支付 + purchaseService.pendingHistoryOrganProductPurchaseRecord(organId, previousId); + } + } } @Override @@ -228,7 +289,7 @@ public class PayOrderServiceImpl implements PayOrderService { PayOrderDO payOrder = createPayOrder(payRechargeOrderReqVO.getPrice(), "imes充值", channelCode); // 设置赠送活动编号 Long rechargeActivityId = payRechargeOrderReqVO.getRechargeActivityId(); - if(ObjectUtil.isNotNull(rechargeActivityId)) { + if (ObjectUtil.isNotNull(rechargeActivityId)) { // 校验活动类型和渠道是否匹配 validRechargeActivity(rechargeActivityId, channelCode); payOrder.setRechargeActivityId(rechargeActivityId); @@ -541,15 +602,25 @@ public class PayOrderServiceImpl implements PayOrderService { /** * 产品购买成功回调 + * * @param organId * @param passbackParams */ private void notifyProductPayOrderSuccess(Long organId, Map passbackParams) { - // 1、更新购买记录的开票状态 Object purchaseRecordIdObj = passbackParams.get(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY); if (ObjectUtil.isNotNull(purchaseRecordIdObj)) { - purchaseService.updateRecordInvoicable(NumberUtil.parseLong(purchaseRecordIdObj.toString()), organId); + // 1、更新购买记录的开票状态 + long purchaseId = NumberUtil.parseLong(purchaseRecordIdObj.toString()); + purchaseService.updateRecordInvoicable(purchaseId, organId); + + // 2、作废前置购买记录 + PurchaseRecordDO record = purchaseService.getRecord(purchaseId); + Long previousId = record.getPreviousId(); + if (ObjectUtil.isNotNull(previousId)) { + purchaseService.invalidHistoryOrganProductPurchaseRecord(organId, previousId); + } } + } /** @@ -632,14 +703,17 @@ public class PayOrderServiceImpl implements PayOrderService { private void notifyProductPayOrderFail(Long organId, Map passbackParams) { Object purchaseRecordIdObj = passbackParams.get(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY); if (ObjectUtil.isNotNull(purchaseRecordIdObj)) { - PurchaseRecordDO record = purchaseService.getRecord(NumberUtil.parseLong(purchaseRecordIdObj.toString())); + long purchaseId = NumberUtil.parseLong(purchaseRecordIdObj.toString()); + PurchaseRecordDO record = purchaseService.getRecord(purchaseId); + // 1、产品购买第三方支付失败 删除购买记录 + purchaseService.deleteRecord(purchaseId); - // 1、混合支付失败,退回扣除的现金和赠送金 + // 2、混合支付失败,退回扣除的现金和赠送金 BigDecimal accountBalanceSpent = record.getAccountBalanceSpent(); BigDecimal giftMoneySpent = record.getGiftMoneySpent(); OrganRechargeAmountRespVO organRefundProductRespVO = organAmountService.refundProductOrganAmountBalanceAndGift(organId, accountBalanceSpent, giftMoneySpent); - // 2、新增退回的收支记录 + // 3、新增退回的收支记录 IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder() .organId(organId) .orderNo(String.valueOf(record.getId())) @@ -655,6 +729,9 @@ public class PayOrderServiceImpl implements PayOrderService { incomeExpenseDetailsDO.setCreator(record.getCreator()); incomeExpenseDetailsDO.setUpdater(record.getUpdater()); incomeExpenseService.createDetail(incomeExpenseDetailsDO); + + // 4、回退前序购买记录的状态 + purchaseService.activateHistoryOrganProductPurchaseRecord(organId, purchaseId); } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java index 42860dd33..e3971333a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java @@ -1,13 +1,18 @@ 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.DateUtil; +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.common.util.date.LocalDateTimeUtils; 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; @@ -22,6 +27,7 @@ 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; @@ -29,11 +35,16 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +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; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PURCHASE_ENDTIME_NOT_ENOUGH_ERROR; /** * 产品购买类型处理器 @@ -59,11 +70,19 @@ public class PayProductProcessor { private boolean initialized = false; - public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, OrganAmountDO organAmountDO, - Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties) { + private PreviouProductDelayRespVO previouProductDelayRespVO; + + private PurchaseRecordDO previousPurchaseRecord; + + public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, PreviouProductDelayRespVO previouProductDelayRespVO, + OrganAmountDO organAmountDO, Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties) { this.productsDO = productsDO; this.productsDetailDO = productsDetailDO; this.orgInitialPurchaseRecord = orgInitialPurchaseRecord; + this.previouProductDelayRespVO = previouProductDelayRespVO; + if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) { + previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); + } this.organAmountDO = organAmountDO; this.organId = organId; this.payConfig = payConfig; @@ -149,6 +168,7 @@ public class PayProductProcessor { .accountBalanceSpent(price) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), @@ -206,6 +226,7 @@ public class PayProductProcessor { .accountBalanceSpent(price) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), @@ -245,7 +266,6 @@ public class PayProductProcessor { PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() .id(purchaseId) .organId(organId) - .organId(organId) .productId(productsDO.getId()) .productName(productsDO.getProductName()) .productDetailsId(productsDetailDO.getId()) @@ -260,10 +280,15 @@ public class PayProductProcessor { .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .alipaySpent(price) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); + // 记录前序购买记录id + if (ObjectUtil.isNotNull(previousPurchaseRecord)) { + purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); + } // 新增第三方支付订单 PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.ALIPAY_PC); @@ -296,10 +321,15 @@ public class PayProductProcessor { .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .wechatSpent(price) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); + // 记录前序购买记录id + if (ObjectUtil.isNotNull(previousPurchaseRecord)) { + purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); + } // 新增第三方支付订单 PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.WECHAT_NATIVE); @@ -337,6 +367,7 @@ public class PayProductProcessor { .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), @@ -413,10 +444,15 @@ public class PayProductProcessor { .originalAmount(productsDetailDO.getCurrentPrice()) .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); + // 记录前序购买记录id + if (ObjectUtil.isNotNull(previousPurchaseRecord)) { + purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); + } // 先用余额 if (balance != null && balance.compareTo(BigDecimal.ZERO) > 0) { @@ -488,11 +524,15 @@ public class PayProductProcessor { .originalAmount(productsDetailDO.getCurrentPrice()) .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); - + // 记录前序购买记录id + if (ObjectUtil.isNotNull(previousPurchaseRecord)) { + purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); + } // 先用余额 if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { @@ -566,10 +606,15 @@ public class PayProductProcessor { .originalAmount(productsDetailDO.getCurrentPrice()) .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .initial(initialized) + .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); + // 记录前序购买记录id + if (ObjectUtil.isNotNull(previousPurchaseRecord)) { + purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); + } // 先用赠送金 if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { @@ -657,20 +702,37 @@ public class PayProductProcessor { * @param giftDurationUnit * @return */ - private static LocalDate calculateEndTime(LocalDate startTime, Integer duration, Integer durationUnit, + private LocalDate calculateEndTime(LocalDate startTime, Integer duration, Integer durationUnit, Integer giftDuration, Integer giftDurationUnit) { - + LocalDate endTime = startTime; // 加正常时间 if (ObjectUtil.isNotNull(duration) && durationUnit != null) { - startTime = addTime(startTime, duration, durationUnit); + endTime = addTime(startTime, duration, durationUnit); } // 加赠送时间 if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) { - startTime = addTime(startTime, giftDuration, giftDurationUnit); + endTime = addTime(startTime, giftDuration, giftDurationUnit); } - return startTime; + List delayRespVOList = previouProductDelayRespVO.getDelayRespVOList(); + if (CollUtil.isNotEmpty(delayRespVOList)) { + // 根据延期列表计算新的结束时间 + endTime = calculateDelayEndTime(startTime, endTime, delayRespVOList); + } + + PurchaseRecordDO previouPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); + if (ObjectUtil.isNotNull(previouPurchaseRecord)) { + // 如果本次订购的时间小于上次订购的结束时间,提示请勿重复订购 + LocalDate previouPurchaseRecordEndTime = previouPurchaseRecord.getEndTime(); + if (endTime.isBefore(previouPurchaseRecordEndTime)) { + throw ServiceExceptionUtil.exception(PRODUCT_PURCHASE_ENDTIME_NOT_ENOUGH_ERROR, + LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN), + LocalDateTimeUtil.format(previouPurchaseRecordEndTime, DatePattern.NORM_DATE_PATTERN)); + } + } + + return endTime; } /** @@ -692,6 +754,44 @@ public class PayProductProcessor { } } + /** + * 根据延期列表中最早的产品结束时间和最晚的延期后时间计算新的结束时间 + * + * @param startTime 订购的开始时间 + * @param endTime 订购的结束时间 + * @param delayRecordsList 延期列表 + * @return + */ + private static LocalDate calculateDelayEndTime(LocalDate startTime, LocalDate endTime, List delayRecordsList) { + // 筛选出延期列表结束时间排序最早和最晚的 + ProductDelayRespVO earliestDelayVO = delayRecordsList.stream().min(Comparator.comparing(ProductDelayRespVO::getEndTime)).get(); + ProductDelayRespVO lastestDelayVO = delayRecordsList.stream().max(Comparator.comparing(ProductDelayRespVO::getDelayTime)).get(); + + // 延期结束时间超过续费结束时间,提示重新选择续费时长 + LocalDate delayEndTime = earliestDelayVO.getEndTime(); + if (delayEndTime.isAfter(endTime)) { + throw ServiceExceptionUtil.exception(PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR, + LocalDateTimeUtil.format(delayEndTime, DatePattern.NORM_DATE_PATTERN), + LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN)); + } + + // 延期区间: [原结束日+1, 延期结束日] + LocalDate delayStart = delayEndTime.plusDays(1); + LocalDate delayEnd = lastestDelayVO.getDelayTime(); + + // 购买区间: [startTime, endTime] + LocalDate overlapStart = startTime.isAfter(delayStart) ? startTime : delayStart; + LocalDate overlapEnd = endTime.isBefore(delayEnd) ? endTime : delayEnd; + + long overlapDays = 0; + if (!overlapEnd.isBefore(overlapStart)) { + overlapDays = ChronoUnit.DAYS.between(overlapStart, overlapEnd) + 1; + } + + // 从 endTime 扣掉重合天数 + return endTime.minusDays(overlapDays); + } + /** * 计算产品价格 *