Affiliate功能-数据库表设计更新

This commit is contained in:
2024-12-18 11:53:41 +08:00
parent bf8af41f3f
commit 7d8f047087
16 changed files with 338 additions and 72 deletions

View File

@@ -7,8 +7,6 @@ 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<Affiliate> {
Boolean registerAsAnAffiliate(String promotionMethod);
@@ -17,11 +15,17 @@ public interface AffiliateService extends IService<Affiliate> {
AffiliateVO personalAffiliateCenter();
double[] getPersonalMonthlyIncome(int year);
Boolean applicationApproval(Long id, Boolean isApproved);
void updateAffiliateInfoWithPayment();
Boolean affiliateLinkViewsIncrease(Long id);
List<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(Long affiliateId, String startTime, String endTime);
IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO);
Affiliate getByAccountId(Long accountId);
void commissionCalculation(Integer year, Integer month);
}

View File

@@ -44,5 +44,7 @@ public interface StripeService {
boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo);
String getCustomerPaymentMethod(String name, String email);
List<Map<String,String>> getCustomerPaymentMethod(String name, String email);
String detachCustomerAllPaymentMethod(String name, String email);
}

View File

@@ -107,6 +107,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Resource
private StripeService stripeService;
@Resource
private AffiliateService affiliateService;
@Override
@Transactional(rollbackFor = Exception.class)
public AccountPreLoginVO preLogin(AccountPreLoginDTO accountDTO) {
@@ -2428,6 +2431,10 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
response.setAutoRenewal(subscriptionInfo.getStatus().equals("active"));
}
Affiliate affiliate = affiliateService.getByAccountId(accountId);
if (!Objects.isNull(affiliate) && affiliate.getStatus().equals("Active")) {
response.setAffiliate(true);
}
return response;
}

View File

