Merge branch 'dev/dev_xp' into dev/dev

This commit is contained in:
2025-04-15 10:23:30 +08:00
17 changed files with 416 additions and 31 deletions

View File

@@ -113,4 +113,10 @@ public class PaymentTask {
}
}
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void calcCouponsCommission(){
log.info("优惠券佣金计算定时器");
affiliateService.calcCouponsCommission();
}
}

View File

@@ -4,8 +4,13 @@ import com.ai.da.common.response.Response;
import com.ai.da.common.utils.DateUtil;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.SendEmailUtil;
import com.ai.da.mapper.primary.entity.ProductCoupons;
import com.ai.da.model.dto.CreateCouponDTO;
import com.ai.da.model.dto.ProductPurchaseDTO;
import com.ai.da.model.dto.QueryCouponsPageDTO;
import com.ai.da.model.vo.CheckCouponsVO;
import com.ai.da.service.StripeService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.paypal.http.HttpResponse;
import com.paypal.payments.Refund;
import com.stripe.exception.StripeException;
@@ -99,6 +104,36 @@ public class StripeController {
stripeService.cancelSubscription(subscriptionId, reason);
return Response.success("success");
}
@ApiOperation("创建推广码")
@PostMapping("/createCoupon")
public Response<String> createCoupon(@Valid @RequestBody CreateCouponDTO createCouponDTO){
return Response.success(stripeService.createCoupon(createCouponDTO));
}
@ApiOperation("检查推广码")
@GetMapping("/checkCoupon")
public Response<CheckCouponsVO> checkCoupon(@RequestParam String promotionCode, @RequestParam Long price){
return Response.success(stripeService.checkProductCoupon(promotionCode, price));
}
@ApiOperation("获取所有推广码")
@PostMapping("/getAllCoupons")
public Response<IPage<ProductCoupons>> getAllCoupons(@RequestBody QueryCouponsPageDTO queryCouponsPageDTO){
return Response.success(stripeService.getAllCoupons(queryCouponsPageDTO));
}
@ApiOperation("检索优惠券")
@GetMapping("/retrieveCoupon")
public Response<String> retrieveCoupon(@RequestParam String couponId){
return Response.success(stripeService.retrieveCoupon(couponId));
}
@ApiOperation("检索推广码")
@GetMapping("/retrievePromotionCode")
public Response<String> retrievePromotionCode(@RequestParam String retrievePromotionCode){
return Response.success(stripeService.retrievePromotionCode(retrievePromotionCode));
}
/*@ApiOperation("临时 取消订阅")
@GetMapping("/cancelSubscriptionTemp")
public Response<String> cancelSubscriptionTemp(@RequestParam String subscriptionId) {

View File

@@ -29,4 +29,6 @@ public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {
List<Map<String, String>> getCountries();
int insertIgnore(@Param("paymentInfo")PaymentInfo paymentInfo);
List<PaymentInfo> selectPaidPaymentsByAccountAndPromotion(Long accountId, String promotionCode);
}

View File

@@ -47,4 +47,6 @@ public class PaymentInfo extends BaseEntity{
private String country;
private String city;
private String promotionCode;
}

View File

@@ -17,7 +17,7 @@ public class ProductCoupons extends BaseEntity{
// 对应的推广码
private String promotionCode;
// 最大兑换次数
private Integer maxRedemptions;
private Long maxRedemptions;
// 优惠券的折扣
private float percentOff;
// 佣金比例
@@ -38,11 +38,12 @@ public class ProductCoupons extends BaseEntity{
public ProductCoupons() {
}
public ProductCoupons(String couponId, Long redeemBy, String promotionCodeId, String promotionCode, float percentOff, float commissionRate) {
public ProductCoupons(String couponId, Long redeemBy, String promotionCodeId, String promotionCode, Long maxRedemptions, float percentOff, float commissionRate) {
this.couponId = couponId;
this.redeemBy = redeemBy;
this.promotionCodeId = promotionCodeId;
this.promotionCode = promotionCode;
this.maxRedemptions = maxRedemptions;
this.percentOff = percentOff;
this.commissionRate = commissionRate;
}

View File

@@ -0,0 +1,20 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class CreateCouponDTO {
@ApiModelProperty("折扣率")
@NotNull(message = "Please set the percentOff")
private Float percentOff;
@ApiModelProperty("佣金比例")
@NotNull(message = "Please set the commissionRate.")
private Float commissionRate;
@ApiModelProperty("推广码到期时间 秒级时间戳")
private Long timestamp;
@ApiModelProperty("推广码最大使用次数")
private Long maxRedemptions;
}

View File

@@ -30,4 +30,7 @@ public class ProductPurchaseDTO {
@ApiModelProperty("使用Alipay-HK时需要选择 ALIPAYHK || ALIPAYCN")
private String wallet;
@ApiModelProperty("优惠码")
private String promotionCode;
}

View File

@@ -0,0 +1,18 @@
package com.ai.da.model.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CheckCouponsVO {
@ApiModelProperty("expired || invalid || valid")
private String status;
private String message;
private Float discountedPrice;
}

View File

@@ -29,4 +29,6 @@ public interface AffiliateService extends IService<Affiliate> {
Affiliate getByAccountId(Long accountId);
void commissionCalculation(Integer year, Integer month);
void calcCouponsCommission();
}

View File

@@ -32,4 +32,6 @@ public interface PaymentInfoService extends IService<PaymentInfo> {
void updatePaymentStatusById(Long id, String status, String content);
PageBaseResponse<OrderListVO> getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO);
List<PaymentInfo> getPaymentInfoByPromCode(Long accountId, String promCode);
}

View File

@@ -1,7 +1,12 @@
package com.ai.da.service;
import com.ai.da.mapper.primary.entity.ProductCoupons;
import com.ai.da.mapper.primary.entity.SubscriptionInfo;
import com.ai.da.model.dto.CreateCouponDTO;
import com.ai.da.model.dto.ProductPurchaseDTO;
import com.ai.da.model.dto.QueryCouponsPageDTO;
import com.ai.da.model.vo.CheckCouponsVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.stripe.exception.StripeException;
import javax.servlet.http.HttpServletRequest;
@@ -51,4 +56,16 @@ public interface StripeService {
// Map getIp(HttpServletRequest request);
String getStackTrace(Exception e, int maxLines);
String createCoupon(CreateCouponDTO createCouponDTO);
CheckCouponsVO checkProductCoupon(String promotionCode, Long price);
ProductCoupons getProductCoupon(String promotionCode, String promotionCodeId);
String retrieveCoupon(String couponId);
String retrievePromotionCode(String promotionCode);
IPage<ProductCoupons> getAllCoupons(QueryCouponsPageDTO queryCouponsPageDTO);
}

View File

@@ -10,6 +10,7 @@ import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.SendEmailUtil;
import com.ai.da.mapper.primary.AffiliateIncomeMapper;
import com.ai.da.mapper.primary.AffiliateMapper;
import com.ai.da.mapper.primary.ProductCouponsMapper;
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
@@ -17,10 +18,7 @@ import com.ai.da.model.dto.AffiliateQueryDTO;
import com.ai.da.model.vo.AffiliateInvitationDetailsVO;
import com.ai.da.model.vo.AffiliateVO;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.ai.da.service.AccountService;
import com.ai.da.service.AffiliateService;
import com.ai.da.service.OrderInfoService;
import com.ai.da.service.PaymentInfoService;
import com.ai.da.service.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -34,9 +32,7 @@ import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.function.Function;
@Service
@@ -45,19 +41,18 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
@Resource
private OrderInfoService orderInfoService;
@Resource
private AccountService accountService;
@Resource
private PaymentInfoService paymentInfoService;
@Resource
private SubscriptionInfoMapper subscriptionInfoMapper;
@Resource
private AffiliateIncomeMapper affiliateIncomeMapper;
@Resource
private StripeService stripeService;
@Resource
private ProductCouponsMapper productCouponsMapper;
@Resource
private RedisUtil redisUtil;
@@ -328,5 +323,47 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
return baseMapper.selectOne(queryWrapper);
}
public void calcCouponsCommission(){
// id存redis
String lastTime = redisUtil.getFromString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME);
String currentTime = LocalDateTime.now().toString();
// 1、查上次更新之后有无使用了优惠券的新订单
QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
if (!StringUtil.isNullOrEmpty(lastTime)){
queryWrapper.gt("create_time", lastTime)
.lt("create_time", currentTime)
.isNotNull("promotion_code");
}
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
// 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());
}
}
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);
}
}
}
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
}
}

View File

@@ -6,6 +6,7 @@ import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.mapper.primary.PaymentInfoMapper;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.mapper.primary.entity.PaymentInfo;
import com.ai.da.mapper.primary.entity.ProductCoupons;
import com.ai.da.model.dto.AlipayHKCallbackDTO;
import com.ai.da.model.dto.QueryPageByTimeDTO;
import com.ai.da.model.vo.OrderListVO;
@@ -232,20 +233,27 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
qw.eq("transaction_id", invoiceId);
PaymentInfo paymentInfo = baseMapper.selectOne(qw);
String status = invoice.getStatus();
// 判断是否有优惠码
String promotionCode = null;
if (Objects.nonNull(invoice.getDiscount()) && !StringUtil.isNullOrEmpty(invoice.getDiscount().getPromotionCode())){
ProductCoupons productCoupon = stripeService.getProductCoupon(null, invoice.getDiscount().getPromotionCode());
promotionCode = productCoupon.getPromotionCode();
}
// 判断当前支付是否已经被记录,确保同一个支付不会被重复记录
if (Objects.isNull(paymentInfo)){
String orderNo;
try {
String chargeId = invoice.getCharge();
orderNo = Charge.retrieve(chargeId).getDescription().replace("AiDA - ", "");
// if (invoice.getBillingReason().equals("manual")){
// // 手动创建的发票针对one-time支付
//// orderNo = invoice.getLines().getData().get(0).getPrice().getMetadata().get("orderId");
// }else {
// String subscriptionId = invoice.getSubscription();
// // 从subscription中获取orderNo
// orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", "");
// }
if (invoice.getBillingReason().equals("manual")){
// 手动创建的发票针对one-time支付
// orderNo = invoice.getLines().getData().get(0).getPrice().getMetadata().get("orderId");
// 当支付失败时chargeId为空
String chargeId = invoice.getCharge();
orderNo = Charge.retrieve(chargeId).getDescription().replace("AiDA - ", "");
}else {
String subscriptionId = invoice.getSubscription();
// 从subscription中获取orderNo
orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", "");
}
} catch (StripeException e) {
throw new RuntimeException(e);
}
@@ -281,6 +289,7 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
paymentInfo.setPaymentMethod(paymentMethod.get("paymentMethod"));
paymentInfo.setLast4(paymentMethod.get("last4"));
paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl());
paymentInfo.setPromotionCode(promotionCode);
paymentInfo.setCreateTime(LocalDateTime.now());
if (!Objects.isNull(orderByOrderNo)){
paymentInfo.setCountry(orderByOrderNo.getCountry());
@@ -291,6 +300,7 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
log.info("Payment Info insert affect rows:{}", row);
}else {
paymentInfo.setTradeState(status);
paymentInfo.setPromotionCode(promotionCode);
paymentInfo.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(paymentInfo);
}
@@ -421,4 +431,8 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
return PageBaseResponse.success(orderListVOIPage);
}
}
public List<PaymentInfo> getPaymentInfoByPromCode(Long accountId, String promCode){
return baseMapper.selectPaidPaymentsByAccountAndPromotion(accountId, promCode);
}
}

View File

@@ -8,14 +8,20 @@ import com.ai.da.common.utils.DateUtil;
import com.ai.da.common.utils.SendEmailUtil;
import com.ai.da.mapper.primary.AccountMapper;
import com.ai.da.mapper.primary.PaymentInfoMapper;
import com.ai.da.mapper.primary.ProductCouponsMapper;
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.CreateCouponDTO;
import com.ai.da.model.dto.ProductPurchaseDTO;
import com.ai.da.model.dto.QueryCouponsPageDTO;
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
import com.ai.da.model.vo.CheckCouponsVO;
import com.ai.da.service.*;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
import com.stripe.Stripe;
import com.stripe.exception.SignatureVerificationException;
@@ -67,6 +73,8 @@ public class StripeServiceImpl implements StripeService {
private SubscriptionInfoMapper subscriptionInfoMapper;
@Resource
private PaymentInfoMapper paymentInfoMapper;
@Resource
private ProductCouponsMapper productCouponsMapper;
@Value("${stripe.private-key}")
private String privateKey;
@@ -85,6 +93,9 @@ public class StripeServiceImpl implements StripeService {
public String pay(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) {
Stripe.apiKey = privateKey;
//创建支付信息得到url
// 一次性支付和周期扣款需要区分mode: payment || subscription || setup
SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder();
ProductEnum productEnum;
switch (productPurchaseDTO.getProductName()){
case "CreditsPurchase":
@@ -105,6 +116,8 @@ public class StripeServiceImpl implements StripeService {
default:
throw new BusinessException("unknown subscription type");
}
// 只有订阅时才允许使用推广码优惠
// sessionBuilder.setAllowPromotionCodes(true);
break;
default:
throw new BusinessException("unknown product type");
@@ -131,12 +144,11 @@ public class StripeServiceImpl implements StripeService {
String priceId = getPrice(productEnum.getPrice(), productId, payType, productPurchaseDTO.getSubscribeType());
// 获取或创建customer
String customerId = getCustomer(account.getUserName(), account.getUserEmail());
log.info("customerId:{}", customerId);
// 获取自定义订单号
String orderId = orderInfo.getOrderNo();
//创建支付信息得到url
// 一次性支付和周期扣款需要区分mode: payment || subscription || setup
SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder();
if (payType.equals("recurring")){
sessionBuilder.setMode(SessionCreateParams.Mode.SUBSCRIPTION);
sessionBuilder.setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setDescription("AiDA - " + orderId).build());
@@ -148,8 +160,6 @@ public class StripeServiceImpl implements StripeService {
}
sessionBuilder.setPaymentMethodConfiguration(paymentMethodConfigurationId);
// sessionBuilder.addPaymentMethodType(SessionCreateParams.PaymentMethodType.ALIPAY);
// sessionBuilder.addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD);
sessionBuilder.setCustomer(customerId);
sessionBuilder.setSuccessUrl(productPurchaseDTO.getReturnUrl());//可自定义成功页面
sessionBuilder.setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN);
@@ -160,6 +170,16 @@ public class StripeServiceImpl implements StripeService {
.build());
sessionBuilder.putMetadata("orderId", orderId); //通过订单号关联用于检索支付信息(可选)
// 添加优惠券
String promotionCode = productPurchaseDTO.getPromotionCode();
if (!StringUtil.isNullOrEmpty(promotionCode)){
ProductCoupons productCoupon = checkProductCoupon(promotionCode);;
if (productCoupon != null){
sessionBuilder.addDiscount(SessionCreateParams.Discount.builder()
.setPromotionCode(productCoupon.getPromotionCodeId()).build());
}
}
Session session = Session.create(sessionBuilder.build());
List<String> paymentMethodTypes = session.getPaymentMethodTypes();
log.info("paymentMethodTypes: {}", paymentMethodTypes);
@@ -171,6 +191,8 @@ public class StripeServiceImpl implements StripeService {
// 更新order信息
orderInfoService.updateOrderNoById(orderInfo.getId(), orderId);
return session.getUrl();
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("创建支付会话出现异常:", e);
}
@@ -1346,4 +1368,192 @@ public class StripeServiceImpl implements StripeService {
}
return sb.toString();
}
/*
* 优惠券实施:
* 1、由管理员创建优惠券手动设置该优惠券对应的折扣
* 2、用户在订阅时手动输入优惠码
* 3、使用stripe的字段 redeem_by 管理优惠券的有效期
* 4、promotion code与 coupons需要绑定
* 5、用户只能使用一次优惠券
*
* 问题:对于一次付款和订阅,优惠码是否可以混用
*/
public String createCoupon(CreateCouponDTO createCouponDTO){
Stripe.apiKey = privateKey;
CouponCreateParams.Builder couponParams = CouponCreateParams.builder()
// 任何客户只能用一次这个优惠券
.setDuration(CouponCreateParams.Duration.ONCE)
.setPercentOff(BigDecimal.valueOf(createCouponDTO.getPercentOff()));
if (Objects.nonNull(createCouponDTO.getTimestamp())){
couponParams.setRedeemBy(createCouponDTO.getTimestamp());
}
try {
// 1、创建优惠券
Coupon coupon = Coupon.create(couponParams.build());
// 2、创建一个推广码
PromotionCode promotionCode = createPromotionCode(coupon.getId(), createCouponDTO.getMaxRedemptions());
// 3、落库
ProductCoupons productCoupons = new ProductCoupons(coupon.getId(), createCouponDTO.getTimestamp(), promotionCode.getId(),
promotionCode.getCode(), createCouponDTO.getMaxRedemptions(), createCouponDTO.getPercentOff(), createCouponDTO.getCommissionRate());
productCoupons.setCreateTime(LocalDateTime.now());
productCouponsMapper.insert(productCoupons);
return promotionCode.getCode();
} catch (StripeException e) {
throw new RuntimeException(e);
}
}
public PromotionCode createPromotionCode(String couponId, Long maxRedemption){
Stripe.apiKey = privateKey;
PromotionCodeCreateParams.Builder promotionCodeParams = PromotionCodeCreateParams.builder()
.setCoupon(couponId)
.setRestrictions(PromotionCodeCreateParams.Restrictions.builder().build());
if (Objects.nonNull(maxRedemption)){
promotionCodeParams.setMaxRedemptions(maxRedemption);
}
try {
return PromotionCode.create(promotionCodeParams.build());
} catch (StripeException e) {
throw new RuntimeException(e);
}
}
public ProductCoupons checkProductCoupon(String promotionCode){
Stripe.apiKey = privateKey;
Long accountId = UserContext.getUserHolder().getId();
// 1、从数据库查找promotionCode对应的promotionCodeId
ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
if (Objects.nonNull(productCoupons)){
// 2、查绑定的Coupons是否存在以及是否过期
long epochSecondNow = Instant.now().getEpochSecond();
Long redeemBy = productCoupons.getRedeemBy();
if (redeemBy < epochSecondNow){
throw new BusinessException("this.promotion.code.has.expired");
} else {
// 判断该用户是否有成功使用过这个推广码
List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
if (!paymentInfoByPromCode.isEmpty()) {
// 已使用过推广码,状态无效
if (paymentInfoByPromCode.size() > 1) {
log.error("用户[{}]多次成功使用优惠码[{}]", accountId, promotionCode);
}
log.info("用户[{}]已成功使用过优惠码[{}]", accountId, promotionCode);
throw new BusinessException("one.time.limit.per.customer");
}
}
}else {
throw new BusinessException("this.promotion.code.is.invalid");
}
return productCoupons;
}
public CheckCouponsVO checkProductCoupon(String promotionCode, Long price){
Stripe.apiKey = privateKey;
Long accountId = UserContext.getUserHolder().getId();
CheckCouponsVO checkCouponsVO = new CheckCouponsVO();
// 1、从数据库查找promotionCode对应的promotionCodeId
ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
if (Objects.nonNull(productCoupons)){
// 2、查绑定的Coupons是否存在以及是否过期
long epochSecondNow = Instant.now().getEpochSecond();
Long redeemBy = productCoupons.getRedeemBy();
if (redeemBy < epochSecondNow){
String msg = BusinessException.getMessageFromResource("this.promotion.code.has.expired");
checkCouponsVO.setMessage(msg);
checkCouponsVO.setStatus("expired");
}else {
// 判断该用户是否有成功使用过这个推广码
List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
if (paymentInfoByPromCode.isEmpty()) {
// 未使用过推广码,状态有效
checkCouponsVO.setStatus("valid");
checkCouponsVO.setDiscountedPrice(price * (1 - productCoupons.getPercentOff() / 100));
} else {
// 已使用过推广码,状态无效
if (paymentInfoByPromCode.size() > 1) {
log.error("用户[{}]多次成功使用优惠码[{}]", accountId, promotionCode);
}
log.info("用户[{}]已成功使用过优惠码[{}]", accountId, promotionCode);
String msg = BusinessException.getMessageFromResource("one.time.limit.per.customer");
checkCouponsVO.setMessage(msg);
checkCouponsVO.setStatus("invalid");
}
}
}else {
String msg = BusinessException.getMessageFromResource("this.promotion.code.is.invalid");
checkCouponsVO.setMessage(msg);
checkCouponsVO.setStatus("invalid");
}
return checkCouponsVO;
}
public ProductCoupons getProductCoupon(String promotionCode, String promotionCodeId){
Stripe.apiKey = privateKey;
QueryWrapper<ProductCoupons> qw = new QueryWrapper<>();
// 1、从数据库查找promotionCode对应的promotionCodeId
if (!StringUtil.isNullOrEmpty(promotionCode)){
qw.eq("promotion_code", promotionCode);
}
if (!StringUtil.isNullOrEmpty(promotionCodeId)){
qw.eq("promotion_code_id", promotionCodeId);
}
return productCouponsMapper.selectOne(qw);
}
public String retrieveCoupon(String couponId){
Stripe.apiKey = privateKey;
try {
Coupon coupon = Coupon.retrieve(couponId);
log.info("retrieve的coupon: {}", coupon);
return JSON.toJSONString(coupon);
} catch (StripeException e) {
throw new RuntimeException(e);
}
}
public String retrievePromotionCode(String promotionCode){
Stripe.apiKey = privateKey;
try {
ProductCoupons productCoupon = getProductCoupon(promotionCode, null);
PromotionCode retrieve = PromotionCode.retrieve(productCoupon.getPromotionCodeId());
log.info("retrieve的promotionCode: {}", retrieve);
return JSON.toJSONString(retrieve);
} catch (StripeException e) {
throw new RuntimeException(e);
}
}
public IPage<ProductCoupons> getAllCoupons(QueryCouponsPageDTO queryCouponsPageDTO){
// 分页 + 按条件查询
QueryWrapper<ProductCoupons> queryWrapper = new QueryWrapper<>();
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getOrderById()) && queryCouponsPageDTO.getOrderById().equals("DESC")){
queryWrapper.orderByDesc("id");
}
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getPromotionCode())){
queryWrapper.like("promotion_code", queryCouponsPageDTO.getPromotionCode());
}
if (Objects.nonNull(queryCouponsPageDTO.getIsExpired())){
long epochSecond = Instant.now().getEpochSecond();
if (queryCouponsPageDTO.getIsExpired()){
queryWrapper.lt("redeem_by", epochSecond);
}else {
queryWrapper.gt("redeem_by", epochSecond);
}
}
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getCooperator())){
queryWrapper.like("cooperator", queryCouponsPageDTO.getCooperator());
}
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getStartTime())){
queryWrapper.gt("create_time", queryCouponsPageDTO.getStartTime());
}
if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getEndTime())){
queryWrapper.lt("create_time", queryCouponsPageDTO.getEndTime());
}
return productCouponsMapper.selectPage(new Page<>(queryCouponsPageDTO.getPage(), queryCouponsPageDTO.getSize()), queryWrapper);
}
}

