diff --git a/src/main/java/com/ai/da/common/constant/CommonConstant.java b/src/main/java/com/ai/da/common/constant/CommonConstant.java index 43c2c99b..85b67e36 100644 --- a/src/main/java/com/ai/da/common/constant/CommonConstant.java +++ b/src/main/java/com/ai/da/common/constant/CommonConstant.java @@ -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="; } diff --git a/src/main/java/com/ai/da/controller/StripeController.java b/src/main/java/com/ai/da/controller/StripeController.java index e02a1765..07ab7a5d 100644 --- a/src/main/java/com/ai/da/controller/StripeController.java +++ b/src/main/java/com/ai/da/controller/StripeController.java @@ -137,9 +137,11 @@ public class StripeController { @ApiOperation("更新推广码信息") @GetMapping("/updatePromCodeInfo") - public Response 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 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("删除推广码") diff --git a/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java b/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java index 88c9ed18..06a00810 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/ProductCoupons.java @@ -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; diff --git a/src/main/java/com/ai/da/model/dto/CreateCouponDTO.java b/src/main/java/com/ai/da/model/dto/CreateCouponDTO.java index 311f7c67..3e1c60b5 100644 --- a/src/main/java/com/ai/da/model/dto/CreateCouponDTO.java +++ b/src/main/java/com/ai/da/model/dto/CreateCouponDTO.java @@ -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("合作者/机构名") diff --git a/src/main/java/com/ai/da/model/vo/CheckCouponsVO.java b/src/main/java/com/ai/da/model/vo/CheckCouponsVO.java index 97b72d9c..dbbf2aeb 100644 --- a/src/main/java/com/ai/da/model/vo/CheckCouponsVO.java +++ b/src/main/java/com/ai/da/model/vo/CheckCouponsVO.java @@ -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; diff --git a/src/main/java/com/ai/da/service/StripeService.java b/src/main/java/com/ai/da/service/StripeService.java index 7087ca1a..4ab7b2b9 100644 --- a/src/main/java/com/ai/da/service/StripeService.java +++ b/src/main/java/com/ai/da/service/StripeService.java @@ -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); diff --git a/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java index 141b3f54..61af2518 100644 --- a/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java @@ -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 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 paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper); + Optional.ofNullable(lastTime) + .filter(time -> !time.isEmpty()) + .ifPresent(time -> queryWrapper.gt("create_time", time)); + + List paymentInfos = paymentInfoService.list(queryWrapper); log.info("目前,新增使用优惠券的订单数:{}", paymentInfos.size()); - // key:推广码, value:用户支付的金额 - HashMap 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 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 entry : codeAmount.entrySet()){ - String promotionCode = entry.getKey(); - ProductCoupons productCoupons = stripeService.getProductCoupon(promotionCode, null); - if (!Objects.isNull(productCoupons)){ - // 2、计算支付金额的总和,更新totalEarnings,commission,unpaidCommission - 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); + } + } diff --git a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java index e9611ca7..32a80574 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -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().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 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().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 paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode); if (paymentInfoByPromCode.isEmpty()) {