@@ -7,6 +7,8 @@ import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.CopyUtil;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.SendEmailUtil;
import com.ai.da.mapper.primary.AccountMapper;
import com.ai.da.mapper.primary.AffiliateIncomeMapper;
import com.ai.da.mapper.primary.AffiliateMapper;
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
import com.ai.da.mapper.primary.entity.*;
@@ -31,9 +33,10 @@ 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.Map;
import java.util.Objects;
import java.util.function.Function;
@Service
@Slf4j
@@ -51,6 +54,9 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
@Resource
private SubscriptionInfoMapper subscriptionInfoMapper;
@Resource
private AffiliateIncomeMapper affiliateIncomeMapper;
@Resource
private RedisUtil redisUtil;
@@ -79,10 +85,13 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
}
public IPage<Affiliate> getAffiliateList(AffiliateQueryDTO affiliateQueryDTO){
log.info("parameter => {}", affiliateQueryDTO.toString());
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
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());
qw.eq(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStatus()), "status", affiliateQueryDTO.getStatus())
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "id", affiliateQueryDTO.getAffiliateId())
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
return baseMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), qw);
}
@@ -96,6 +105,18 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
return affiliateVO;
}
public double[] getPersonalMonthlyIncome(int year){
Long accountId = UserContext.getUserHolder().getId();
List<Map<String, Object>> personalMonthlyIncome = affiliateIncomeMapper.getPersonalMonthlyIncome(accountId, year);
double[] commissions = new double[12];
personalMonthlyIncome.forEach(income -> {
int month = Integer.parseInt(income.get("yearMonth").toString());
commissions[month-1] = (double)income.get("totalCommission");
});
return commissions;
}
// 审批申请
public Boolean applicationApproval(Long id, Boolean isApproved){
Affiliate affiliate = baseMapper.selectById(id);
@@ -107,7 +128,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
affiliate.setApproved(true);
affiliate.setLink(CommonConstant.AFFILIATE_LINK + affiliate.getId());
} else {
affiliate.setStatus("Inactive");
affiliate.setStatus("Refused");
affiliate.setApproved(false);
}
affiliate.setUpdateTime(LocalDateTime.now());
@@ -142,6 +163,9 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
paymentInfos.forEach(paymentInfo -> {
// 2、根据order_no查付款用户id
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(paymentInfo.getOrderNo());
if (Objects.isNull(orderInfo)){
return;
}
Long accountId = orderInfo.getAccountId();
// 3、查该用户之前是否有初次订阅的订单
QueryWrapper<OrderInfo> qwOrderInfo = new QueryWrapper<>();
@@ -155,6 +179,7 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
// 3、若有, 直接更新affiliate的所得
Affiliate affiliate = baseMapper.selectById(account.getInvitationCode());
Float payerTotal = paymentInfo.getPayerTotal();
if (payerTotal > 0){
// 分配新用户首次订阅所付费用的25%作为佣金
BigDecimal commission = BigDecimal.valueOf(payerTotal).multiply(new BigDecimal("0.25"));
@@ -168,6 +193,17 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
baseMapper.updateById(affiliate);
orderInfo.setIsCommissionCalculated((byte)1);
// 4、添加到t_affiliate_income
AffiliateIncome affiliateIncome = new AffiliateIncome();
affiliateIncome.setAffiliateId(affiliate.getId());
affiliateIncome.setAffiliateAccountId(affiliate.getAccountId());
affiliateIncome.setInviteeAccountId(accountId);
affiliateIncome.setAmount(payerTotal);
affiliateIncome.setPaymentTime(paymentInfo.getCreateTime());
affiliateIncome.setCommission(commission.floatValue());
affiliateIncome.setCreateTime(LocalDateTime.now());
affiliateIncomeMapper.insert(affiliateIncome);
}
}
orderInfo.setIsFirstSubscription((byte)1);
@@ -190,51 +226,69 @@ public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate
// 查看每个affiliate带来收入的详情
@Override
public List<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(Long affiliateId, String startTime, String endTime) {
List<AffiliateInvitationDetailsVO> resp = new ArrayList<>() ;
// 1、从account表中找到所有关联了指定affiliateId的accountId
QueryWrapper<Account> qw = new QueryWrapper<>();
qw.eq("invitation_code", affiliateId);
List<Account> accountList = accountService.getBaseMapper().selectList(qw);
if (accountList.isEmpty()){
return null;
} else {
accountList.forEach(account -> {
// 2、分别找到各个accountId产生的第一笔订阅
Long accountId = account.getId();
QueryWrapper<SubscriptionInfo> 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);
}
});
public IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO) {
if (Objects.isNull(affiliateQueryDTO.getAffiliateId())){
throw new BusinessException("Please specify the affiliate ID.", ResultEnum.PROMPT.getCode());
}
return resp;
QueryWrapper<AffiliateIncome> affiliateIncomeQueryWrapper = new QueryWrapper<>();
affiliateIncomeQueryWrapper
.gt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getStartTime()), "create_time", affiliateQueryDTO.getStartTime())
.lt(!StringUtils.isNullOrEmpty(affiliateQueryDTO.getEndTime()), "create_time", affiliateQueryDTO.getEndTime())
.eq(!Objects.isNull(affiliateQueryDTO.getAffiliateId()), "affiliate_id", affiliateQueryDTO.getAffiliateId())
.orderByDesc(affiliateQueryDTO.getOrder().equals("DESC"), "create_time");
IPage<AffiliateIncome> affiliateIncomePage = affiliateIncomeMapper.selectPage(new Page<>(affiliateQueryDTO.getPage(), affiliateQueryDTO.getSize()), affiliateIncomeQueryWrapper);
return affiliateIncomePage.convert((Function<AffiliateIncome, AffiliateInvitationDetailsVO>) affiliateIncome -> {
AffiliateInvitationDetailsVO affiliateInvitationDetailsVO = CopyUtil.copyObject(affiliateIncome, AffiliateInvitationDetailsVO.class);
affiliateInvitationDetailsVO.setAccountId(affiliateIncome.getInviteeAccountId());
affiliateInvitationDetailsVO.setUsername(accountService.getBaseMapper().selectById(affiliateIncome.getInviteeAccountId()).getUserName());
affiliateInvitationDetailsVO.setFirstSubscriptionPaymentAmount(affiliateIncome.getAmount());
affiliateInvitationDetailsVO.setCommission(affiliateIncome.getCommission());
affiliateInvitationDetailsVO.setTime(affiliateIncome.getPaymentTime());
return affiliateInvitationDetailsVO;
});
}
// todo 每个月给kim发一封邮件统计本月的affiliate等的收入
public void commissionCalculation(){
// 1、总收入(近一个月通过affiliate产生的收入)
public void commissionCalculation(Integer year, Integer month) {
if (Objects.isNull(year)) {
year = LocalDateTime.now().getYear();
}
if (Objects.isNull(month)) {
month = LocalDateTime.now().getMonthValue();
}
// 2、未支付的金额 affiliate表中unpaid的总和
List<Map<String, Object>> monthlyAffiliateIncome = affiliateIncomeMapper.getMonthlyAffiliateIncome(year, month);
// 1、总收入(近一个月通过affiliate产生的收入),未支付的金额 affiliate表中unpaid的总和
Double totalAmount = 0.0;
Double totalCommission = 0.0;
if (!monthlyAffiliateIncome.isEmpty()){
Map<String, Object> monthlyIncome = monthlyAffiliateIncome.get(0);
totalAmount = (Double) monthlyIncome.get("totalAmount");
totalCommission = (Double) monthlyIncome.get("totalCommission");
}
// 3、邀请的新人 查询account表中本月新增并有invitation_id的数量
// 2、本月新注册的Affiliate
Map<String, Long> monthlyApprovedAffiliate = baseMapper.getMonthlyApprovedAffiliate(year, month);
Long count = monthlyApprovedAffiliate.get("count");
AffiliateEmailParamsDTO affiliateEmailParamsDTO = new AffiliateEmailParamsDTO();
affiliateEmailParamsDTO.setTotalProgramRevenue(totalAmount.toString());
affiliateEmailParamsDTO.setNewApprovedAffiliates(count.toString());
affiliateEmailParamsDTO.setUnpaidEarnings(totalCommission.toString());
affiliateEmailParamsDTO.setPaidEarnings("0");
String receiverEmail = "xupei3360@163.com";
// String receiverEmail = "kimwong@code-create.com.hk";
// 邮件通知
SendEmailUtil.affiliateEmailReminder(receiverEmail, affiliateEmailParamsDTO, "summary");
}
@Override
public Affiliate getByAccountId(Long accountId) {
QueryWrapper<Affiliate> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("account_id", accountId).orderByDesc("id").last("limit 1");
return baseMapper.selectOne(queryWrapper);
}

View File

@@ -1004,8 +1004,8 @@ public class StripeServiceImpl implements StripeService {
try {
OrderInfo orderInfo = orderInfoService.createOrderByProductId(1, PayTypeEnum.STRIPE.getType(), ProductEnum.DailySubscription);
String customerId = getCustomer(name, email);
/* String paymentMethodCode = "pm_card_mastercard";
// String customerId = getCustomer(name, email);
String paymentMethodCode = "pm_card_mastercard";
PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodCode);
String customerId = getCustomer(name, email);
@@ -1014,14 +1014,14 @@ public class StripeServiceImpl implements StripeService {
PaymentMethodAttachParams attachParams = PaymentMethodAttachParams.builder()
.setCustomer(customerId)
.build();
paymentMethod.attach(attachParams);*/
paymentMethod.attach(attachParams);
// 设置默认付款方式
Customer updatedCustomer = Customer.retrieve(customerId);
CustomerUpdateParams params = CustomerUpdateParams.builder()
.setInvoiceSettings(
CustomerUpdateParams.InvoiceSettings.builder()
// .setDefaultPaymentMethod(paymentMethod.getId())
.setDefaultPaymentMethod(paymentMethod.getId())
.build()
)
.build();
@@ -1081,19 +1081,54 @@ public class StripeServiceImpl implements StripeService {
}
}
public String getCustomerPaymentMethod(String name, String email){
public List<Map<String,String>> getCustomerPaymentMethod(String name, String email){
Stripe.apiKey = privateKey;
try {
String customerId = getCustomer(name, email);
Customer customer = Customer.retrieve(customerId);
PaymentMethodCollection paymentMethodCollection = customer.listPaymentMethods();
List<PaymentMethod> data = paymentMethodCollection.getData();
ArrayList<Map<String,String>> resp = new ArrayList<>();
data.forEach(paymentMethod -> {
Map<String,String> map = new HashMap<>();
if (paymentMethod.getType().equals("card")){
map.put(paymentMethod.getId(),paymentMethod.getCard().getLast4());
}else {
map.put(paymentMethod.getId(),null);
}
resp.add(map);
});
// todo 方向: 向用户添加了多种付款方式,更改默认的付款方式后,默认付款方式付款失败后是否自动使用其他付款方式付款?
return resp;
// 方向: 向用户添加了多种付款方式,更改默认的付款方式后,默认付款方式付款失败后是否自动使用其他付款方式付款?
// 如果是的,则需要删除能成功的付款方式,保留唯一失败的付款方式进行续订付款失败测试
} catch (StripeException e) {
throw new RuntimeException(e);
}
}
public String detachCustomerAllPaymentMethod(String name, String email){
Stripe.apiKey = privateKey;
// 方向: 向用户添加了多种付款方式,更改默认的付款方式后,默认付款方式付款失败后是否自动使用其他付款方式付款?
// 如果是的,则需要删除能成功的付款方式,保留唯一失败的付款方式进行续订付款失败测试
try {
String customerId = getCustomer(name, email);
Customer customer = Customer.retrieve(customerId);
PaymentMethodCollection paymentMethodCollection = customer.listPaymentMethods();
List<PaymentMethod> data = paymentMethodCollection.getData();
data.forEach(paymentMethod -> {
try {
PaymentMethod retrieve = PaymentMethod.retrieve(paymentMethod.getId());
PaymentMethodDetachParams params = PaymentMethodDetachParams.builder().build();
retrieve.detach(params);
} catch (StripeException e) {
throw new RuntimeException(e);
}
});
} catch (StripeException e) {
throw new RuntimeException(e);
}
return null;
}
@@ -1102,8 +1137,10 @@ public class StripeServiceImpl implements StripeService {
qw.eq("subscription_id", subscriptionId);
SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw);
subscriptionInfo.setCancelReason(reason);
subscriptionInfoMapper.updateById(subscriptionInfo);
if (!Objects.isNull(subscriptionInfo)) {
subscriptionInfo.setCancelReason(reason);
subscriptionInfoMapper.updateById(subscriptionInfo);
}
}