TASK: 推广码 添加开始生效时间;优化数据计算类型,使用BigDecimal替换float;更新paidCommission后自动计算unpaidCommission

This commit is contained in:
2025-06-06 19:37:38 +08:00
parent 6249d53b7b
commit 4b7fd649a3
8 changed files with 106 additions and 48 deletions

View File

@@ -81,5 +81,7 @@ public class CommonConstant {
public static final String TIME_FORMAT_MMM_dd_yyyy = "MMM. dd, yyyy";
public static final String TIME_FORMAT_yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
public static final String AFFILIATE_LINK = "https://www.aida.com.hk?ref=";
}

View File

@@ -137,9 +137,11 @@ public class StripeController {
@ApiOperation("更新推广码信息")
@GetMapping("/updatePromCodeInfo")
public Response<ProductCoupons> updateCouponsInfo(@RequestParam Long id, @RequestParam(required = false) Long paidCommission,
@RequestParam(required = false) String cooperator, @RequestParam(required = false) String remark){
return Response.success(stripeService.updateCouponsInfo(id, paidCommission, cooperator, remark));
public Response<ProductCoupons> updateCouponsInfo(@RequestParam Long id, @RequestParam(required = false) String paidCommission,
@RequestParam(required = false) String cooperator,
@RequestParam(required = false) String remark,
@RequestParam(required = false) Long startTime){
return Response.success(stripeService.updateCouponsInfo(id, paidCommission, cooperator, remark, startTime));
}
@ApiOperation("删除推广码")

View File

@@ -7,6 +7,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("t_product_coupons")
@@ -20,6 +22,8 @@ public class ProductCoupons extends BaseEntity{
private String promotionCodeId;
// 对应的推广码
private String promotionCode;
// 优惠券有效期开始时间
private Long startTime;
// 最大兑换次数
private Long maxRedemptions;
// 优惠券的折扣
@@ -29,13 +33,13 @@ public class ProductCoupons extends BaseEntity{
// 合作者
private String cooperator;
// 使用了该优惠券支付的总金额
private float totalEarnings;
private BigDecimal totalEarnings = BigDecimal.ZERO;
// 佣金
private float commission;
private BigDecimal commission = BigDecimal.ZERO;
// 已付佣金
private float paidCommission;
private BigDecimal paidCommission = BigDecimal.ZERO;
// 未付佣金
private float unpaidCommission;
private BigDecimal unpaidCommission = BigDecimal.ZERO;
// 备注
private String remark;

View File

@@ -14,7 +14,9 @@ public class CreateCouponDTO {
@NotNull(message = "Please set the commissionRate.")
private Float commissionRate;
@ApiModelProperty("推广码到期时间 秒级时间戳")
private Long timestamp;
private Long endTime;
@ApiModelProperty("推广码开始时间 秒级时间戳")
private Long startTime;
@ApiModelProperty("推广码最大使用次数")
private Long maxRedemptions;
@ApiModelProperty("合作者/机构名")

View File

@@ -9,7 +9,7 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class CheckCouponsVO {
@ApiModelProperty("expired || invalid || valid")
@ApiModelProperty("expired 过期 || invalid 无效 || valid 有效 || pending 尚未生效")
private String status;
private String message;

View File

@@ -61,7 +61,7 @@ public interface StripeService {
CheckCouponsVO checkProductCoupon(String promotionCode, Long price);
ProductCoupons updateCouponsInfo(Long id, Long paidCommission, String cooperator, String remark);
ProductCoupons updateCouponsInfo(Long id, String paidCommission, String cooperator, String remark, Long startTime);
ProductCoupons getProductCoupon(String promotionCode, String promotionCodeId);

View File

@@ -31,9 +31,11 @@ import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@Slf4j
@@ -342,49 +344,66 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
return baseMapper.selectOne(queryWrapper);
}
public void calcCouponsCommission(){
public void calcCouponsCommission() {
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
log.info("优惠券佣金计算,上次执行时间:{}", lastTime);
String currentTime = LocalDateTime.now().toString();
// 1、查上次更新之后有无使用了优惠券的新订单
// 1. 查询新增的优惠券订单
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("trade_state","paid")
queryWrapper.eq("trade_state", "paid")
.lt("create_time", currentTime)
.isNotNull("promotion_code");
if (!StringUtil.isNullOrEmpty(lastTime)){
queryWrapper.gt("create_time", lastTime);
}
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
Optional.ofNullable(lastTime)
.filter(time -> !time.isEmpty())
.ifPresent(time -> queryWrapper.gt("create_time", time));
List<PaymentInfo> paymentInfos = paymentInfoService.list(queryWrapper);
log.info("目前,新增使用优惠券的订单数:{}", paymentInfos.size());
// key:推广码, value:用户支付金额
HashMap<String, Float> codeAmount = new HashMap<>();
if (!paymentInfos.isEmpty()){
for (PaymentInfo paymentInfo : paymentInfos){
String promotionCode = paymentInfo.getPromotionCode();
Float sum = codeAmount.get(promotionCode);
if (sum == null || sum == 0.0f){
codeAmount.put(promotionCode, paymentInfo.getPayerTotal());
}else {
codeAmount.put(promotionCode, sum + paymentInfo.getPayerTotal());
}
// 2. 按推广码汇总支付金额
Map<String, BigDecimal> codeAmount = paymentInfos.stream()
.collect(Collectors.toMap(
PaymentInfo::getPromotionCode,
payment -> new BigDecimal(payment.getPayerTotal()),
BigDecimal::add
));
// 3. 更新佣金数据
codeAmount.forEach((promotionCode, amount) -> {
ProductCoupons coupon = stripeService.getProductCoupon(promotionCode, null);
if (coupon != null) {
updateCouponCommission(coupon, amount);
productCouponsMapper.updateById(coupon);
}
for (Map.Entry<String, Float> entry : codeAmount.entrySet()){
String promotionCode = entry.getKey();
ProductCoupons productCoupons = stripeService.getProductCoupon(promotionCode, null);
if (!Objects.isNull(productCoupons)){
// 2、计算支付金额的总和更新totalEarningscommissionunpaidCommission
float sum = productCoupons.getTotalEarnings() + entry.getValue();
productCoupons.setTotalEarnings(sum);
float commission = sum * productCoupons.getCommissionRate() / 100;
productCoupons.setCommission(commission);
productCoupons.setUnpaidCommission(commission - productCoupons.getPaidCommission());
productCouponsMapper.updateById(productCoupons);
}
}
}
});
// 4. 更新最后执行时间
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
}
/**
* 更新优惠券的佣金数据
*/
private void updateCouponCommission(ProductCoupons coupon, BigDecimal newAmount) {
// 总收益 = 原收益默认0 + 新增金额
BigDecimal totalEarnings = Optional.ofNullable(coupon.getTotalEarnings())
.orElse(BigDecimal.ZERO)
.add(newAmount);
coupon.setTotalEarnings(totalEarnings);
// 佣金 = 总收益 × 佣金比例需除以100
BigDecimal commission = totalEarnings.multiply(
BigDecimal.valueOf(coupon.getCommissionRate())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
coupon.setCommission(commission);
// 未支付佣金 = 总佣金 - 已支付佣金默认0
BigDecimal unpaidCommission = commission.subtract(
Optional.ofNullable(coupon.getPaidCommission())
.orElse(BigDecimal.ZERO));
coupon.setUnpaidCommission(unpaidCommission);
}
}

View File

@@ -1413,8 +1413,8 @@ public class StripeServiceImpl implements StripeService {
.setDuration(CouponCreateParams.Duration.ONCE)
// percent_off 与 amount_off 不能同时设置
.setPercentOff(BigDecimal.valueOf(createCouponDTO.getPercentOff()));
if (Objects.nonNull(createCouponDTO.getTimestamp())){
couponParams.setRedeemBy(createCouponDTO.getTimestamp());
if (Objects.nonNull(createCouponDTO.getEndTime())){
couponParams.setRedeemBy(createCouponDTO.getEndTime());
}
try {
// 1、创建优惠券
@@ -1422,9 +1422,10 @@ public class StripeServiceImpl implements StripeService {
// 2、创建一个推广码
PromotionCode promotionCode = createPromotionCode(coupon.getId(), createCouponDTO.getMaxRedemptions());
// 3、落库
ProductCoupons productCoupons = new ProductCoupons(coupon.getId(), createCouponDTO.getTimestamp(), promotionCode.getId(),
ProductCoupons productCoupons = new ProductCoupons(coupon.getId(), createCouponDTO.getEndTime(), promotionCode.getId(),
promotionCode.getCode(), createCouponDTO.getMaxRedemptions(), createCouponDTO.getPercentOff(),
createCouponDTO.getCommissionRate(), createCouponDTO.getCooperator(), createCouponDTO.getRemark());
productCoupons.setStartTime(createCouponDTO.getStartTime());
productCoupons.setCreateTime(LocalDateTime.now());
productCouponsMapper.insert(productCoupons);
@@ -1450,7 +1451,7 @@ public class StripeServiceImpl implements StripeService {
}
}
public ProductCoupons updateCouponsInfo(Long id, Long paidCommission, String cooperator, String remark){
public ProductCoupons updateCouponsInfo(Long id, String paidCommission, String cooperator, String remark, Long startTime){
ProductCoupons productCoupons = productCouponsMapper.selectById(id);
if (Objects.isNull(productCoupons)){
throw new BusinessException("Unknown Promotion Code");
@@ -1465,7 +1466,22 @@ public class StripeServiceImpl implements StripeService {
flag = true;
}
if (Objects.nonNull(paidCommission)){
productCoupons.setPaidCommission(paidCommission);
// 将 paidCommission 从 String 转换为 BigDecimal
if (!paidCommission.matches("-?\\d+(\\.\\d+)?")) {
throw new BusinessException("Invalid paidCommission value: " + paidCommission);
}
BigDecimal paidCommissionBigDecimal = new BigDecimal(paidCommission);
// 设置已支付佣金
productCoupons.setPaidCommission(paidCommissionBigDecimal);
BigDecimal commission = Objects.isNull(productCoupons.getCommission()) ? new BigDecimal(0) : productCoupons.getCommission();
// 计算未支付佣金
BigDecimal unpaidCommission = commission.subtract(paidCommissionBigDecimal);
// 设置未支付佣金,确保其不为负数
productCoupons.setUnpaidCommission(unpaidCommission.compareTo(BigDecimal.ZERO) > 0 ? unpaidCommission : BigDecimal.ZERO);
flag = true;
}
if (Objects.nonNull(startTime)){
productCoupons.setStartTime(startTime);
flag = true;
}
if (flag){
@@ -1478,6 +1494,7 @@ public class StripeServiceImpl implements StripeService {
public ProductCoupons checkProductCoupon(String promotionCode){
Stripe.apiKey = privateKey;
Long accountId = UserContext.getUserHolder().getId();
String language = UserContext.getUserHolder().getLanguage();
// 1、从数据库查找promotionCode对应的promotionCodeId
ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
if (Objects.nonNull(productCoupons)){
@@ -1486,6 +1503,11 @@ public class StripeServiceImpl implements StripeService {
Long redeemBy = productCoupons.getRedeemBy();
if (redeemBy < epochSecondNow){
throw new BusinessException("this.promotion.code.has.expired");
} else if (Objects.nonNull(productCoupons.getStartTime()) && productCoupons.getStartTime() > epochSecondNow) {
String startTime = DateUtil.changeTimeStampFormat(productCoupons.getStartTime(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);
String en = "This coupon will become active on " + startTime + ". Please try again then!";
String cn = "该优惠券尚未到生效时间,请在 " + startTime + " 后使用。";
throw new BusinessException(language.equals("ENGLISH") ? en : cn);
} else {
// 判断该用户是否有成功使用过这个推广码
List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
@@ -1507,6 +1529,7 @@ public class StripeServiceImpl implements StripeService {
public CheckCouponsVO checkProductCoupon(String promotionCode, Long price){
Stripe.apiKey = privateKey;
Long accountId = UserContext.getUserHolder().getId();
String language = UserContext.getUserHolder().getLanguage();
CheckCouponsVO checkCouponsVO = new CheckCouponsVO();
// 1、从数据库查找promotionCode对应的promotionCodeId
ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
@@ -1518,7 +1541,13 @@ public class StripeServiceImpl implements StripeService {
String msg = BusinessException.getMessageFromResource("this.promotion.code.has.expired");
checkCouponsVO.setMessage(msg);
checkCouponsVO.setStatus("expired");
}else {
}else if (Objects.nonNull(productCoupons.getStartTime()) && productCoupons.getStartTime() > epochSecondNow) {
String startTime = DateUtil.changeTimeStampFormat(productCoupons.getStartTime(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);
String en = "This coupon will become active on " + startTime + ". Please try again then!";
String cn = "该优惠券尚未到生效时间,请在 " + startTime + " 后使用。";
checkCouponsVO.setMessage(language.equals("ENGLISH") ? en : cn);
checkCouponsVO.setStatus("pending");
} else {
// 判断该用户是否有成功使用过这个推广码
List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
if (paymentInfoByPromCode.isEmpty()) {