View File

@@ -167,11 +167,21 @@
INSERT IGNORE INTO
t_payment_info (order_no, transaction_id, payment_type, trade_state, payer_total,
type, content, notified, payment_method, last4, hosted_invoice_url,
country, city, ip_address, create_time)
country, city, ip_address, promotion_code, create_time)
VALUES (#{paymentInfo.orderNo}, #{paymentInfo.transactionId}, #{paymentInfo.paymentType},
#{paymentInfo.tradeState}, #{paymentInfo.payerTotal},#{paymentInfo.type}, #{paymentInfo.content},
#{paymentInfo.notified},#{paymentInfo.paymentMethod}, #{paymentInfo.last4},#{paymentInfo.hostedInvoiceUrl},
#{paymentInfo.country},#{paymentInfo.city},#{paymentInfo.ipAddress},#{paymentInfo.createTime});
#{paymentInfo.country},#{paymentInfo.city},#{paymentInfo.ipAddress},#{paymentInfo.promotionCode},#{paymentInfo.createTime});
</insert>
<select id="selectPaidPaymentsByAccountAndPromotion" resultType="com.ai.da.mapper.primary.entity.PaymentInfo">
SELECT pi.*
FROM t_order_info oi
LEFT JOIN t_payment_info pi ON oi.order_no = pi.order_no
WHERE oi.account_id = #{accountId}
AND pi.promotion_code = #{promotionCode}
AND pi.trade_state = 'paid'
</select>
</mapper>

View File

@@ -153,6 +153,9 @@ generate.result.below.standard=The quality of the generated images currently fal
partial.design.failed=Partial design failed, Please try again later.
email.count.limit=Rate limit reached. Retry in 1 hour.
model.path.cannot.be.empty=Model path cannot be empty.
this.promotion.code.has.expired=This promotion code has expired.
this.promotion.code.is.invalid=This promotion code is invalid.
one.time.limit.per.customer=This code has already been redeemed. Promo codes are limited to one-time use per customer.
# 可能会报异常
# Informative:

View File

@@ -149,6 +149,9 @@ generate.result.below.standard=当前生成的图像质量低于标准。请考
partial.design.failed=局部设计失败。请稍后重试。
email.count.limit=您的账号触发邮件发送频率限制,请一小时后重试。
model.path.cannot.be.empty=模特路径不能为空
this.promotion.code.has.expired=该促销码已过期。
this.promotion.code.is.invalid=该促销码无效。
one.time.limit.per.customer=该码已兑换。每个促销码每位用户仅限使用一次。
# 可能会报异常
# Informative: