完善软件购买时扣除延期时间区间、作废/待支付旧订购记录、第三方支付回调支持作废/激活旧支付记录流程

This commit is contained in:
gaoqr
2025-09-19 18:33:16 +08:00
parent b14ed0628a
commit 9629734e48
15 changed files with 473 additions and 62 deletions
@@ -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, "充值金额、赠送金额不能同时为空");
@@ -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<ProductDelayRespVO> delayRespVOList;
}
@@ -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)
@@ -42,6 +42,11 @@ public class ProductDelayRecordDO extends BaseDO {
*/
private String organName;
/**
* 产品id
*/
private Long productId;
/**
* 产品名称
*/
@@ -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;
}
@@ -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;
}
@@ -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<ProductDelayRespVO> getRecordsByPurchaseId(Long purchaseId, Long organId);
/**
* 查询组织产品下有效的延期记录
*
* @param productId
* @param organId
* @return
*/
PreviouProductDelayRespVO getActiveRecordsByProductId(Long productId, Long organId);
}
@@ -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<PurchaseRecordDO>()
.eq(PurchaseRecordDO::getOrganId, organId)
.eq(PurchaseRecordDO::getProductId, productId)
.eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus())
);
List<ProductDelayRespVO> resultList;
if(ObjectUtil.isNotNull(purchaseRecordDO)) {
previouProductDelayRespVO.setPreviouPurchaseRecord(purchaseRecordDO);
List<ProductDelayRecordDO> recordDOS = productDelayRecordMapper.selectList(new LambdaQueryWrapper<ProductDelayRecordDO>()
.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;
}
}
@@ -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<ProductsDetailDO> 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);
}
@@ -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<PurchaseRecordRespVO> 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<PurchaseRecordDO> getOrgPurchaseList(Set<Long> organIds) {
return purchaseRecordMapper.selectList(new LambdaQueryWrapper<PurchaseRecordDO>().in(PurchaseRecordDO::getOrganId, organIds));
return purchaseRecordMapper.selectList(new LambdaQueryWrapper<PurchaseRecordDO>()
.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<PurchaseRecordDO>()
.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<PurchaseRecordDO>()
.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<PurchaseRecordDO>()
.eq(PurchaseRecordDO::getOrganId, organId)
.eq(PurchaseRecordDO::getId, purchaseRecordId)
.eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.PENDING.getStatus())
.eq(PurchaseRecordDO::getDeleted, false)
.set(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus()));
}
}
@@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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);
}
}
@@ -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<ProductDelayRespVO> 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<ProductDelayRespVO> 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);
}
/**
* 计算产品价格
*