From bf8af41f3f95b8cd29c7c170dc664cfe79e925f7 Mon Sep 17 00:00:00 2001 From: xupei Date: Mon, 16 Dec 2024 10:26:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Affiliate=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/da/common/constant/CommonConstant.java | 4 +- .../java/com/ai/da/common/utils/DateUtil.java | 19 ++++- .../ai/da/controller/AffiliateController.java | 12 ++- .../ai/da/controller/OrderInfoController.java | 10 ++- .../ai/da/controller/StripeController.java | 4 +- .../da/mapper/primary/PaymentInfoMapper.java | 7 ++ .../da/mapper/primary/entity/Affiliate.java | 2 + .../primary/entity/SubscriptionInfo.java | 3 + .../com/ai/da/model/vo/AccountLoginVO.java | 15 ++++ .../vo/AffiliateInvitationDetailsVO.java | 25 ++++++ .../java/com/ai/da/model/vo/OrderListVO.java | 29 +++++++ .../com/ai/da/service/AffiliateService.java | 5 ++ .../com/ai/da/service/PaymentInfoService.java | 8 +- .../java/com/ai/da/service/StripeService.java | 5 +- .../da/service/impl/AccountServiceImpl.java | 11 +++ .../da/service/impl/AffiliateServiceImpl.java | 70 ++++++++++++++-- .../service/impl/PaymentInfoServiceImpl.java | 48 ++++++++++- .../ai/da/service/impl/StripeServiceImpl.java | 83 ++++++++++++------- src/main/resources/application-dev.properties | 2 +- .../mapper/primary/PaymentInfoMapper.xml | 42 ++++++++++ 20 files changed, 349 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java create mode 100644 src/main/java/com/ai/da/model/vo/OrderListVO.java create mode 100644 src/main/resources/mapper/primary/PaymentInfoMapper.xml 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 cd019602..35ff4504 100644 --- a/src/main/java/com/ai/da/common/constant/CommonConstant.java +++ b/src/main/java/com/ai/da/common/constant/CommonConstant.java @@ -79,7 +79,5 @@ public class CommonConstant { public static final String TIME_FORMAT_MMM_dd_yyyy = "MMM. dd, yyyy"; - public static final String AFFILIATE_LINK = ""; - - + public static final String AFFILIATE_LINK = "https://www.aida.com.hk?ref="; } diff --git a/src/main/java/com/ai/da/common/utils/DateUtil.java b/src/main/java/com/ai/da/common/utils/DateUtil.java index 94269826..e1b1d755 100644 --- a/src/main/java/com/ai/da/common/utils/DateUtil.java +++ b/src/main/java/com/ai/da/common/utils/DateUtil.java @@ -9,8 +9,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.Calendar; import java.util.Date; +import java.util.Locale; import java.util.TimeZone; @Slf4j @@ -81,4 +81,21 @@ public class DateUtil { return String.valueOf(epochSecond).substring(0, 10); } + public static String changeTimeStampFormat(Long timeStamp, String type, String format){ + // 将秒级时间戳转换为毫秒级 + if (type.equals("seconds")){ + timeStamp = timeStamp * 1000; + } + // 输出格式 + SimpleDateFormat outputFormat = new SimpleDateFormat(format, Locale.ENGLISH); + // 创建Date对象 + Date date = new Date(timeStamp); + // 格式化输出 + return outputFormat.format(date); + } + + public static String changeTimeStampFormat(LocalDateTime localDate){ + return localDate.format(DateTimeFormatter.ofPattern("MMM. dd, yyyy, EEEE", Locale.US)); + } + } diff --git a/src/main/java/com/ai/da/controller/AffiliateController.java b/src/main/java/com/ai/da/controller/AffiliateController.java index 4e55ce71..f980dd10 100644 --- a/src/main/java/com/ai/da/controller/AffiliateController.java +++ b/src/main/java/com/ai/da/controller/AffiliateController.java @@ -4,19 +4,23 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; import com.ai.da.mapper.primary.entity.Affiliate; 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.service.AffiliateService; import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; +import java.util.List; @Slf4j @RestController @RequestMapping("/api/affiliate") +@Api(tags = "Affiliate模块") public class AffiliateController { @Resource @@ -46,7 +50,7 @@ public class AffiliateController { return Response.success(affiliateService.applicationApproval(id, isApproved)); } - @ApiOperation(value = "审批affiliate申请") + @ApiOperation(value = "定时计算佣金") @GetMapping("/testTask") public Response testTask() { affiliateService.updateAffiliateInfoWithPayment(); @@ -59,5 +63,11 @@ public class AffiliateController { return Response.success(affiliateService.affiliateLinkViewsIncrease(id)); } + @ApiOperation(value = "获取每个affiliate产生的收入") + @GetMapping("/getEachAffiliateGeneratedRevenue") + public Response> getEachAffiliateGeneratedRevenue(@RequestParam("id") Long id, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime) { + return Response.success(affiliateService.getEachAffiliateGeneratedRevenue(id, startTime, endTime)); + } + } diff --git a/src/main/java/com/ai/da/controller/OrderInfoController.java b/src/main/java/com/ai/da/controller/OrderInfoController.java index 3a9ce1d5..56e60a48 100644 --- a/src/main/java/com/ai/da/controller/OrderInfoController.java +++ b/src/main/java/com/ai/da/controller/OrderInfoController.java @@ -3,9 +3,10 @@ package com.ai.da.controller; import com.ai.da.common.enums.OrderStatusEnum; import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.response.Response; -import com.ai.da.mapper.primary.entity.OrderInfo; import com.ai.da.model.dto.QueryPageByTimeDTO; +import com.ai.da.model.vo.OrderListVO; import com.ai.da.service.OrderInfoService; +import com.ai.da.service.PaymentInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; @@ -22,10 +23,13 @@ public class OrderInfoController { @Resource private OrderInfoService orderInfoService; + @Resource + private PaymentInfoService paymentInfoService; + @ApiOperation("订单列表") @PostMapping("/list") - public Response> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){ - PageBaseResponse orderByAccountId = orderInfoService.getOrderByPage(queryPageByTimeDTO); + public Response> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){ + PageBaseResponse orderByAccountId = paymentInfoService.getPaymentInfo(queryPageByTimeDTO); // List list = orderInfoService.listOrderByCreateTimeDesc(); return Response.success(orderByAccountId); } diff --git a/src/main/java/com/ai/da/controller/StripeController.java b/src/main/java/com/ai/da/controller/StripeController.java index 21c91a86..ede043d6 100644 --- a/src/main/java/com/ai/da/controller/StripeController.java +++ b/src/main/java/com/ai/da/controller/StripeController.java @@ -70,8 +70,8 @@ public class StripeController { @ApiOperation("取消订阅") @GetMapping("/cancelSubscription") - public Response cancelSubscription(@RequestParam String subscriptionId) { - stripeService.cancelSubscription(subscriptionId); + public Response cancelSubscription(@RequestParam String subscriptionId, @RequestParam String reason) { + stripeService.cancelSubscription(subscriptionId, reason); return Response.success("success"); } @ApiOperation("临时 取消订阅") diff --git a/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java b/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java index 27b147a4..ea23d39d 100644 --- a/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java +++ b/src/main/java/com/ai/da/mapper/primary/PaymentInfoMapper.java @@ -1,7 +1,14 @@ package com.ai.da.mapper.primary; import com.ai.da.mapper.primary.entity.PaymentInfo; +import com.ai.da.model.vo.OrderListVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import java.util.List; + public interface PaymentInfoMapper extends BaseMapper { + + List selectPageOrderList(Long accountId, String startTime, String endTime, int offset, int pageSize); + + int queryOrderListTotalCount(Long accountId, String startTime, String endTime); } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java b/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java index 56204d75..beb52662 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/Affiliate.java @@ -25,4 +25,6 @@ public class Affiliate extends BaseEntity{ private Boolean approved = false; private String link; + + private String promotionMethod; } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java index b7895c08..7ee32ab0 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/SubscriptionInfo.java @@ -33,4 +33,7 @@ public class SubscriptionInfo extends BaseEntity{ // 当前订阅订单有效期结束时间 private Long currentPeriodEnd; + // 取消订阅原因 + private String cancelReason; + } diff --git a/src/main/java/com/ai/da/model/vo/AccountLoginVO.java b/src/main/java/com/ai/da/model/vo/AccountLoginVO.java index 9a1f9b85..8033057b 100644 --- a/src/main/java/com/ai/da/model/vo/AccountLoginVO.java +++ b/src/main/java/com/ai/da/model/vo/AccountLoginVO.java @@ -44,4 +44,19 @@ public class AccountLoginVO { private List accountExtendList; + // 订阅id(stripe提供) + private String subscriptionId; + + // 订阅状态 + private String status; + + // 订阅过期时间 + private String expireTime; + + // 订阅类型 month || year + private String subscriptionType; + + // 是否自动续订 + private boolean isAutoRenewal; + } diff --git a/src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java b/src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java new file mode 100644 index 00000000..4a9cdf54 --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/AffiliateInvitationDetailsVO.java @@ -0,0 +1,25 @@ +package com.ai.da.model.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AffiliateInvitationDetailsVO { + + private Long accountId; + + private String username; + + private Float firstSubscriptionPaymentAmount; + + private Float commission; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private LocalDateTime time; +} diff --git a/src/main/java/com/ai/da/model/vo/OrderListVO.java b/src/main/java/com/ai/da/model/vo/OrderListVO.java new file mode 100644 index 00000000..44e42bff --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/OrderListVO.java @@ -0,0 +1,29 @@ +package com.ai.da.model.vo; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 用于订单列表展示(展示的是所有支付信息) + */ +@Data +public class OrderListVO { + + private Long id; + + private Float amount; + + private String paymentMethod; + + private String state; + + private String orderType; + + private String invoiceLink; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") + private LocalDateTime createTime; +} diff --git a/src/main/java/com/ai/da/service/AffiliateService.java b/src/main/java/com/ai/da/service/AffiliateService.java index ed340ae4..768bb214 100644 --- a/src/main/java/com/ai/da/service/AffiliateService.java +++ b/src/main/java/com/ai/da/service/AffiliateService.java @@ -2,10 +2,13 @@ package com.ai.da.service; import com.ai.da.mapper.primary.entity.Affiliate; import com.ai.da.model.dto.AffiliateQueryDTO; +import com.ai.da.model.vo.AffiliateInvitationDetailsVO; import com.ai.da.model.vo.AffiliateVO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; +import java.util.List; + public interface AffiliateService extends IService { Boolean registerAsAnAffiliate(String promotionMethod); @@ -19,4 +22,6 @@ public interface AffiliateService extends IService { void updateAffiliateInfoWithPayment(); Boolean affiliateLinkViewsIncrease(Long id); + + List getEachAffiliateGeneratedRevenue(Long affiliateId, String startTime, String endTime); } diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java index 785fb286..761c0d8b 100644 --- a/src/main/java/com/ai/da/service/PaymentInfoService.java +++ b/src/main/java/com/ai/da/service/PaymentInfoService.java @@ -1,12 +1,16 @@ package com.ai.da.service; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.model.dto.AlipayHKCallbackDTO; +import com.ai.da.model.dto.QueryPageByTimeDTO; +import com.ai.da.model.vo.OrderListVO; import com.baomidou.mybatisplus.extension.service.IService; import com.paypal.orders.Order; import com.stripe.model.Charge; import com.stripe.model.Invoice; +import java.util.List; import java.util.Map; public interface PaymentInfoService extends IService { @@ -23,7 +27,9 @@ public interface PaymentInfoService extends IService { PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge); - PaymentInfo getPaymentInfoByOrderId(String orderId); + List getPaymentInfoByOrderNo(String orderId, String order); void updatePaymentStatusById(Long id, String status, String content); + + PageBaseResponse getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO); } diff --git a/src/main/java/com/ai/da/service/StripeService.java b/src/main/java/com/ai/da/service/StripeService.java index f6f60dc3..f0a95a7c 100644 --- a/src/main/java/com/ai/da/service/StripeService.java +++ b/src/main/java/com/ai/da/service/StripeService.java @@ -1,5 +1,6 @@ package com.ai.da.service; +import com.ai.da.mapper.primary.entity.SubscriptionInfo; import com.ai.da.model.dto.ProductPurchaseDTO; import com.stripe.exception.StripeException; @@ -13,6 +14,8 @@ public interface StripeService { Boolean notify(HttpServletRequest request); + SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId); + String refund(String amount, String orderId, String reason); void checkOrderStatus(String orderNo); @@ -21,7 +24,7 @@ public interface StripeService { Map getPaymentMethodByInvoiceId(String invoiceId); - void cancelSubscription(String orderNo); + void cancelSubscription(String orderNo, String cancelReason); void cancelSubscriptionTemp(String subscriptionId); diff --git a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java index 7fff9732..74ccd3f2 100644 --- a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java @@ -104,6 +104,9 @@ public class AccountServiceImpl extends ServiceImpl impl @Resource private RedisUtil redisUtil; + @Resource + private StripeService stripeService; + @Override @Transactional(rollbackFor = Exception.class) public AccountPreLoginVO preLogin(AccountPreLoginDTO accountDTO) { @@ -2416,6 +2419,14 @@ public class AccountServiceImpl extends ServiceImpl impl if (CollectionUtil.isNotEmpty(accountExtends)) { response.setAccountExtendList(accountExtends); } + SubscriptionInfo subscriptionInfo = stripeService.getLatestSubscriptionInfoByAccountId(accountId); + if (!Objects.isNull(subscriptionInfo)) { + response.setSubscriptionId(subscriptionInfo.getSubscriptionId()); + response.setSubscriptionType(subscriptionInfo.getType()); + response.setStatus(subscriptionInfo.getStatus()); + response.setExpireTime(String.valueOf(subscriptionInfo.getCurrentPeriodEnd())); + response.setAutoRenewal(subscriptionInfo.getStatus().equals("active")); + } return response; } 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 47f2a0b6..a249b08d 100644 --- a/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AffiliateServiceImpl.java @@ -5,13 +5,14 @@ import com.ai.da.common.constant.CommonConstant; import com.ai.da.common.context.UserContext; import com.ai.da.common.response.ResultEnum; import com.ai.da.common.utils.CopyUtil; -import com.ai.da.common.utils.ObjectUtils; import com.ai.da.common.utils.RedisUtil; import com.ai.da.common.utils.SendEmailUtil; import com.ai.da.mapper.primary.AffiliateMapper; +import com.ai.da.mapper.primary.SubscriptionInfoMapper; import com.ai.da.mapper.primary.entity.*; import com.ai.da.model.dto.AffiliateEmailParamsDTO; 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; @@ -22,6 +23,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.mysql.cj.util.StringUtils; import io.netty.util.internal.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -29,6 +31,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -45,6 +48,9 @@ public class AffiliateServiceImpl extends ServiceImpl getAffiliateList(AffiliateQueryDTO affiliateQueryDTO){ QueryWrapper qw = new QueryWrapper<>(); - qw.eq(affiliateQueryDTO.getStatus() != null, "status", affiliateQueryDTO.getStatus()); - qw.gt(affiliateQueryDTO.getStartTime() != null, "create_time", affiliateQueryDTO.getStartTime()); - qw.lt(affiliateQueryDTO.getEndTime() != null, "create_time", affiliateQueryDTO.getEndTime()); + qw.eq(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStatus()), "status", affiliateQueryDTO.getStatus()); + qw.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime()); + qw.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime()); return baseMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), qw); } @@ -85,7 +92,7 @@ public class AffiliateServiceImpl extends ServiceImpl getEachAffiliateGeneratedRevenue(Long affiliateId, String startTime, String endTime) { + List resp = new ArrayList<>() ; + // 1、从account表中找到所有关联了指定affiliateId的accountId + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("invitation_code", affiliateId); + + List accountList = accountService.getBaseMapper().selectList(qw); + if (accountList.isEmpty()){ + return null; + } else { + accountList.forEach(account -> { + // 2、分别找到各个accountId产生的第一笔订阅 + Long accountId = account.getId(); + QueryWrapper subscriptionInfoQueryWrapper = new QueryWrapper<>(); + subscriptionInfoQueryWrapper.eq("account_id", accountId) + .and(s -> s.eq("status", "active").or().eq("status", "canceled")) + .gt(!StringUtils.isNullOrEmpty(startTime) ,"create_time", startTime) + .lt(!StringUtils.isNullOrEmpty(endTime) ,"create_time", endTime).last("limit 1"); + + SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(subscriptionInfoQueryWrapper); + // 2、分别第一笔订阅的付款信息 + if (!Objects.isNull(subscriptionInfo)){ + PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderNo(subscriptionInfo.getOrderNo(), "ASC").get(0); + AffiliateInvitationDetailsVO affiliateInvitationDetailsVO = new AffiliateInvitationDetailsVO(); + affiliateInvitationDetailsVO.setAccountId(accountId); + affiliateInvitationDetailsVO.setUsername(account.getUserName()); + affiliateInvitationDetailsVO.setFirstSubscriptionPaymentAmount(paymentInfo.getPayerTotal()); + affiliateInvitationDetailsVO.setCommission(BigDecimal.valueOf(paymentInfo.getPayerTotal()).multiply(new BigDecimal("0.25")).floatValue()); + affiliateInvitationDetailsVO.setTime(subscriptionInfo.getCreateTime()); + resp.add(affiliateInvitationDetailsVO); + } + }); + } + return resp; } // todo 每个月给kim发一封邮件统计本月的affiliate等的收入 public void commissionCalculation(){ + // 1、总收入(近一个月通过affiliate产生的收入) + + // 2、未支付的金额 affiliate表中unpaid的总和 + + // 3、邀请的新人 查询account表中,本月新增并有invitation_id的数量 + } diff --git a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java index e5b35ed0..538886aa 100644 --- a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java @@ -1,11 +1,17 @@ package com.ai.da.service.impl; +import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.PayTypeEnum; +import com.ai.da.common.response.PageBaseResponse; import com.ai.da.mapper.primary.PaymentInfoMapper; import com.ai.da.mapper.primary.entity.PaymentInfo; import com.ai.da.model.dto.AlipayHKCallbackDTO; +import com.ai.da.model.dto.QueryPageByTimeDTO; +import com.ai.da.model.vo.OrderListVO; 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; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.gson.Gson; import com.paypal.orders.Order; @@ -15,15 +21,19 @@ import com.stripe.model.Charge; import com.stripe.model.Invoice; import com.stripe.model.Subscription; import com.stripe.model.checkout.Session; +import io.netty.util.internal.StringUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -313,11 +323,11 @@ public class PaymentInfoServiceImpl extends ServiceImpl getPaymentInfoByOrderNo(String orderId, String order){ QueryWrapper qw = new QueryWrapper<>(); - qw.eq("order_no", orderId); + qw.eq("order_no", orderId).orderByDesc(order.equals("DESC"),"id"); - return baseMapper.selectOne(qw); + return baseMapper.selectList(qw); } @Override @@ -329,4 +339,36 @@ public class PaymentInfoServiceImpl extends ServiceImpl getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO){ + Long accountId = UserContext.getUserHolder().getId(); + + String startTime = queryPageByTimeDTO.getStartTime(); + String endTime = queryPageByTimeDTO.getEndTime(); + if (StringUtil.isNullOrEmpty(startTime)) { + startTime = "2024-02-01 00:00:00"; + } + if (StringUtil.isNullOrEmpty(endTime)) { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + endTime = now.format(dateTimeFormatter); + } + + int offset = (queryPageByTimeDTO.getPage() - 1) * queryPageByTimeDTO.getSize(); + List orderListVOS = baseMapper.selectPageOrderList(accountId, startTime, endTime, offset, queryPageByTimeDTO.getSize()); + + if (CollectionUtils.isEmpty(orderListVOS)) { + return PageBaseResponse.success(new Page<>()); + }else { + int totalCount = baseMapper.queryOrderListTotalCount(accountId, startTime, endTime); + IPage orderListVOIPage = new Page<>(); + Integer size = queryPageByTimeDTO.getSize(); + orderListVOIPage.setSize(size); + orderListVOIPage.setRecords(orderListVOS); + orderListVOIPage.setCurrent(queryPageByTimeDTO.getPage()); + orderListVOIPage.setPages((long)Math.ceil((double) totalCount / size)); + orderListVOIPage.setTotal(totalCount); + return PageBaseResponse.success(orderListVOIPage); + } + } } 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 35b44944..3d776186 100644 --- a/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/StripeServiceImpl.java @@ -4,6 +4,7 @@ import com.ai.da.common.config.exception.BusinessException; import com.ai.da.common.constant.CommonConstant; import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.*; +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; @@ -37,11 +38,9 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.math.RoundingMode; -import java.text.SimpleDateFormat; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; import java.util.*; @SuppressWarnings("LoggingSimilarMessage") @@ -440,11 +439,9 @@ public class StripeServiceImpl implements StripeService { @Transactional(rollbackFor = Exception.class) public SubscriptionInfo createSubscription(Subscription subscription){ // 确认当前subscription是否已经记录 - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("subscription_id", subscription.getId()); - - SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw); - if (Objects.isNull(subscriptionInfo)){ + SubscriptionInfo subscriptionInfo = getSubscriptionInfoBySubId(subscription.getId()); +// SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw); + if (Objects.isNull(subscriptionInfo)) { String description = subscription.getDescription(); String orderNo = description.replace("AiDA - ", ""); OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo); @@ -459,7 +456,7 @@ public class StripeServiceImpl implements StripeService { subscriptionInfo.setSubscriptionId(subscription.getId()); subscriptionInfo.setType(interval); subscriptionInfo.setStatus(subscription.getStatus()); - subscriptionInfo.setNextPayDate(changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); + subscriptionInfo.setNextPayDate(DateUtil.changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart()); subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd()); subscriptionInfo.setCreateTime(LocalDateTime.now()); @@ -471,21 +468,34 @@ public class StripeServiceImpl implements StripeService { return subscriptionInfo; } - public String changeTimeStampFormat(Long timeStamp, String type, String format){ - // 将秒级时间戳转换为毫秒级 - if (type.equals("seconds")){ - timeStamp = timeStamp * 1000; + public SubscriptionInfo getSubscriptionInfoBySubId(String subId){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("subscription_id", subId); + + List subscriptionInfos = subscriptionInfoMapper.selectList(qw); + if (subscriptionInfos.size() == 1){ + return subscriptionInfos.get(0); + }else if (subscriptionInfos.size() > 1) { + // 如果新建了多个订阅,则筛选出状态为active的订单 + Optional activeSubscriptionInfo = subscriptionInfos.stream() + .filter(sub -> sub.getStatus().equals("active")) + .findFirst(); + + return activeSubscriptionInfo.orElseGet(() -> subscriptionInfos.get(0)); + }else { + return null; } - // 输出格式 - SimpleDateFormat outputFormat = new SimpleDateFormat(format, Locale.ENGLISH); - // 创建Date对象 - Date date = new Date(timeStamp); - // 格式化输出 - return outputFormat.format(date); } - public String changeTimeStampFormat(LocalDateTime localDate){ - return localDate.format(DateTimeFormatter.ofPattern("MMM. dd, yyyy, EEEE", Locale.US)); + public SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("account_id", accountId); + List subscriptionInfos = subscriptionInfoMapper.selectList(qw); + if (subscriptionInfos.isEmpty()){ + return null; + }else { + return subscriptionInfos.get(0); + } } @Transactional(rollbackFor = Exception.class) @@ -504,10 +514,10 @@ public class StripeServiceImpl implements StripeService { } if (!subscriptionInfo.getCurrentPeriodEnd().equals(subscription.getCurrentPeriodEnd())){ subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd()); - subscriptionInfo.setNextPayDate(changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); + subscriptionInfo.setNextPayDate(DateUtil.changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); // 更新账号到期时间 updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd()); - log.info("更新 {} 账号到期时间为:{}", subscriptionInfo.getAccountId(), changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); + log.info("更新 {} 账号到期时间为:{}", subscriptionInfo.getAccountId(), DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE)); flag = true; } if (flag){ @@ -526,7 +536,7 @@ public class StripeServiceImpl implements StripeService { } // 取消连续订阅 将订阅从pause状态转为cancel状态(使用定时器,定期检索DB中,过期且不续订的订阅) - public void cancelSubscription(String subscriptionId) { + public void cancelSubscription(String subscriptionId, String cancelReason) { Stripe.apiKey = privateKey; Long accountId = UserContext.getUserHolder().getId(); log.info("用户 {} 申请取消连续订阅 {}", accountId, subscriptionId); @@ -538,6 +548,9 @@ public class StripeServiceImpl implements StripeService { try { Subscription cancel = subscription.cancel(); cancel.getStatus(); + + // 更新数据库 + updateCancelReason(subscriptionId, cancelReason); } catch (StripeException e) { log.error("订阅 {} 取消失败, error message : {}", subscription.getId(), e.getMessage()); } @@ -564,8 +577,9 @@ public class StripeServiceImpl implements StripeService { try { Stripe.apiKey = privateKey; + // todo transactionId不再是sessionId而是invoiceId,所以这里需要更新 // 根据orderId找到对应的sessionId - String sessionId = paymentInfoService.getPaymentInfoByOrderId(orderNo).getTransactionId(); + String sessionId = paymentInfoService.getPaymentInfoByOrderNo(orderNo, "DESC").get(0).getTransactionId(); if (StringUtils.isNotEmpty(sessionId)) { //根据会话编号退款 Session session = Session.retrieve(sessionId); @@ -628,7 +642,8 @@ public class StripeServiceImpl implements StripeService { public void checkOrderStatus(String orderNo) { Stripe.apiKey = privateKey; // 1、通过orderNo 查询sessionId - PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderId(orderNo); + // todo transactionId不再是sessionId而是invoiceId,所以这里需要更新 + PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderNo(orderNo, "DESC").get(0); try { Session session = Session.retrieve(paymentInfo.getTransactionId()); if (Objects.isNull(session)) { @@ -822,8 +837,8 @@ public class StripeServiceImpl implements StripeService { emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " ")); emailParamsDTO.setQuantity(String.valueOf(1)); emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString()); - emailParamsDTO.setLastOrderDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); - emailParamsDTO.setEndOfPrepaidTerm(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setLastOrderDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setEndOfPrepaidTerm(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO); SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail()); @@ -941,9 +956,9 @@ public class StripeServiceImpl implements StripeService { emailParamsDTO.setSubscriptionId(subscriptionInfo.getId().toString()); emailParamsDTO.setFailMessage(orderByOrderNo.getNote()); emailParamsDTO.setSubscriptionType(subscriptionInfo.getType()); - emailParamsDTO.setStartDate(changeTimeStampFormat(orderByOrderNo.getCreateTime())); - emailParamsDTO.setNextPayDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); - emailParamsDTO.setRenewalTime(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setStartDate(DateUtil.changeTimeStampFormat(orderByOrderNo.getCreateTime())); + emailParamsDTO.setNextPayDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setRenewalTime(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); } public void subscriptionReminder(){ @@ -1082,6 +1097,14 @@ public class StripeServiceImpl implements StripeService { return null; } + public void updateCancelReason(String subscriptionId, String reason){ + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("subscription_id", subscriptionId); + SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw); + + subscriptionInfo.setCancelReason(reason); + subscriptionInfoMapper.updateById(subscriptionInfo); + } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 992396c3..2afd01c3 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -20,7 +20,7 @@ spring.security.jwtExpiration=8640000000 spring.security.ignorePaths=/,/favicon.ico,/doc.html,/webjars/**,/swagger-resources,/v2/api-docs,\ /api/account/**,/api/element/**,/api/python/**,/api/design/**,/api/history/**,/api/library/**,/api/third/party/**,/api/generate/**,/api/workspace/**,/api/classification/**,\ /api/product/**,/api/ali-pay/**,/api/order-info/**,/api/paypal/**,/api/credits/**,/api/inquiry/**,/api/tasks/**,/api/python/prepareForSR,/api/alipay-hk/**,/api/portfolio/**,\ - /api/stripe/**,/api/message/**,/notification/** + /api/stripe/**,/api/message/**,/notification/**,/api/affiliate/** spring.security.authApi=/auth/login diff --git a/src/main/resources/mapper/primary/PaymentInfoMapper.xml b/src/main/resources/mapper/primary/PaymentInfoMapper.xml new file mode 100644 index 00000000..8bab6ca5 --- /dev/null +++ b/src/main/resources/mapper/primary/PaymentInfoMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + +