TASK:Stripe订阅,添加月付(经济版)及相应费用、积分的更新

This commit is contained in:
2025-08-25 15:02:34 +08:00
parent eebb790aeb
commit e45d572078
9 changed files with 220 additions and 159 deletions

View File

@@ -13,19 +13,22 @@ import java.util.stream.Collectors;
@Getter
public enum CreditsEventsEnum {
PRICE("price","6"),
PRICE("price","10"),
// PRICE("price","1"),// for test
// PRICE("price","0.1"),
BUY_CREDITS("Buy Credits","60"),
BUY_CREDITS("Buy Credits","50"),
// BUY_CREDITS("Buy Credits","10"),// for test
REFUND("Refund","60"),
REFUND("Refund","50"),
// BUY_CREDITS("Buy Credits","10"),
// 每月更新
INIT_YEARLY("init_yearly", "6000"),
INIT_MONTHLY("init_monthly", "5000"),
INIT_YEARLY("init_yearly", "50000"),
INIT_MONTHLY("init_monthly", "3500"),
INIT_MONTHLY_ECO("init_monthly_eco", "500"),
INIT_QUARTERLY("init_quarterly", "12000"),
INIT_MONTHLY_EDU("init_monthly_edu", "3500"),
INIT_TRIAL("init_trial", "100"),
INIT_WEEKLY("init_weekly","6000"),
RESET_YEAR_CREDITS("reset_year_credits","6000"),

View File

@@ -7,13 +7,19 @@ import lombok.Getter;
@AllArgsConstructor
public enum ProductEnum {
// 积分购买
CreditsProduct("AiDA credits purchase", 6L),
CreditsProduct("AiDA credits purchase", 10L, 60L),
// 年度订阅
AnnualSubscription("AiDA Annual Subscription", 5000L),
// 月度订阅
MonthlySubscription("AiDA Monthly Subscription", 500L),
AnnualSubscription("AiDA Annual Subscription", 5000L, 50000L),
// 月度订阅订阅费500每月3500 积分)
MonthlySubscription("AiDA Monthly Subscription", 500L, 3500L),
// 月度订阅 -- 经济实惠版 订阅费100每月500 积分)
Eco_MonthlySubscription("AiDA Eco Monthly Subscription", 100L, 500L),
// 季度订阅
QuarterlySubscription("AiDA Quarterly Subscription", 1500L, 12000L),
// 月度订阅 -- 教育版
EDUMonthlySubscription("AiDA Edu Monthly Subscription", 200L, 3500L),
// 测试
DailySubscription("AiDA Daily Subscription", 5L),
DailySubscription("AiDA Daily Subscription", 5L, 100L),
;
/**
@@ -22,4 +28,6 @@ public enum ProductEnum {
private final String name;
private final Long price;
private final Long credits;
}

View File

@@ -28,7 +28,7 @@ public class AccountTask {
// @Scheduled(cron = "0 0 0 1 * ?")
public void refreshCreditsMonthly() {
log.info("每月1号0点 将年费用户积分重置为 6000");
accountService.refreshCreditsWeekly();
accountService.refreshCreditsMonthly();
}
// @Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes

View File

@@ -1,134 +1,137 @@
package com.ai.da.common.task;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.utils.DateUtil;
import com.ai.da.mapper.primary.PoseTransformationMapper;
import com.ai.da.mapper.primary.ToProductImageResultMapper;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.service.APIGenerateService;
import com.ai.da.service.CreditsService;
import com.ai.da.service.GenerateService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE;
@Slf4j
@Component
public class GenerateTask {
@Resource
private APIGenerateService apiGenerateService;
@Resource
private CreditsService creditsService;
@Resource
private GenerateService generateService;
@Resource
private ToProductImageResultMapper toProductImageResultMapper;
@Resource
private PoseTransformationMapper poseTransformationMapper;
/*
* 对于使用了第三方api的允许异步获得结果的生成功能可能在第三方接口的结果Ready时没有及时存储结果导致第三方链接失效
* 万相 24h失效
* flux 10mins失效 使用了flux接口的功能 ToProductImage || Relight, Pattern这里不做补偿
* 故这里通过定时任务做补偿
* flux五分钟查询一次万相1小时查询一次
*/
@Scheduled(cron = "0 */4 * * * ?")
public void fluxCompensationMechanism(){
// 1、查所有 任务还没成功、还没失败正在等待或者执行中的任务id有哪些
// 由于获取结果的polling_url在redis中只存一天大部分结果超过一天之后就无法再找到任务小部分可以通过公共路径查到结果
List<APIGenerate> apiGenerates = apiGenerateService.getPendingTaskByStatus("flux");
if (apiGenerates != null && !apiGenerates.isEmpty()){
for (APIGenerate apiGenerate : apiGenerates){
String taskId = apiGenerate.getTaskId();
// 1. 根据taskId查toProductImageResult 判断当前任务状态与超时状态
ToProductImageResult toProductImageResult = toProductImageResultMapper.selectOne(new QueryWrapper<ToProductImageResult>().eq("task_id", taskId));
if (Objects.nonNull(toProductImageResult) && "Pending".equals(toProductImageResult.getStatus())){
// 判断当前任务的超时状态
if (!DateUtil.isMoreThanOneDayApart(toProductImageResult.getCreateTime())){
// 1. 未超时,获取当前任务结果
String fileName = toProductImageResult.getResultType().equals(TO_PRODUCT_IMAGE.getName()) ? "product_image" : "relight_image";
String objectName = apiGenerate.getAccountId() + "/" + fileName +"/" + taskId + ".png";
String fluxResult = generateService.getFluxResult(taskId, objectName);
// 2. 成功获取结果下载图片上传至minio,更新toProductImageResult表
if (StringUtil.isNullOrEmpty(fluxResult) || fluxResult.equals("Fail")){
toProductImageResult.setStatus("Fail");
toProductImageResultMapper.updateById(toProductImageResult);
apiGenerate.setStatus("Fail");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
} else if (!fluxResult.equals("Pending")){
if (StringUtil.isNullOrEmpty(toProductImageResult.getUrl())){
toProductImageResult.setStatus("Success");
toProductImageResult.setUrl(fluxResult);
toProductImageResultMapper.updateById(toProductImageResult);
apiGenerate.setStatus("Success");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
}
// 扣积分
Boolean flag = creditsService.taskCreditsDeduction(apiGenerate.getAccountId(), taskId);
if (flag) creditsService.updateChangedCredits(String.valueOf(apiGenerate.getAccountId()), taskId);
}
} else {
// 超时,设置状态为失败
toProductImageResult.setStatus("Fail");
toProductImageResultMapper.updateById(toProductImageResult);
apiGenerate.setStatus("Fail");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
}
}
}
}
}
// 万相 -> pose transformation 补偿 一小时执行一次
@Scheduled(fixedDelay = 60 * 60 * 1000)
public void wxCompensationMechanism(){
List<APIGenerate> apiGenerates = apiGenerateService.getPendingTaskByStatus("wx");
if (apiGenerates != null && !apiGenerates.isEmpty()){
for (APIGenerate apiGenerate : apiGenerates){
String taskId = apiGenerate.getTaskId();
PoseTransformation poseTransformation = poseTransformationMapper.selectOne(new QueryWrapper<PoseTransformation>().eq("unique_id", taskId));
if (Objects.nonNull(poseTransformation) && "Pending".equals(poseTransformation.getTaskStatus())){
// 判断当前任务的超时状态
if (!DateUtil.isMoreThanOneDayApart(poseTransformation.getCreateTime())){
try {
// 方法中已经完成了pose_transformation和api_generate表的更新不用额外做处理
generateService.getAnimateResult(taskId);
} catch (BusinessException e){
log.warn("万相 animation 生成失败,原因:{}", e.getMessage());
}
}
} else {
poseTransformation.setTaskStatus("Fail");
poseTransformation.setUpdateTime(LocalDateTime.now());
poseTransformationMapper.updateById(poseTransformation);
apiGenerate.setStatus("Fail");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
}
}
}
}
}
package com.ai.da.common.task;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.utils.DateUtil;
import com.ai.da.mapper.primary.PoseTransformationMapper;
import com.ai.da.mapper.primary.ToProductImageResultMapper;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.service.APIGenerateService;
import com.ai.da.service.CreditsService;
import com.ai.da.service.GenerateService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE;
@Slf4j
@Component
public class GenerateTask {
@Resource
private APIGenerateService apiGenerateService;
@Resource
private CreditsService creditsService;
@Resource
private GenerateService generateService;
@Resource
private ToProductImageResultMapper toProductImageResultMapper;
@Resource
private PoseTransformationMapper poseTransformationMapper;
/*
* 对于使用了第三方api的允许异步获得结果的生成功能可能在第三方接口的结果Ready时没有及时存储结果导致第三方链接失效
* 万相 24h失效
* flux 10mins失效 使用了flux接口的功能 ToProductImage || Relight, Pattern这里不做补偿
* 故这里通过定时任务做补偿
* flux五分钟查询一次万相1小时查询一次
*/
@Scheduled(cron = "0 */4 * * * ?")
public void fluxCompensationMechanism(){
// 1、查所有 任务还没成功、还没失败正在等待或者执行中的任务id有哪些
// 由于获取结果的polling_url在redis中只存一天大部分结果超过一天之后就无法再找到任务小部分可以通过公共路径查到结果
List<APIGenerate> apiGenerates = apiGenerateService.getPendingTaskByStatus("flux");
if (apiGenerates != null && !apiGenerates.isEmpty()){
for (APIGenerate apiGenerate : apiGenerates){
String taskId = apiGenerate.getTaskId();
// 1. 根据taskId查toProductImageResult 判断当前任务状态与超时状态
ToProductImageResult toProductImageResult = toProductImageResultMapper.selectOne(new QueryWrapper<ToProductImageResult>().eq("task_id", taskId));
if (Objects.nonNull(toProductImageResult) && "Pending".equals(toProductImageResult.getStatus())){
// 判断当前任务的超时状态
if (!DateUtil.isMoreThanOneDayApart(toProductImageResult.getCreateTime())){
// 1. 未超时,获取当前任务结果
String fileName = toProductImageResult.getResultType().equals(TO_PRODUCT_IMAGE.getName()) ? "product_image" : "relight_image";
String objectName = apiGenerate.getAccountId() + "/" + fileName +"/" + taskId + ".png";
String fluxResult = generateService.getFluxResult(taskId, objectName);
// 2. 成功获取结果下载图片上传至minio,更新toProductImageResult表
if (StringUtil.isNullOrEmpty(fluxResult) || fluxResult.equals("Fail")){
toProductImageResult.setStatus("Fail");
toProductImageResultMapper.updateById(toProductImageResult);
apiGenerate.setStatus("Fail");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
} else if (!fluxResult.equals("Pending")){
if (StringUtil.isNullOrEmpty(toProductImageResult.getUrl())){
toProductImageResult.setStatus("Success");
toProductImageResult.setUrl(fluxResult);
toProductImageResultMapper.updateById(toProductImageResult);
apiGenerate.setStatus("Success");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
}
// 扣积分
Boolean flag = creditsService.taskCreditsDeduction(apiGenerate.getAccountId(), taskId);
if (flag) creditsService.updateChangedCredits(String.valueOf(apiGenerate.getAccountId()), taskId);
}
} else {
// 超时,设置状态为失败
toProductImageResult.setStatus("Fail");
toProductImageResultMapper.updateById(toProductImageResult);
apiGenerate.setStatus("Fail");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
}
}
}
}
}
// 万相 -> pose transformation 补偿 一小时执行一次
@Scheduled(fixedDelay = 60 * 60 * 1000)
public void wxCompensationMechanism(){
List<APIGenerate> apiGenerates = apiGenerateService.getPendingTaskByStatus("wx");
if (apiGenerates != null && !apiGenerates.isEmpty()){
for (APIGenerate apiGenerate : apiGenerates){
String taskId = apiGenerate.getTaskId();
PoseTransformation poseTransformation = poseTransformationMapper.selectOne(new QueryWrapper<PoseTransformation>().eq("unique_id", taskId));
if (Objects.nonNull(poseTransformation) && "Pending".equals(poseTransformation.getTaskStatus())){
// 判断当前任务的超时状态
if (!DateUtil.isMoreThanOneDayApart(poseTransformation.getCreateTime())){
try {
// 方法中已经完成了pose_transformation和api_generate表的更新不用额外做处理
PoseTransformationVO animateResult = generateService.getAnimateResult(taskId);
if (animateResult.getStatus().equals("Success")){
sendSysMsgToUser(poseTransformation.getAccountId(), "您的姿势变换生成任务已完成");
}
} catch (BusinessException e){
log.warn("万相 animation 生成失败,原因:{}", e.getMessage());
}
}
} else {
poseTransformation.setTaskStatus("Fail");
poseTransformation.setUpdateTime(LocalDateTime.now());
poseTransformationMapper.updateById(poseTransformation);
apiGenerate.setStatus("Fail");
apiGenerate.setUpdateTime(LocalDateTime.now());
apiGenerateService.updateById(apiGenerate);
}
}
}
}
}

View File

@@ -40,7 +40,7 @@ public class ProductCoupons extends BaseEntity{
private String remark;
@TableLogic
private Integer isDeleted;
private Integer isDeleted = 0;
public ProductCoupons(String couponId, Long redeemBy, String promotionCodeId, String promotionCode, Long maxRedemptions, float percentOff, float commissionRate, String cooperator, String remark) {
this.couponId = couponId;

View File

@@ -22,7 +22,7 @@ public class ProductPurchaseDTO {
@ApiModelProperty("产品名 CreditsPurchase || Subscription")
private String productName;
@ApiModelProperty("Month || Year")
@ApiModelProperty("EcoMonth || Month || Year")
private String subscribeType;
@ApiModelProperty("是否自动续订 one_time || recurring")

View File

@@ -152,7 +152,7 @@ public interface AccountService extends IService<Account> {
Boolean collectQuestionnaires(String questionnaireInfo);
void refreshCreditsWeekly();
void refreshCreditsMonthly();
List<Account> getExpiredUserBySystemUser(Integer systemUserNum);
@@ -227,7 +227,7 @@ public interface AccountService extends IService<Account> {
boolean updateAccountValidity(Long accountId, Long currentPeriodEnd);
void updateUserRoleAndCredits(Long accountId, String type);
void updateUserRoleAndCredits(Long accountId, String orderNo);
Boolean updateUserInfo(UpdateUserInfoDTO updateUserInfoDTO);

View File

@@ -3045,9 +3045,48 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
return true;
}
public void updateUserRoleAndCredits(Long accountId, String type){
@Override
public void updateUserRoleAndCredits(Long accountId, String orderNo){
Account account = accountMapper.selectById(accountId);
switch (type) {
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
if (Objects.nonNull(orderInfo) && !StringUtil.isNullOrEmpty(orderInfo.getTitle())){
String description = orderInfo.getTitle();
Long productCredits;
if (description.equals(ProductEnum.DailySubscription.getName())){
productCredits = ProductEnum.DailySubscription.getCredits();
account.setSystemUser(3);
account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_WEEKLY.getValue())));
}else if (description.equals(ProductEnum.MonthlySubscription.getName())){
productCredits = ProductEnum.MonthlySubscription.getCredits();
account.setSystemUser(2);
account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_MONTHLY.getValue())));
} else if (description.equals(ProductEnum.Eco_MonthlySubscription.getName())){
productCredits = ProductEnum.Eco_MonthlySubscription.getCredits();
account.setSystemUser(2);
account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_MONTHLY_ECO.getValue())));
} else if (description.equals(ProductEnum.AnnualSubscription.getName())){
productCredits = ProductEnum.AnnualSubscription.getCredits();
account.setSystemUser(1);
account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_YEARLY.getValue())));
}else {
log.error("未知订阅类型: {}", description);
return;
}
accountMapper.updateById(account);
// 先判断是否已添加添加积分变更记录
CreditsDetail creditsDetail = creditsService.queryDetailByTaskId(orderNo);
if (Objects.isNull(creditsDetail)){
creditsService.insertToCreditsDetail(accountId,
description + "--Stripe",
String.valueOf(productCredits),
"positive", orderNo);
}
} else {
log.error("orderNo: {} 无法找到对应的记录", orderNo);
}
/*switch (type) {
case "month":
account.setSystemUser(2);
account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_MONTHLY.getValue())));
@@ -3060,9 +3099,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
account.setSystemUser(3);
account.setCredits(BigDecimal.valueOf(Long.parseLong(CreditsEventsEnum.INIT_WEEKLY.getValue())));
break;
}
accountMapper.updateById(account);
}*/
}
public Boolean updateUserInfo(UpdateUserInfoDTO updateUserInfoDTO){

View File

@@ -108,6 +108,10 @@ public class StripeServiceImpl implements StripeService {
case "Month":
productEnum = ProductEnum.MonthlySubscription;
break;
case "EcoMonth":
productEnum = ProductEnum.Eco_MonthlySubscription;
productPurchaseDTO.setSubscribeType("Month");
break;
case "Year":
productEnum = ProductEnum.AnnualSubscription;
break;
@@ -586,7 +590,7 @@ public class StripeServiceImpl implements StripeService {
// 更新账号到期时间
boolean b = accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
// 更新账号身份和积分
if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), interval);
if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), orderNo);
}
}
return subscriptionInfo;
@@ -618,7 +622,13 @@ public class StripeServiceImpl implements StripeService {
// 获取一天后的时间戳(秒级)
ZonedDateTime now = ZonedDateTime.now();
currentPeriodEnd = now.plusMonths(1).toEpochSecond();
}else if (description.equals(ProductEnum.AnnualSubscription.getName())
} else if (description.equals(ProductEnum.Eco_MonthlySubscription.getName())
&& amount.equals(ProductEnum.Eco_MonthlySubscription.getPrice() * 100)){
interval = "month";
// 获取一天后的时间戳(秒级)
ZonedDateTime now = ZonedDateTime.now();
currentPeriodEnd = now.plusMonths(1).toEpochSecond();
} else if (description.equals(ProductEnum.AnnualSubscription.getName())
&& amount.equals(ProductEnum.AnnualSubscription.getPrice() * 100)){
interval = "year";
// 获取一天后的时间戳(秒级)
@@ -643,7 +653,7 @@ public class StripeServiceImpl implements StripeService {
// 更新账号到期时间
boolean b = accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
// 更新账号身份和积分
if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), interval);
if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), orderNo);
return true;
}
return true;
@@ -700,7 +710,7 @@ public class StripeServiceImpl implements StripeService {
// 更新账号到期时间
accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
// 更新账号身份和积分
accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getType());
accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getOrderNo());
log.info("更新 {} 账号到期时间为:{}", subscriptionInfo.getAccountId(), DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
flag = true;
}
@@ -708,7 +718,7 @@ public class StripeServiceImpl implements StripeService {
// 更新账号到期时间
boolean b = accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
// 更新账号身份和积分
if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getType());
if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getOrderNo());
}
if (flag){
subscriptionInfo.setUpdateTime(LocalDateTime.now());