Merge branch 'dev/dev_xp' into dev/dev

# Conflicts:
#	src/main/java/com/ai/da/model/vo/AccountLoginVO.java
#	src/main/java/com/ai/da/service/impl/AccountServiceImpl.java
This commit is contained in:
2024-12-18 14:02:06 +08:00
58 changed files with 2659 additions and 403 deletions

View File

@@ -75,5 +75,9 @@ public class CommonConstant {
public static final String PORTFOLIO_DELETED_CN = "作品已删除";
public static final String TIME_FORMAT_MMM_dd_yyyy_EEEE = "MMM. dd, yyyy, EEEE";
public static final String TIME_FORMAT_MMM_dd_yyyy = "MMM. dd, yyyy";
public static final String AFFILIATE_LINK = "https://www.aida.com.hk?ref=";
}

View File

@@ -10,38 +10,34 @@ public enum OrderStatusEnum {
* 未支付
*/
NOT_PAY("未支付"),
/**
* 支付成功
*/
SUCCESS("支付成功"),
/**
* 支付失败
*/
FAILURE("支付失败"),
/**
* 已关闭
*/
TIMEOUT_CLOSED("超时已关闭"),
/**
* 已取消
*/
CANCEL("用户已取消"),
/**
* 退款中
*/
REFUND_PROCESSING("退款中"),
/**
* 已退款
*/
REFUND_SUCCESS("已退款"),
/**
* 退款异常
*/
REFUND_ABNORMAL("退款异常"),
/**
* paypal订单状态为 APPROVED
*/

View File

@@ -0,0 +1,25 @@
package com.ai.da.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ProductEnum {
// 积分购买
CreditsProduct("AiDA credits purchase", 6L),
// 年度订阅
AnnualSubscription("AiDA Annual Subscription", 5000L),
// 月度订阅
MonthlySubscription("AiDA Monthly Subscription", 500L),
// 测试
DailySubscription("AiDA Daily Subscription", 5L),
;
/**
* 类型
*/
private final String name;
private final Long price;
}

View File

@@ -1,42 +0,0 @@
package com.ai.da.common.task;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.common.enums.PayTypeEnum;
import com.ai.da.service.AliPayService;
import com.ai.da.service.OrderInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@Component
public class AliPayTask {
@Resource
private OrderInfoService orderInfoService;
@Resource
private AliPayService aliPayService;
/**
* 从第0秒开始每隔30秒执行1次查询创建超过5分钟并且未支付的订单
*/
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm(){
// log.info("Alipay orderConfirm 被执行......");
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
aliPayService.checkOrderStatus(orderNo);
}
}
}

View File

@@ -1,21 +0,0 @@
package com.ai.da.common.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class GenerateTask {
// @Scheduled(cron = "0 0 */1 * * ?")
public void generateScheduled(){
log.info("测试定时器generate");
try{
}catch(Exception e){
}
}
}

View File

@@ -0,0 +1,121 @@
package com.ai.da.common.task;
import com.ai.da.common.enums.PayTypeEnum;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.service.*;
import com.paypal.http.exceptions.SerializeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.List;
@Slf4j
@Component
public class PaymentTask {
@Resource
private OrderInfoService orderInfoService;
@Resource
private StripeService stripeService;
@Resource
private AffiliateService affiliateService;
// 考虑删除该定时任务(原因:之后的订单列允许用户查看发票,发票未过期时仍可以支付,所以不需要手动使订单过期)
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirmForStripe() throws SerializeException {
// 查看超过30分钟以上仍未支付的订单 置为超时订单
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.STRIPE.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
stripeService.checkOrderStatus(orderNo);
}
}
@Resource
private PayPalCheckoutService payPalCheckoutService;
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirmForPaypal() throws SerializeException {
// log.info("PayPal orderConfirm 被执行......");
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.PAYPAL.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
payPalCheckoutService.checkOrderStatus(orderNo);
}
}
@Resource
private AliPayService aliPayService;
/**
* 从第0秒开始每隔30秒执行1次查询创建超过5分钟并且未支付的订单
*/
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirmForAlipay(){
/*
log.info("Alipay orderConfirm 被执行......");
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(5, PayTypeEnum.ALIPAY.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
aliPayService.checkOrderStatus(orderNo);
}*/
}
// 提前7天向用户发送提醒邮件,每天早上8点执行
@Scheduled(cron = "0 0 8 * * ?")
public void subscriptionReminder(){
stripeService.subscriptionReminder();
}
// 每天凌晨检查subscription中有哪些已过期更新状态
// @Scheduled(cron = "0 0 0 * * ?")
// public void checkSubscriptionExpiration(){
// stripeService.checkSubscriptionExpiration();
// }
// 如果有订阅已创建,但是没有发邮件通知的,需要主动获取回调信息并向用户发送邮件
public void checkSubscriptionPayment(){
//
}
@Scheduled(cron = "0 */5 * * * *") // Run every 5 minutes
public void updateAffiliateInfoWithPayment(){
affiliateService.updateAffiliateInfoWithPayment();
}
@Scheduled(cron = "0 0 8 28-31 * ?")
public void commissionSummaryReminder(){
// 每个月末的最后一天的早上八点执行
LocalDate today = LocalDate.now();
// 判断是否为月底
if (today.plusDays(1).getDayOfMonth() == 1) {
log.info("今天是月底,执行佣金结算提醒任务!");
affiliateService.commissionCalculation(null, null);
}
}
}

View File

@@ -1,42 +0,0 @@
package com.ai.da.common.task;
import com.ai.da.common.enums.PayTypeEnum;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.service.OrderInfoService;
import com.ai.da.service.PayPalCheckoutService;
import com.paypal.http.exceptions.SerializeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@Component
public class PaypalTask {
@Resource
private OrderInfoService orderInfoService;
@Resource
private PayPalCheckoutService payPalCheckoutService;
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm() throws SerializeException {
// log.info("PayPal orderConfirm 被执行......");
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.PAYPAL.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
payPalCheckoutService.checkOrderStatus(orderNo);
}
}
}

View File

@@ -1,40 +0,0 @@
package com.ai.da.common.task;
import com.ai.da.common.enums.PayTypeEnum;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.service.OrderInfoService;
import com.ai.da.service.StripeService;
import com.paypal.http.exceptions.SerializeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@Component
public class StripeTask {
@Resource
private OrderInfoService orderInfoService;
@Resource
private StripeService stripeService;
// @Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm() throws SerializeException {
// 查看超过30分钟以上仍未支付的订单 置为超时订单
List<OrderInfo> orderInfoList = orderInfoService.getNoPayOrderByDuration(30, PayTypeEnum.STRIPE.getType());
for (OrderInfo orderInfo : orderInfoList) {
String orderNo = orderInfo.getOrderNo();
log.warn("超时订单 ===> {}", orderNo);
//核实订单状态:调用支付宝查单接口
stripeService.checkOrderStatus(orderNo);
}
}
}

View File

@@ -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));
}
}

View File

@@ -278,4 +278,18 @@ public class RedisUtil {
// 设置过期时间为 5 分钟300 秒)
redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES);
}
public final static String PAYMENT_INFO_LAST_SCAN_TIME = "PaymentInfoLastScanTime";
public final static String AFFILIATE_LINK_VIEW_KEY = "AffiliateLink:view:";
public void increaseAffiliateLinkViewCount(Long accountId) {
String key = AFFILIATE_LINK_VIEW_KEY + accountId;
redisTemplate.opsForValue().increment(key);
}
public Long getAffiliateLinkViewCount(Long accountId) {
String key = AFFILIATE_LINK_VIEW_KEY + accountId;
return redisTemplate.opsForValue().increment(key, 0);
}
}

View File

@@ -2,8 +2,11 @@ package com.ai.da.common.utils;
import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.mapper.primary.entity.TrialOrder;
import com.ai.da.model.dto.AffiliateEmailParamsDTO;
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
import com.alibaba.fastjson.JSONObject;
import com.ai.da.common.config.exception.BusinessException;
import com.alibaba.fastjson2.JSON;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
@@ -175,7 +178,7 @@ public class SendEmailUtil {
subject = "Approval Confirmation for AiDA System Trial Access";
if (country.equals("China")) {
template.setTemplateID(NOTIFICATION_CHINESE_TEMPLATE_ID);
}else {
} else {
template.setTemplateID(NOTIFICATION_TEMPLATE_ID);
}
template.setTemplateData(buildNotificationData(trialOrder, link));
@@ -225,7 +228,7 @@ public class SendEmailUtil {
attachment.setFileName(fileName); // 设置附件文件名
// 设置附件内容
attachment.setContent(Base64.getEncoder().encodeToString(fileContent));
req.setAttachments(new Attachment[] {attachment});
req.setAttachments(new Attachment[]{attachment});
// 发送邮件
SendEmailResponse resp = client.SendEmail(req);
log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp));
@@ -268,7 +271,9 @@ public class SendEmailUtil {
throw new BusinessException("failed.to.send.mail");
}
}
private final static Long WILLBEEXPIRED_TEMPLATE_ID = 118178L;
public static void sendWillBeExpiredEmail(Account account, String senderAddress) {
try {
// 实例化一个认证对象
@@ -360,7 +365,7 @@ public class SendEmailUtil {
jsonObject.put("email", trialOrder.getEmail());
if (link) {
jsonObject.put("days", 14);
}else {
} else {
jsonObject.put("days", 5);
}
return jsonObject.toJSONString();
@@ -370,6 +375,7 @@ public class SendEmailUtil {
private final static Long UPGRADE_SUCCESS_NOTIFICATION_ID = 118856L;
private final static Long UPGRADE_NOTIFICATION_ID_CHINESE = 122898L;
private final static Long UPGRADE_SUCCESS_NOTIFICATION_ID_CHINESE = 122899L;
public static void sendUpgradeNotification(Account account, String senderAddress, Integer type) {
try {
// 实例化一个认证对象
@@ -418,7 +424,8 @@ public class SendEmailUtil {
}
private final static Long GENERATE_EXCEPTION_WARNING_ID = 122589L;
public static void sendGenerateExceptionWarning(String message){
public static void sendGenerateExceptionWarning(String message) {
try {
// 实例化一个认证对象
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
@@ -457,7 +464,8 @@ public class SendEmailUtil {
private final static Long QUESTIONNAIRE_FEEDBACK_EN_ID = 124151L;
private final static Long QUESTIONNAIRE_FEEDBACK_CN_ID = 124156L;
public static void questionnaireRelatedNotify(String userName, String email, String language){
public static void questionnaireRelatedNotify(String userName, String email, String language) {
try {
// 实例化一个认证对象
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
@@ -502,7 +510,7 @@ public class SendEmailUtil {
private final static Long RENEWAL_NOTIFICATION_FOR_OLD_USER_EN = 124892L;
private final static Long RENEWAL_NOTIFICATION_FOR_OLD_USER_CN = 124891L;
public static void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date){
public static void notificationForPaidUser(String receiverAddress, int emailType, String country, String userName, String date) {
try {
// 实例化一个认证对象
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
@@ -525,7 +533,7 @@ public class SendEmailUtil {
subject = "Welcome to AiDA!";
if (country.equals("China")) {
template.setTemplateID(NEW_USER_PAYMENT_NOTIFICATION_CN);
}else {
} else {
template.setTemplateID(NEW_USER_PAYMENT_NOTIFICATION_EN);
}
parameter.put("userName", userName);
@@ -536,7 +544,7 @@ public class SendEmailUtil {
subject = "Account renewal notification";
if (country.equals("China")) {
template.setTemplateID(RENEWAL_NOTIFICATION_FOR_OLD_USER_CN);
}else {
} else {
template.setTemplateID(RENEWAL_NOTIFICATION_FOR_OLD_USER_EN);
}
break;
@@ -597,8 +605,8 @@ public class SendEmailUtil {
private final static Long NEW_USER_REGISTER_NOTIFICATION_EN = 126919L;
public static void notificationForRegisterUser(String receiverAddress){
try{
public static void notificationForRegisterUser(String receiverAddress) {
try {
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
@@ -634,8 +642,8 @@ public class SendEmailUtil {
private final static Long CHANGE_MAILBOX_CONFIRM_CN = 128278L;
private final static Long CHANGE_MAILBOX_CONFIRM_EN = 128277L;
public static void changeMailboxConfirm(String receiverAddress, String language, String name, String link){
try{
public static void changeMailboxConfirm(String receiverAddress, String language, String name, String link) {
try {
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
@@ -653,10 +661,10 @@ public class SendEmailUtil {
req.setFromEmailAddress(SEND_ADDRESS);
req.setDestination(new String[]{receiverAddress});
Template template = new Template();
if (language.equals("ENGLISH")){
if (language.equals("ENGLISH")) {
req.setSubject("Change the email address bound to the AiDA account");
template.setTemplateID(CHANGE_MAILBOX_CONFIRM_EN);
}else {
} else {
req.setSubject("更换AiDA账号绑定的邮箱地址");
template.setTemplateID(CHANGE_MAILBOX_CONFIRM_CN);
}
@@ -678,12 +686,12 @@ public class SendEmailUtil {
private final static Long UPLOAD_TIMEOUT_REMINDER = 128324L;
public static void uploadTimeoutReminder(String userName, String time){
public static void uploadTimeoutReminder(String userName, String time) {
String xp = "xupei3360@163.com";
String shb = "shahaibodd99@gmail.com";
String wxd = "X1627315083@163.com";
String pkc = "kaicpang.pang@connect.polyu.hk";
try{
try {
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
@@ -721,6 +729,7 @@ public class SendEmailUtil {
private final static Long HALFPRICEPROMOTION_CN_ID = 128582L;
private final static Long HALFPRICEPROMOTION_EN_ID = 128583L;
public static void halfPricePromotion(Account account, String senderAddress, int type) {
try {
// 实例化一个认证对象
@@ -750,7 +759,7 @@ public class SendEmailUtil {
if (type == 1) {
subject = "AiDA workshop - Win a trip to Hong Kong";
template.setTemplateID(HALFPRICEPROMOTION_EN_ID);
}else {
} else {
subject = "AiDA workshop - 赢取香港之旅";
template.setTemplateID(HALFPRICEPROMOTION_CN_ID);
}
@@ -808,4 +817,171 @@ public class SendEmailUtil {
throw new BusinessException("failed.to.send.mail");
}
}
private final static Long CANCEL_MERCHANT_EN = 130720L;
private final static Long NEW_MERCHANT_EN = 130721L;
private final static Long NEW_USER_EN = 130722L;
private final static Long NEW_USER_CN = 130723L;
private final static Long RENEWAL_MERCHANT_EN = 130724L;
private final static Long RENEWAL_USER_EN = 130725L;
private final static Long RENEWAL_USER_CN = 130726L;
private final static Long RENEWAL_REMINDER_USER_EN = 130727L;
private final static Long RENEWAL_REMINDER_USER_CN = 130728L;
private final static Long PAYMENT_FAILED_NEW_MERCHANT_EN = 131230L;
private final static Long PAYMENT_FAILED_RENEWAL_MERCHANT_EN = 131225L;
private final static Long PAYMENT_FAILED_RENEWAL_USER_EN = 131563L;
private final static Long PAYMENT_FAILED_RENEWAL_USER_CN = 131564L;
public static void subscriptionEmailReminder(String type, SubscriptionEmailParamsDTO subscriptionEmailParamsDTO, String language, String receiverAddress) {
try {
// String merchantEmail = "kimwong@code-create.com.hk";
String merchantEmail = "xupei3360@163.com";
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ses.tencentcloudapi.com");
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
SesClient client = new SesClient(cred, "ap-hongkong", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SendEmailRequest user = new SendEmailRequest();
user.setFromEmailAddress(SEND_ADDRESS);
user.setDestination(new String[]{receiverAddress});
SendEmailRequest merchant = new SendEmailRequest();
merchant.setFromEmailAddress(SEND_ADDRESS);
merchant.setDestination(new String[]{merchantEmail});
Template templateUser = new Template();
Template templateMerchant = new Template();
switch (type) {
case "cancel":
merchant.setSubject("[Code-Create] Subscription Cancelled");
templateMerchant.setTemplateID(CANCEL_MERCHANT_EN);
break;
case "fail_new":
merchant.setSubject("[Code-Create] Payment Failed : New Order (" + subscriptionEmailParamsDTO.getOrderId() + ")");
templateMerchant.setTemplateID(PAYMENT_FAILED_NEW_MERCHANT_EN);
break;
case "fail_renewal":
merchant.setSubject("[Code-Create] Payment Failed : Renewal Order (" + subscriptionEmailParamsDTO.getOrderId() + ")");
templateMerchant.setTemplateID(PAYMENT_FAILED_RENEWAL_MERCHANT_EN);
// todo to user
if (language.equals("ENGLISH")) {
user.setSubject("[Code-Create] Payment Failed : Renewal Order (" + subscriptionEmailParamsDTO.getOrderId() + ")");
templateUser.setTemplateID(PAYMENT_FAILED_RENEWAL_USER_EN);
} else {
user.setSubject("[Code-Create] 自动续费失败 (" + subscriptionEmailParamsDTO.getOrderId() + ")");
templateUser.setTemplateID(PAYMENT_FAILED_RENEWAL_USER_CN);
}
break;
case "new":
merchant.setSubject("[Code-Create] New Order(" + subscriptionEmailParamsDTO.getOrderId() + ")");
templateMerchant.setTemplateID(NEW_MERCHANT_EN);
if (language.equals("ENGLISH")) {
user.setSubject("[Code-Create] You have successfully subscribed to AiDA");
templateUser.setTemplateID(NEW_USER_EN);
} else {
user.setSubject("[Code-Create] 您已成功订阅AiDA");
templateUser.setTemplateID(NEW_USER_CN);
}
break;
case "renewal":
merchant.setSubject("[Code-Create] New subscription renewal order (" + subscriptionEmailParamsDTO.getOrderId() + ")");
templateMerchant.setTemplateID(RENEWAL_MERCHANT_EN);
if (language.equals("ENGLISH")) {
user.setSubject("[Code-Create] AiDA Renewal Successful");
templateUser.setTemplateID(RENEWAL_USER_EN);
} else {
user.setSubject("[Code-Create] AiDA续订成功");
templateUser.setTemplateID(RENEWAL_USER_CN);
}
break;
case "reminder":
if (language.equals("ENGLISH")) {
user.setSubject("[Code-Create] AiDA Subscription Renewal Reminder");
templateUser.setTemplateID(RENEWAL_REMINDER_USER_EN);
} else {
user.setSubject("[Code-Create] AiDA续订提醒");
templateUser.setTemplateID(RENEWAL_REMINDER_USER_CN);
}
break;
default:
log.error("unknown subscription email type");
// throw new BusinessException("unknown subscription email type");
}
templateUser.setTemplateData(JSON.toJSONString(subscriptionEmailParamsDTO));
user.setTemplate(templateUser);
templateMerchant.setTemplateData(JSON.toJSONString(subscriptionEmailParamsDTO));
merchant.setTemplate(templateMerchant);
if (!type.equals("cancel") && !type.equals("fail_new")) {
// 返回的resp是一个SendEmailResponse的实例与请求对象对应
SendEmailResponse respUser = client.SendEmail(user);
log.info("邮件主题:{}发送结果toUser###{}", user.getSubject(), SendEmailResponse.toJsonString(respUser));
}
if (!type.equals("reminder")) {
SendEmailResponse respMerchant = client.SendEmail(merchant);
log.info("邮件主题:{}发送结果toMerchant###{}", merchant.getSubject(), SendEmailResponse.toJsonString(respMerchant));
}
} catch (TencentCloudSDKException e) {
log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail");
}
}
private final static Long NEW_REGISTRATION = 132123L;
private final static Long AFFILIATE_ACCEPTED = 132124L;
private final static Long AFFILIATE_REFUSED = 132125L;
private final static Long AFFILIATE_MONTHLY_SUMMARY = 132126L;
public static void affiliateEmailReminder(String receiverAddress, AffiliateEmailParamsDTO paramsDTO, String type) {
try {
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ses.tencentcloudapi.com");
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
SesClient client = new SesClient(cred, "ap-hongkong", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SendEmailRequest req = new SendEmailRequest();
req.setFromEmailAddress(SEND_ADDRESS);
req.setDestination(new String[]{receiverAddress});
Template template = new Template();
switch (type) {
case "new":
req.setSubject("New Affiliate Registration");
template.setTemplateID(NEW_REGISTRATION);
break;
case "accepted":
req.setSubject("Affiliate Application Accepted");
template.setTemplateID(AFFILIATE_ACCEPTED);
break;
case "refused":
req.setSubject("Affiliate Application Refused");
template.setTemplateID(AFFILIATE_REFUSED);
break;
case "summary":
req.setSubject("Your Monthly AffiliateWP Summary for AiDA");
template.setTemplateID(AFFILIATE_MONTHLY_SUMMARY);
break;
}
template.setTemplateData(JSON.toJSONString(paramsDTO));
req.setTemplate(template);
// 返回的resp是一个SendEmailResponse的实例与请求对象对应
SendEmailResponse resp = client.SendEmail(req);
log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp));
} catch (TencentCloudSDKException e) {
log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail");
}
}
}

View File

@@ -0,0 +1,86 @@
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
private AffiliateService affiliateService;
@ApiOperation(value = "注册成为affiliate")
@GetMapping("/registration")
public Response<Boolean> completeGuidance(@RequestParam(value = "promotionMethod", required = false) String promotionMethod) {
return Response.success(affiliateService.registerAsAnAffiliate(promotionMethod));
}
@ApiOperation(value = "获取affiliate列表")
@PostMapping("/list")
public Response<IPage<Affiliate>> getAffiliateList(@Valid @RequestBody AffiliateQueryDTO affiliateQueryDTO) {
return Response.success(affiliateService.getAffiliateList(affiliateQueryDTO));
}
@ApiOperation(value = "获取affiliate个人中心")
@GetMapping("/personalCenter")
public Response<AffiliateVO> personalAffiliateCenter() {
return Response.success(affiliateService.personalAffiliateCenter());
}
@ApiOperation(value = "获取个人佣金图表数据")
@GetMapping("/getPersonalMonthlyIncome")
public Response<double[]> getPersonalMonthlyIncome(@RequestParam("year")int year) {
return Response.success(affiliateService.getPersonalMonthlyIncome(year));
}
@ApiOperation(value = "审批affiliate申请")
@GetMapping("/approval")
public Response<Boolean> applicationApproval(@RequestParam("id") Long id, @RequestParam("isApproved")Boolean isApproved) {
return Response.success(affiliateService.applicationApproval(id, isApproved));
}
/*@ApiOperation(value = "定时计算佣金")
@GetMapping("/testTask")
public Response<String> testTask() {
affiliateService.updateAffiliateInfoWithPayment();
return Response.success("success ");
}*/
/*@ApiOperation(value = "每月发送结算邮件")
@GetMapping("/commissionCalculation")
public Response<String> commissionCalculation() {
affiliateService.commissionCalculation(null, null);
return Response.success("success ");
}*/
@ApiOperation(value = "affiliate链接浏览量增加")
@GetMapping("/viewsIncrease")
public Response<Boolean> viewsGet(@RequestParam("id") Long id) {
return Response.success(affiliateService.affiliateLinkViewsIncrease(id));
}
@ApiOperation(value = "获取每个affiliate产生的收入")
@PostMapping("/getEachAffiliateGeneratedRevenue")
public Response<IPage<AffiliateInvitationDetailsVO>> getEachAffiliateGeneratedRevenue(@RequestBody AffiliateQueryDTO affiliateQueryDTO) {
return Response.success(affiliateService.getEachAffiliateGeneratedRevenue(affiliateQueryDTO));
}
}

View File

@@ -66,6 +66,8 @@ public class ElementController {
return Response.success();
}
/** 该功能已删除 */
@Deprecated
@ApiOperation(value = "生成印花")
@PostMapping("/generatePrint")
public Response<GenerateCollectionItemVO> generatePrint(@Valid @RequestBody CollectionGeneratePrintDTO generatePrintDTO) {

View File

@@ -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<PageBaseResponse<OrderInfo>> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){
PageBaseResponse<OrderInfo> orderByAccountId = orderInfoService.getOrderByPage(queryPageByTimeDTO);
public Response<PageBaseResponse<OrderListVO>> list(@Valid @RequestBody QueryPageByTimeDTO queryPageByTimeDTO){
PageBaseResponse<OrderListVO> orderByAccountId = paymentInfoService.getPaymentInfo(queryPageByTimeDTO);
// List<OrderInfo> list = orderInfoService.listOrderByCreateTimeDesc();
return Response.success(orderByAccountId);
}

View File

@@ -1,47 +1,55 @@
package com.ai.da.controller;
import com.ai.da.common.response.Response;
import com.ai.da.model.dto.ProductPurchaseDTO;
import com.ai.da.service.StripeService;
import com.paypal.http.HttpResponse;
import com.paypal.payments.Refund;
import com.stripe.exception.StripeException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Api(tags = "Stripe模块")
@Slf4j
@RestController
@RequestMapping("/api/stripe")
@ApiIgnore
public class StripeController {
@Resource
private StripeService stripeService;
@ApiOperation("创建支付链接")
@PostMapping("/createOrder/{amount}")
public Response<String> pay(@PathVariable Integer amount, @RequestParam String returnUrl) {
return Response.success(stripeService.pay(amount, returnUrl));
@PostMapping("/createOrder")
public Response<String> pay(@Valid @RequestBody ProductPurchaseDTO productPurchaseDTO) {
return Response.success(stripeService.pay(productPurchaseDTO));
}
@ApiOperation("支付通知")
@PostMapping("/trade/notify")
public Response<String> callback(HttpServletRequest request) throws ServletException, IOException {
public void callback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Boolean result = stripeService.notify(request);
if (result){
return Response.success(200,"success");
response.setStatus(HttpServletResponse.SC_OK);
}else {
return Response.fail(400,"failure");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
@ApiOperation("申请退款")
@PostMapping("/trade/refund/{orderNo}/{reason}")
@GetMapping("/trade/refund/{orderNo}/{reason}")
public Response<HttpResponse<Refund>> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException {
String response = stripeService.refund(null,orderNo,reason);
if (response.equals("退款成功")){
@@ -51,4 +59,57 @@ public class StripeController {
}
}
@ApiOperation("获取订阅")
@GetMapping("/getSubscription")
public Response<List<String>> getSubscription(@RequestParam String name, @RequestParam String email) {
try {
return Response.success(stripeService.getSubscriptionIds(name, email));
} catch (StripeException e) {
throw new RuntimeException(e);
}
}
@ApiOperation("取消订阅")
@GetMapping("/cancelSubscription")
public Response<String> cancelSubscription(@RequestParam String subscriptionId, @RequestParam(required = false) String reason) {
stripeService.cancelSubscription(subscriptionId, reason);
return Response.success("success");
}
@ApiOperation("临时 取消订阅")
@GetMapping("/cancelSubscriptionTemp")
public Response<String> cancelSubscriptionTemp(@RequestParam String subscriptionId) {
stripeService.cancelSubscriptionTemp(subscriptionId);
return Response.success("success");
}
@ApiOperation("创建订阅 临时")
@GetMapping("/createSubscriptionTemp")
public Response<String> createSubscriptionTemp(@RequestParam String name, @RequestParam String email) {
return Response.success(stripeService.createSubscriptionTemp(name, email));
}
@ApiOperation("修改用户默认支付方式 临时")
@GetMapping("/changeCustomerPayment")
public Response<String> changeCustomerPayment(@RequestParam String name, @RequestParam String email) {
return Response.success(stripeService.changeCustomerPayment(name, email));
}
@ApiOperation("临时 发送续订失败邮件")
@GetMapping("/sendRenewalFailEmail")
public Response<Boolean> sendRenewalFailEmail(@RequestParam String invoiceId, @RequestParam String subscriptionId, @RequestParam String orderNo) {
return Response.success(stripeService.sendRenewalFailEmail(invoiceId, subscriptionId,orderNo));
}
@ApiOperation("临时 查询指定用户绑定的付款方式")
@GetMapping("/getCustomerPaymentMethod")
public Response<List<Map<String,String>>> getCustomerPaymentMethod(@RequestParam String name, @RequestParam String email) {
return Response.success(stripeService.getCustomerPaymentMethod(name, email));
}
@ApiOperation("临时 解绑指定用户绑定的所有付款方式")
@GetMapping("/detachCustomerAllPaymentMethod")
public Response<String> detachCustomerAllPaymentMethod(@RequestParam String name, @RequestParam String email) {
return Response.success(stripeService.detachCustomerAllPaymentMethod(name, email));
}
}

View File

@@ -0,0 +1,14 @@
package com.ai.da.mapper.primary;
import com.ai.da.mapper.primary.entity.AffiliateIncome;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import java.util.Map;
public interface AffiliateIncomeMapper extends BaseMapper<AffiliateIncome> {
List<Map<String, Object>> getPersonalMonthlyIncome(Long affiliateAccountId, int year);
List<Map<String, Object>> getMonthlyAffiliateIncome(int year, int month);
}

View File

@@ -0,0 +1,11 @@
package com.ai.da.mapper.primary;
import com.ai.da.mapper.primary.entity.Affiliate;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.Map;
public interface AffiliateMapper extends BaseMapper<Affiliate> {
Map<String, Long> getMonthlyApprovedAffiliate(int year, int month);
}

View File

@@ -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<PaymentInfo> {
List<OrderListVO> selectPageOrderList(Long accountId, String startTime, String endTime, int offset, int pageSize);
int queryOrderListTotalCount(Long accountId, String startTime, String endTime);
}

View File

@@ -0,0 +1,8 @@
package com.ai.da.mapper.primary;
import com.ai.da.mapper.primary.entity.SubscriptionInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SubscriptionInfoMapper extends BaseMapper<SubscriptionInfo> {
}

View File

@@ -0,0 +1,30 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("t_affiliate")
public class Affiliate extends BaseEntity{
private Long accountId;
// Active活跃 || Inactive过期 || Pending待审批 || Refused(拒绝)
private String status;
private Float totalEarnings = 0.00F;
private Float monthlyEarnings = 0.00F;
private Float unpaidEarnings = 0.00F;
private Integer visits = 0;
private Boolean approved = false;
private String link;
private String promotionMethod;
}

View File

@@ -0,0 +1,26 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("t_affiliate_income")
public class AffiliateIncome extends BaseEntity {
private Long affiliateId;
private Long affiliateAccountId;
private Long inviteeAccountId;
private Float amount;
private LocalDateTime paymentTime;
private Float commission;
}

View File

@@ -21,5 +21,12 @@ public class OrderInfo extends BaseEntity{
private String orderStatus;//订单状态
private String note;
private String paymentType;//支付方式
// 可用于标记用户订单是否首次订阅
private byte isFirstSubscription = 0;
private byte isCommissionCalculated = 0;
}

View File

@@ -2,7 +2,9 @@ package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("t_payment_info")
public class PaymentInfo extends BaseEntity{
@@ -13,11 +15,22 @@ public class PaymentInfo extends BaseEntity{
private String paymentType;//支付类型
private String tradeType;//交易类型
private String tradeState;//交易状态
private Float payerTotal;//支付金额(元)
private String content;//通知参数
// 支付类型 new || renewal
private String type;
// 当前支付是否已邮件通知 0 || 1
private Integer notified;
private String paymentMethod;
private String last4;
// 发票托管页面
private String hostedInvoiceUrl;
}

View File

@@ -0,0 +1,39 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("t_subscription_info")
public class SubscriptionInfo extends BaseEntity{
private Long accountId;
private String orderNo;
// stripe || paypal 平台生成的id
private String subscriptionId;
// month || year
private String type;
// active || expired
private String status = "active";
private byte cancelNotified = (byte)0;
// 续订的下一个付款日
private String nextPayDate;
// 当前订阅订单有效期开始时间
private Long currentPeriodStart;
// 当前订阅订单有效期结束时间
private Long currentPeriodEnd;
// 取消订阅原因
private String cancelReason;
}

View File

@@ -0,0 +1,31 @@
package com.ai.da.model.dto;
import lombok.Data;
@Data
public class AffiliateEmailParamsDTO {
private String username;
private String promotionMethod;
private String totalProgramRevenue;
private String newApprovedAffiliates;
private String unpaidEarnings;
private String paidEarnings;
public AffiliateEmailParamsDTO() {
}
public AffiliateEmailParamsDTO(String username) {
this.username = username;
}
public AffiliateEmailParamsDTO(String username, String promotionMethod) {
this.username = username;
this.promotionMethod = promotionMethod;
}
}

View File

@@ -0,0 +1,31 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel("查询affiliate列表")
public class AffiliateQueryDTO extends TimeQueryBaseDTO{
@ApiModelProperty("Active活跃 || Inactive过期 || Pending待审批 || Refused(拒绝)")
private String status;
@ApiModelProperty("推广者id")
private Long affiliateId;
@ApiModelProperty("按时间 DESC 降序 || ASC 升序")
private String order = "ASC";
@Override
public String toString() {
return "AffiliateQueryDTO{" +
"status='" + status + '\'' + ' ' +
"startTime='" + super.getStartTime() + '\'' + ' ' +
"endTime='" + super.getEndTime() + '\'' + ' ' +
"page='" + super.getPage() + '\'' + ' ' +
"size='" + super.getSize() + '\'' + ' ' +
'}';
}
}

View File

@@ -12,49 +12,50 @@ import javax.validation.constraints.NotNull;
public class GenerateThroughImageTextDTO {
@NotNull(message = "userId cannot be empty")
@ApiModelProperty("用户id")
Long userId;
private Long userId;
@ApiModelProperty("caption | prompt")
String text;
private String text;
@ApiModelProperty("图片在t_collection_element表中的id")
Long collectionElementId;
private Long collectionElementId;
// todo 后续取消这个字段的传输,由后端自行判断相关参数是否有值
// @NotBlank(message = "you have to choose the generate type")
@ApiModelProperty("text image text-image")
String generateType;
private String generateType;
@ApiModelProperty("图片来源update从library中选择,从toProductImage结果中选择 collection || library || productImage")
String designType;
private String designType;
@NotBlank(message = "level1Type cannot be empty!")
@ApiModelProperty("Moodboard Printboard Sketchboard MarketingSketch")
String level1Type;
private String level1Type;
@ApiModelProperty("Outwear Dress Blouse Skirt Trousers || Logo Slogan Pattern")
String level2Type;
private String level2Type;
@ApiModelProperty("性别")
String gender;
private String gender;
@ApiModelProperty("选择的模型名")
String version;
@ApiModelProperty("选择的模型名 high || fast")
private String version;
@NotBlank(message = "timeZone cannot be empty!")
@ApiModelProperty("本地时区,比如 'Asia/Tokyo' 东京时间 , 'Asia/Shanghai' 北京时间 由js本地获取")
String timeZone;
private String timeZone;
@ApiModelProperty("唯一id用于保持消息唯一性")
String uniqueId;
private String uniqueId;
@NotNull(message = "Please check if the required fields are empty.(isTestUser)")
@ApiModelProperty("是否是测试用户")
Boolean isTestUser;
private Boolean isTestUser;
@ApiModelProperty("页面上用户设计的slogan所截的图片")
String sloganBase64;
private String sloganBase64;
@ApiModelProperty("种子 取值范围 0~500")
String seed;
private String seed;
}

View File

@@ -0,0 +1,30 @@
package com.ai.da.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel("购买产品DTO")
public class ProductPurchaseDTO {
@ApiModelProperty("购买数量")
private int quantity;
// http://example.com
@NotBlank(message = "return url cannot be empty")
@ApiModelProperty("购买完成后返回页面地址")
private String returnUrl;
@NotBlank(message = "product name cannot be empty")
@ApiModelProperty("产品名 CreditsPurchase || Subscription")
private String productName;
@ApiModelProperty("Month || Year")
private String subscribeType;
@ApiModelProperty("是否自动续订 one_time || recurring")
private Boolean autoRenewal;
}

View File

@@ -0,0 +1,54 @@
package com.ai.da.model.dto;
import lombok.Data;
@Data
public class SubscriptionEmailParamsDTO {
// 用户名
private String username;
// t_payment_info id每次支付对于用户来说是一笔新订单
private String orderId;
// 订单支付创建日期
private String createDate;
// 购买数量
private String quantity;
// 费用
private String totalFee;
// 当前订阅开始时间
private String lastOrderDate;
// 当前订阅结束时间
private String endOfPrepaidTerm;
// 付款方式
private String paymentMethod;
private String last4;
// 订阅Id
private String subscriptionId;
// 订阅方式
private String subscriptionType;
// 订阅开始时间
private String startDate;
// 下一个支付日期
private String nextPayDate;
// 下次付款时间reminder
private String renewalTime;
// 付款失败原因
private String failMessage;
private String accountPageRef;
}

View File

@@ -0,0 +1,19 @@
package com.ai.da.model.dto;
import com.ai.da.model.vo.PageQueryBaseVo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel("按时间查询")
public class TimeQueryBaseDTO extends PageQueryBaseVo {
@ApiModelProperty("按时间区间查询 区间起点")
private String startTime;
@ApiModelProperty("按时间区间查询 区间终点")
private String endTime;
}

View File

@@ -46,4 +46,22 @@ public class AccountLoginVO {
private String Language;
// 订阅id(stripe提供)
private String subscriptionId;
// 订阅状态
private String status;
// 订阅过期时间
private String expireTime;
// 订阅类型 month || year
private String subscriptionType;
// 是否自动续订
private boolean isAutoRenewal;
// 是否是affiliate
private boolean isAffiliate = false;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,15 @@
package com.ai.da.model.vo;
import com.ai.da.mapper.primary.entity.Affiliate;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AffiliateVO extends Affiliate {
private Long linkViewCount;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,31 @@
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;
public interface AffiliateService extends IService<Affiliate> {
Boolean registerAsAnAffiliate(String promotionMethod);
IPage<Affiliate> getAffiliateList(AffiliateQueryDTO affiliateQueryDTO);
AffiliateVO personalAffiliateCenter();
double[] getPersonalMonthlyIncome(int year);
Boolean applicationApproval(Long id, Boolean isApproved);
void updateAffiliateInfoWithPayment();
Boolean affiliateLinkViewsIncrease(Long id);
IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO);
Affiliate getByAccountId(Long accountId);
void commissionCalculation(Integer year, Integer month);
}

View File

@@ -17,9 +17,9 @@ public interface CreditsService extends IService<CreditsDetail> {
String getCredits(Long accountId);
void creditsRefund(Long accountId, Integer quantity);
void creditsRefund(Long accountId, Integer quantity, String orderNo);
void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType);
void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType, String orderNo);
PageBaseResponse<CreditsDetail> queryCreditsDetailsPage(QueryIncomeOrExpenditureDTO queryPageByTimeDTO);

View File

@@ -2,6 +2,7 @@ package com.ai.da.service;
import com.ai.da.common.enums.OrderStatusEnum;
import com.ai.da.common.enums.ProductEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.model.dto.QueryPageByTimeDTO;
@@ -13,6 +14,8 @@ public interface OrderInfoService extends IService<OrderInfo> {
OrderInfo createOrderByProductId(Integer productId, String paymentType);
OrderInfo createOrderByProductId(Integer amount, String paymentType, ProductEnum product);
void saveCodeUrl(String orderNo, String codeUrl);
List<OrderInfo> listOrderByCreateTimeDesc();
@@ -28,4 +31,7 @@ public interface OrderInfoService extends IService<OrderInfo> {
PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO);
void updateOrderNoById(Long id, String orderNo);
void updateTotalFeeByOrderNo(String orderNo);
}

View File

@@ -1,13 +1,19 @@
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.checkout.Session;
import com.stripe.model.Charge;
import com.stripe.model.Invoice;
import java.util.List;
import java.util.Map;
public interface PaymentInfoService {
public interface PaymentInfoService extends IService<PaymentInfo> {
void createPaymentInfo(String plainText);
@@ -17,9 +23,13 @@ public interface PaymentInfoService {
void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO);
void createPaymentInfoForStripe(Session session);
PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice);
PaymentInfo getPaymentInfoByOrderId(String orderId);
PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge);
List<PaymentInfo> getPaymentInfoByOrderNo(String orderId, String order);
void updatePaymentStatusById(Long id, String status, String content);
PageBaseResponse<OrderListVO> getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO);
}

View File

@@ -1,15 +1,50 @@
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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
public interface StripeService {
String pay(Integer quantity, String returnUrl);
String pay(ProductPurchaseDTO productPurchaseDTO);
Boolean notify(HttpServletRequest request);
SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId);
String refund(String amount, String orderId, String reason);
void checkOrderStatus(String orderNo);
List<String> getSubscriptionIds(String name, String userEmail) throws StripeException;
Map<String, String> getPaymentMethodByInvoiceId(String invoiceId);
void cancelSubscription(String orderNo, String cancelReason);
void cancelSubscriptionTemp(String subscriptionId);
Map<String, String> getPaymentMethod(String paymentMethodId);
/*void updateSubscription(String subscriptionId);
void resume(String subscriptionId);*/
void subscriptionReminder();
void checkSubscriptionExpiration();
String createSubscriptionTemp(String name, String email);
String changeCustomerPayment(String name, String email);
boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo);
List<Map<String,String>> getCustomerPaymentMethod(String name, String email);
String detachCustomerAllPaymentMethod(String name, String email);
}

View File

@@ -104,6 +104,12 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Resource
private RedisUtil redisUtil;
@Resource
private StripeService stripeService;
@Resource
private AffiliateService affiliateService;
@Override
@Transactional(rollbackFor = Exception.class)
public AccountPreLoginVO preLogin(AccountPreLoginDTO accountDTO) {
@@ -2418,6 +2424,19 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
response.setAccountExtendList(accountExtends);
}
response.setLanguage(Language.valueOf(account.getLanguage()).name());
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"));
}
Affiliate affiliate = affiliateService.getByAccountId(accountId);
if (!Objects.isNull(affiliate) && affiliate.getStatus().equals("Active")) {
response.setAffiliate(true);
}
return response;
}

View File

@@ -0,0 +1,295 @@
package com.ai.da.service.impl;
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.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.*;
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;
import com.ai.da.service.AffiliateService;
import com.ai.da.service.OrderInfoService;
import com.ai.da.service.PaymentInfoService;
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;
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.function.Function;
@Service
@Slf4j
public class AffiliateServiceImpl extends ServiceImpl<AffiliateMapper, Affiliate> implements AffiliateService {
@Resource
private OrderInfoService orderInfoService;
@Resource
private AccountService accountService;
@Resource
private PaymentInfoService paymentInfoService;
@Resource
private SubscriptionInfoMapper subscriptionInfoMapper;
@Resource
private AffiliateIncomeMapper affiliateIncomeMapper;
@Resource
private RedisUtil redisUtil;
// 推广者注册
public Boolean registerAsAnAffiliate(String promotionMethod){
AuthPrincipalVo userHolder = UserContext.getUserHolder();
// 判断该用户是否已注册
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
qw.eq("account_id", userHolder.getId());
Affiliate affiliate = baseMapper.selectOne(qw);
if (Objects.isNull(affiliate)){
affiliate = new Affiliate();
affiliate.setAccountId(userHolder.getId());
affiliate.setStatus("Pending");
affiliate.setCreateTime(LocalDateTime.now());
affiliate.setPromotionMethod(promotionMethod);
baseMapper.insert(affiliate);
// 邮件通知审批者
// String email = "kimwong@code-create.com.hk";
String email = "xupei3360@163.com";
SendEmailUtil.affiliateEmailReminder(email, new AffiliateEmailParamsDTO(userHolder.getUsername(), promotionMethod), "new");
}else {
throw new BusinessException("You have registered an Affiliate", ResultEnum.PROMPT.getCode());
}
return true;
}
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())
.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);
}
public AffiliateVO personalAffiliateCenter(){
QueryWrapper<Affiliate> qw = new QueryWrapper<>();
Long accountId = UserContext.getUserHolder().getId();
qw.eq("account_id", accountId);
Affiliate affiliate = baseMapper.selectOne(qw);
AffiliateVO affiliateVO = CopyUtil.copyObject(affiliate, AffiliateVO.class);
affiliateVO.setLinkViewCount(getAffiliateLinkViewCount(affiliate.getId()));
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);
// 1、更新db状态
if (isApproved){
// 更新状态
affiliate.setStatus("Active");
affiliate.setApproved(true);
affiliate.setLink(CommonConstant.AFFILIATE_LINK + affiliate.getId());
} else {
affiliate.setStatus("Refused");
affiliate.setApproved(false);
}
affiliate.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(affiliate);
// 2、将批准结果邮件通知用户
Account account = accountService.getById(affiliate.getAccountId());
String userEmail = account.getUserEmail();
String userName = account.getUserName();
if (isApproved){
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "accepted");
}else {
SendEmailUtil.affiliateEmailReminder(userEmail, new AffiliateEmailParamsDTO(userName), "refused");
}
return true;
}
// 定时计算佣金
public void updateAffiliateInfoWithPayment(){
// 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);
}
queryWrapper.eq("type","new").eq("trade_state", "paid");
List<PaymentInfo> paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper);
if (!paymentInfos.isEmpty()){
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<>();
qwOrderInfo.eq("account_id", accountId).eq("is_first_subscription", 1);
List<OrderInfo> orderInfos = orderInfoService.getBaseMapper().selectList(qwOrderInfo);
// 该用户首次订阅(非首次订阅,不分配佣金)
if (orderInfos.isEmpty()){
// 查询是否绑定affiliateId
Account account = accountService.getById(accountId);
if (!Objects.isNull(account.getInvitationCode())){
// 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"));
BigDecimal monthlyEarning = BigDecimal.valueOf(affiliate.getMonthlyEarnings()).add(commission);
BigDecimal unpaidEarnings = BigDecimal.valueOf(affiliate.getUnpaidEarnings()).add(commission);
int visits = affiliate.getVisits() + 1;
affiliate.setMonthlyEarnings(monthlyEarning.floatValue());
affiliate.setUnpaidEarnings(unpaidEarnings.floatValue());
affiliate.setVisits(visits);
affiliate.setUpdateTime(LocalDateTime.now());
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);
orderInfo.setUpdateTime(LocalDateTime.now());
orderInfoService.updateById(orderInfo);
}
});
}
redisUtil.addToString(RedisUtil.PAYMENT_INFO_LAST_SCAN_TIME, currentTime);
}
public Boolean affiliateLinkViewsIncrease(Long affiliateId) {
redisUtil.increaseAffiliateLinkViewCount(affiliateId);
return Boolean.TRUE;
}
private Long getAffiliateLinkViewCount(Long affiliateId) {
return redisUtil.getAffiliateLinkViewCount(affiliateId);
}
// 查看每个affiliate带来收入的详情
@Override
public IPage<AffiliateInvitationDetailsVO> getEachAffiliateGeneratedRevenue(AffiliateQueryDTO affiliateQueryDTO) {
if (Objects.isNull(affiliateQueryDTO.getAffiliateId())){
throw new BusinessException("Please specify the affiliate ID.", ResultEnum.PROMPT.getCode());
}
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;
});
}
public void commissionCalculation(Integer year, Integer month) {
if (Objects.isNull(year)) {
year = LocalDateTime.now().getYear();
}
if (Objects.isNull(month)) {
month = LocalDateTime.now().getMonthValue();
}
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");
}
// 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

@@ -217,7 +217,8 @@ public class AliPayServiceImpl implements AliPayService {
creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
CreditsEventsEnum.BUY_CREDITS.getName() + "--Alipay",
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
"positive");
"positive",
orderByOrderNo.getOrderNo());
} finally {
//要主动释放锁
lock.unlock();
@@ -320,7 +321,8 @@ public class AliPayServiceImpl implements AliPayService {
creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
CreditsEventsEnum.BUY_CREDITS.getName() + "--Alipay",
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
"positive");
"positive",
orderByOrderNo.getOrderNo());
}
}
@@ -393,7 +395,7 @@ public class AliPayServiceImpl implements AliPayService {
// 更新积分状态
OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
// creditsService.creditsRefund(orderByOrderNo.getAccountId(), orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue()));
creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())));
creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())), orderNo);
} else {
log.info("调用失败,返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());

View File

@@ -248,7 +248,8 @@ public class AlipayHKServiceImpl implements AlipayHKService {
creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
CreditsEventsEnum.BUY_CREDITS.getName() + "--AlipayHK",
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
"positive");
"positive",
orderByOrderNo.getOrderNo());
log.info("用户:{} 积分信息更新成功",orderByOrderNo.getAccountId());
} finally {
//要主动释放锁

View File

@@ -216,6 +216,8 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
collectionElementMapper.deleteBatchIds(ids);
}
/** 该方法已不再使用 */
@Deprecated
@Override
public GenerateCollectionItemVO generatePrint(CollectionGeneratePrintDTO generatePrintDTO) {
Long userId = UserContext.getUserHolder().getId();
@@ -914,7 +916,7 @@ public class CollectionElementServiceImpl extends ServiceImpl<CollectionElementM
generate.setAccountId(userId);
generate.setLevel1Type(CollectionLevel1TypeEnum.PRINT_BOARD.getRealName());
generate.setGenerateType("synthesis");
generate.setModelName("Image Synthesis Model");
// generate.setModelName("Image Synthesis Model");
generate.setCreateDate(DateUtil.getByTimeZone(timeZone));
return generate;
}

View File

@@ -100,7 +100,7 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
return account.getCredits().toString();
}
public void creditsRefund(Long accountId, Integer quantity) {
public void creditsRefund(Long accountId, Integer quantity, String orderNo) {
BigDecimal existingCredits = accountMapper.selectById(accountId).getCredits();
BigDecimal newCredits = new BigDecimal(CreditsEventsEnum.BUY_CREDITS.getValue()).multiply(new BigDecimal(quantity));
BigDecimal subtracted = existingCredits.subtract(newCredits);
@@ -111,7 +111,7 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
insertToCreditsDetail(accountId,
CreditsEventsEnum.REFUND.getName() + "--Stripe",
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
"negative");
"negative", orderNo);
}
/**
@@ -122,7 +122,7 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
* @param changeType 变更类型 positive->增 negative->减
*/
@Override
public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType) {
public void insertToCreditsDetail(Long accountId, String changeEvent, String credits, String changeType, String orderNo) {
CreditsDetail creditsDetail = new CreditsDetail();
Account account = accountMapper.selectById(accountId);
// BigDecimal finalCredits;
@@ -137,6 +137,7 @@ public class CreditsServiceImpl extends ServiceImpl<CreditsDetailMapper, Credits
creditsDetail.setAccountId(accountId);
creditsDetail.setChangeEvent(changeEvent);
creditsDetail.setChangedCredits(changeCredits);
creditsDetail.setTaskId(orderNo);
// creditsDetail.setCredits(finalCredits);
creditsDetail.setCredits(account.getCredits());
creditsDetail.setCreateTime(LocalDateTime.now());

View File

@@ -289,7 +289,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
if (b) creditsService.insertToCreditsDetail(accountId,
CreditsEventsEnum.TO_PRODUCT_IMAGE.getName(),
CreditsEventsEnum.TO_PRODUCT_IMAGE.getValue(),
"negative");
"negative", null);
}
}
@@ -764,7 +764,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
if (b) creditsService.insertToCreditsDetail(accountId,
CreditsEventsEnum.RELIGHT.getName(),
CreditsEventsEnum.RELIGHT.getValue(),
"negative");
"negative", null);
}
}

View File

@@ -4,15 +4,19 @@ package com.ai.da.service.impl;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.CreditsEventsEnum;
import com.ai.da.common.enums.OrderStatusEnum;
import com.ai.da.common.enums.ProductEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.utils.OrderNoUtils;
import com.ai.da.mapper.primary.OrderInfoMapper;
import com.ai.da.mapper.primary.PaymentInfoMapper;
import com.ai.da.mapper.primary.ProductMapper;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.mapper.primary.entity.PaymentInfo;
import com.ai.da.model.dto.QueryPageByTimeDTO;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.ai.da.service.OrderInfoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.netty.util.internal.StringUtil;
@@ -34,9 +38,13 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
@Resource
private ProductMapper productMapper;
@Resource
private PaymentInfoMapper paymentInfoMapper;
@Override
public OrderInfo createOrderByProductId(Integer amount, String paymentType) {
//查找已存在但未支付的订单
/*OrderInfo orderInfo = this.getNoPayOrderByProductId(amount, paymentType);
if( orderInfo != null){
@@ -51,7 +59,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
//生成订单
OrderInfo orderInfo = new OrderInfo();
orderInfo.setAccountId(accountId);
orderInfo.setTitle("积分购买 X" + amount );
orderInfo.setTitle("积分购买 X" + amount);
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号 ??
// orderInfo.setProductId(amount);
// orderInfo.setTotalFee(Integer.parseInt(CreditsEventsEnum.PRICE.getValue()) * amount); // 元 HKD
@@ -63,8 +71,37 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
return orderInfo;
}
public OrderInfo createOrderByProductId(Integer amount, String paymentType, ProductEnum product) {
//获取商品信息
// Product product = productMapper.selectById(amount);
String title;
if (product.equals(ProductEnum.CreditsProduct)) {
title = "积分购买 X" + amount;
} else {
title = product.getName();
}
AuthPrincipalVo userHolder = UserContext.getUserHolder();
Long accountId = userHolder.getId();
//生成订单
OrderInfo orderInfo = new OrderInfo();
orderInfo.setAccountId(accountId);
orderInfo.setTitle(title);
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); // 自定义订单号
// orderInfo.setProductId(amount);
// orderInfo.setTotalFee(Integer.parseInt(CreditsEventsEnum.PRICE.getValue()) * amount); // 元 HKD
orderInfo.setTotalFee((float) (product.getPrice() * amount)); // 元 HKD
orderInfo.setOrderStatus(OrderStatusEnum.NOT_PAY.getType()); //未支付
orderInfo.setPaymentType(paymentType);
baseMapper.insert(orderInfo);
return orderInfo;
}
/**
* 存储订单二维码
*
* @param orderNo
* @param codeUrl
*/
@@ -82,6 +119,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
/**
* 查询订单列表,并倒序查询
*
* @return
*/
@Override
@@ -93,6 +131,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
/**
* 根据订单号更新订单状态
*
* @param orderNo
* @param orderStatus
*/
@@ -112,6 +151,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
/**
* 根据订单号获取订单状态
*
* @param orderNo
* @return
*/
@@ -121,7 +161,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("order_no", orderNo);
OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
if(orderInfo == null){
if (orderInfo == null) {
return null;
}
return orderInfo.getOrderStatus();
@@ -129,6 +169,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
/**
* 查询创建超过minutes分钟并且未支付的订单
*
* @param minutes
* @return
*/
@@ -149,6 +190,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
/**
* 根据订单号获取订单
*
* @param orderNo
* @return
*/
@@ -166,6 +208,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
/**
* 根据商品id查询未支付订单
* 防止重复创建订单对象
*
* @param productId
* @return
*/
@@ -181,16 +224,16 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
}
@Override
public PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO){
public PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO) {
QueryWrapper<OrderInfo> qw = new QueryWrapper<>();
qw.eq("account_id",UserContext.getUserHolder().getId());
qw.eq("account_id", UserContext.getUserHolder().getId());
String startTime = queryPageByTimeDTO.getStartTime();
String endTime = queryPageByTimeDTO.getEndTime();
if (StringUtil.isNullOrEmpty(startTime)){
if (StringUtil.isNullOrEmpty(startTime)) {
startTime = "2024-02-01 00:00:00";
}
if (StringUtil.isNullOrEmpty(endTime)){
if (StringUtil.isNullOrEmpty(endTime)) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
endTime = now.format(dateTimeFormatter);
@@ -206,11 +249,28 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
return PageBaseResponse.success(orderInfo);
}
public void updateOrderNoById(Long id, String orderNo){
public void updateOrderNoById(Long id, String orderNo) {
OrderInfo orderInfo = new OrderInfo();
orderInfo.setId(id);
orderInfo.setOrderNo(orderNo);
baseMapper.updateById(orderInfo);
}
public void updateTotalFeeByOrderNo(String orderNo) {
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
qw.eq("order_no", orderNo);
List<PaymentInfo> paymentInfos = paymentInfoMapper.selectList(qw);
Float sum = paymentInfos.stream()
.map(PaymentInfo::getPayerTotal)
.reduce(0f, Float::sum);
baseMapper.update(
new OrderInfo(),
new UpdateWrapper<OrderInfo>()
.eq("order_no", orderNo)
.set("total_fee", sum)
);
}
}

View File

@@ -441,11 +441,11 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
* 申请退款
*/
@Transactional(rollbackFor = Exception.class)
public Boolean refundOrder(String orderId, String reason) throws IOException {
public Boolean refundOrder(String orderNo, String reason) throws IOException {
RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderId, reason);
RefundInfo refundByOrderNo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderId);
OrdersGetRequest ordersGetRequest = new OrdersGetRequest(orderNo);
PayPalClient payPalClient = new PayPalClient();
HttpResponse<com.paypal.orders.Order> ordersGetResponse = null;
ordersGetRequest.authorization("Bearer " + getOAuth());
@@ -461,7 +461,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
request.authorization("Bearer " + getOAuth());
request.prefer("return=representation");
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId);
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
request.requestBody(buildRefundRequestBody(String.valueOf(orderInfo.getTotalFee()), reason));
HttpResponse<com.paypal.payments.Refund> response = null;
try {
@@ -476,7 +476,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
//进行数据库操作,修改状态为已退款(配合回调和退款查询确定退款成功)
//更新订单状态
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_SUCCESS);
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS);
refundsInfoService.updateRefundForPayPal(
refundByOrderNo.getId(),
@@ -485,14 +485,14 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功
// 更新积分状态
OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId);
OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
// creditsService.creditsRefund(orderByOrderNo.getAccountId(), orderByOrderNo.getTotalFee() / Integer.parseInt(CreditsEventsEnum.PRICE.getValue()));
creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())));
creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())), orderNo);
log.info("退款成功");
result = Boolean.TRUE;
} else {
//更新订单状态
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL);
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL);
//更新退款单
refundsInfoService.updateRefundForPayPal(
@@ -571,19 +571,19 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
// 处理当前订单
@Transactional(rollbackFor = Exception.class)
public void processOrder(String orderId) {
public void processOrder(String orderNo) {
// 1、确定当前订单是否已经被扣款
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderId);
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
if (orderInfo.getOrderStatus().equals(OrderStatusEnum.SUCCESS.getType())) {
// 直接返回
return;
}
// 发起扣款请求
Order capturedOrder = captureOrder(orderId);
Order capturedOrder = captureOrder(orderNo);
// 业务处理
if (PayPalOrderStatusEnum.COMPLETED.getStatus().equals(capturedOrder.status())) {
//更新订单状态
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS);
orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
//记录支付日志
paymentInfoService.createPaymentInfoForPayPal(capturedOrder);
float quantity = orderInfo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue());
@@ -593,7 +593,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
creditsService.insertToCreditsDetail(orderInfo.getAccountId(),
CreditsEventsEnum.BUY_CREDITS.getName() + "--PayPal",
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
"positive");
"positive", orderNo);
}
}
@@ -637,7 +637,7 @@ public class PayPalCheckoutServiceImpl implements PayPalCheckoutService {
creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
CreditsEventsEnum.BUY_CREDITS.getName() + "--Paypal",
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
"positive");
"positive", orderNo);
}
}

View File

@@ -1,27 +1,49 @@
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.service.PaymentInfoService;
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;
import com.stripe.Stripe;
import com.stripe.exception.StripeException;
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;
@Service
@Slf4j
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo> implements PaymentInfoService {
@Resource
private StripeService stripeService;
/**
* 记录支付日志:微信支付
* @param plainText
@@ -50,7 +72,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPaymentType(PayTypeEnum.WXPAY.getType());
paymentInfo.setTransactionId(transactionId);
paymentInfo.setTradeType(tradeType);
paymentInfo.setTradeState(tradeState);
// 原来的单位是:分 Int 现改为:元 Float
paymentInfo.setPayerTotal(payerTotal / 100.0F);
@@ -83,7 +104,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPaymentType(PayTypeEnum.ALIPAY.getType());
paymentInfo.setTransactionId(transactionId);
paymentInfo.setTradeType("电脑网站支付");
paymentInfo.setTradeState(tradeStatus);
// 原来的单位是分 Int 现改为元 Long
paymentInfo.setPayerTotal(totalAmountInt / 100.0F);
@@ -107,7 +127,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
paymentInfo.setOrderNo(order.id());
paymentInfo.setPaymentType(PayTypeEnum.PAYPAL.getType());
paymentInfo.setTransactionId(order.id());
paymentInfo.setTradeType("电脑网站支付");
paymentInfo.setTradeState(order.status());
// todo 确认这里的数据单位是不是元
paymentInfo.setPayerTotal(totalAmountFloat);
@@ -139,7 +158,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPaymentType(PayTypeEnum.ALIPAY_HK.getType());
paymentInfo.setTransactionId(transactionId);
paymentInfo.setTradeType("电脑网站支付");
paymentInfo.setTradeState(tradeStatus);
paymentInfo.setPayerTotal(totalAmountFloat);
@@ -150,10 +168,10 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
baseMapper.insert(paymentInfo);
}
@Override
public void createPaymentInfoForStripe(Session session){
public void createOrUpdatePaymentInfoForStripe(Session session){
String orderId = session.getMetadata().get("orderId");
String status = session.getStatus();
// 获取transactionId,从sessionId更改为invoiceId
String sessionId = session.getId();
Long amountTotal = session.getAmountTotal();
// stripe 的支付金额单位是分
@@ -163,7 +181,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
paymentInfo.setOrderNo(orderId);
paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
paymentInfo.setTransactionId(sessionId);
paymentInfo.setTradeType("电脑网站支付");
paymentInfo.setTradeState(status);
paymentInfo.setPayerTotal(divide);
Gson gson = new Gson();
@@ -173,12 +190,144 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
baseMapper.insert(paymentInfo);
}
@Override
public PaymentInfo getPaymentInfoByOrderId(String orderId){
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
qw.eq("order_no", orderId);
@Value("${stripe.private-key}")
private String privateKey;
public PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice){
Stripe.apiKey = privateKey;
// 获取transactionId,从sessionId更改为invoiceId
String invoiceId = invoice.getId();
return baseMapper.selectOne(qw);
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
qw.eq("transaction_id", invoiceId);
PaymentInfo paymentInfo = baseMapper.selectOne(qw);
String status = invoice.getStatus();
// 判断当前支付是否已经被记录,确保同一个支付不会被重复记录
if (Objects.isNull(paymentInfo)){
String orderNo;
try {
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 - ", "");
}
} catch (StripeException e) {
throw new RuntimeException(e);
}
Long amountTotal;
if (status.equals("paid")){
amountTotal = invoice.getAmountPaid();
}else {
amountTotal = invoice.getAmountDue();
}
// stripe 的支付金额单位是分,在我们数据库中金额单位为 元
Float divide = new BigDecimal(amountTotal).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue();
String type = invoice.getBillingReason().equals("subscription_create") ? "new" :
invoice.getBillingReason().equals("subscription_cycle") ? "renewal" : invoice.getBillingReason();
Map<String, String> paymentMethod = stripeService.getPaymentMethodByInvoiceId(invoiceId);
paymentInfo = new PaymentInfo();
paymentInfo.setOrderNo(orderNo);
paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
paymentInfo.setTransactionId(invoiceId);
// 使用invoice的状态
paymentInfo.setTradeState(status);
paymentInfo.setPayerTotal(divide);
Gson gson = new Gson();
String json = gson.toJson(invoice);
paymentInfo.setContent(json);
paymentInfo.setType(type);
paymentInfo.setNotified(0);
paymentInfo.setPaymentMethod(paymentMethod.get("paymentMethod"));
paymentInfo.setLast4(paymentMethod.get("last4"));
paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl());
paymentInfo.setCreateTime(LocalDateTime.now());
baseMapper.insert(paymentInfo);
}else {
paymentInfo.setTradeState(status);
paymentInfo.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(paymentInfo);
}
return paymentInfo;
}
public PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge){
Stripe.apiKey = privateKey;
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
qw.eq("transaction_id", charge.getInvoice());
PaymentInfo paymentInfo = baseMapper.selectOne(qw);
Charge.PaymentMethodDetails paymentMethodDetails = charge.getPaymentMethodDetails();
String paymentMethod;
String last4 = "N/A";
switch (paymentMethodDetails.getType()){
case "alipay":
paymentMethod = "Alipay";
break;
case "bancontact":
paymentMethod = "BanContact";
break;
case "card":
Charge.PaymentMethodDetails.Card card = paymentMethodDetails.getCard();
String brand = card.getBrand();
brand = brand.substring(0, 1).toUpperCase() + brand.substring(1);
paymentMethod = brand + " " + card.getFunding() + "card";
last4 = card.getLast4();
break;
case "eps":
Charge.PaymentMethodDetails.Eps eps = paymentMethodDetails.getEps();
paymentMethod = eps.getBank();
break;
case "giropay":
paymentMethod = "GiroPay";
break;
case "ideal":
Charge.PaymentMethodDetails.Ideal ideal = paymentMethodDetails.getIdeal();
paymentMethod = ideal.getBank();
break;
case "link":
paymentMethod = "Link";
break;
default:
paymentMethod = "N/A";
}
if (Objects.isNull(paymentInfo)){
Stripe.apiKey = privateKey;
Float divide = new BigDecimal(charge.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue();
paymentInfo = new PaymentInfo();
paymentInfo.setOrderNo(charge.getDescription().replace("AiDA - ", ""));
paymentInfo.setTransactionId(charge.getInvoice());
paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
paymentInfo.setTradeState(charge.getStatus());
paymentInfo.setPayerTotal(divide);
paymentInfo.setNotified(0);
paymentInfo.setPaymentMethod(paymentMethod);
paymentInfo.setLast4(last4);
paymentInfo.setCreateTime(LocalDateTime.now());
baseMapper.insert(paymentInfo);
}else {
paymentInfo.setTradeState(charge.getStatus());
paymentInfo.setPaymentMethod(paymentMethod);
paymentInfo.setLast4(last4);
paymentInfo.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(paymentInfo);
}
return paymentInfo;
}
@Override
public List<PaymentInfo> getPaymentInfoByOrderNo(String orderId, String order){
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
qw.eq("order_no", orderId).orderByDesc(order.equals("DESC"),"id");
return baseMapper.selectList(qw);
}
@Override
@@ -190,4 +339,36 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
baseMapper.updateById(paymentInfo);
}
public PageBaseResponse<OrderListVO> 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<OrderListVO> 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<OrderListVO> 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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -149,7 +149,7 @@ public class SuperResolutionServiceImpl extends ServiceImpl<TaskListMapper, Task
creditsService.insertToCreditsDetail(accountId,
CreditsEventsEnum.SUPER_RESOLUTION.getName(),
CreditsEventsEnum.SUPER_RESOLUTION.getValue(),
"negative");
"negative", null);
}
}

View File

@@ -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

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ai.da.mapper.primary.AffiliateIncomeMapper">
<select id="getPersonalMonthlyIncome" resultType="java.util.Map">
SELECT
DATE_FORMAT(payment_time, '%m') as yearMonth,
SUM(commission) AS totalCommission
FROM
t_affiliate_income
WHERE
YEAR(payment_time) = #{year}
and affiliate_account_id = #{affiliateAccountId}
GROUP BY
yearMonth
ORDER BY
yearMonth
</select>
<select id="getMonthlyAffiliateIncome" resultType="java.util.Map">
SELECT
DATE_FORMAT(payment_time, '%m') as yearMonth,
SUM(amount) AS totalAmount,
SUM(commission) AS totalCommission
FROM
t_affiliate_income
WHERE
YEAR(payment_time) = #{year}
and MONTH(payment_time) = #{month}
GROUP BY
yearMonth
ORDER BY
yearMonth
</select>
</mapper>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ai.da.mapper.primary.AffiliateMapper">
<select id="getMonthlyApprovedAffiliate" resultType="java.util.Map">
SELECT
count(*) AS count
FROM
t_affiliate
WHERE
YEAR ( create_time ) = #{year}
AND MONTH ( create_time ) = #{month}
AND status = 'Active'
</select>
</mapper>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ai.da.mapper.primary.PaymentInfoMapper">
<select id="selectPageOrderList" resultType="com.ai.da.model.vo.OrderListVO">
SELECT
p.id AS id,
p.payer_total AS amount,
p.payment_type AS paymentMethod,
p.trade_state AS state,
o.title AS orderType,
p.hosted_invoice_url AS invoiceLink,
p.create_time
FROM
`t_payment_info` p
LEFT JOIN
`t_order_info` o
ON
p.order_no = o.order_no
WHERE
o.account_id = #{accountId}
AND p.create_time BETWEEN #{startTime} AND #{endTime}
ORDER BY
p.id DESC
LIMIT #{pageSize} OFFSET #{offset};
</select>
<select id="queryOrderListTotalCount" parameterType="map" resultType="int">
SELECT
COUNT(*)
FROM
`t_payment_info` p
LEFT JOIN
`t_order_info` o
ON
p.order_no = o.order_no
WHERE
o.account_id = #{accountId}
AND p.create_time BETWEEN #{startTime} AND #{endTime};
</select>
</mapper>

View File

@@ -148,6 +148,7 @@ remaining.modifications=Remaining modifications are 0
you.have.participated.in.the.event=You have participated in the event.
only.original.works.can.participate.in.the.event=Sorry, only original works can participate in the event.
remaining.credits.insufficient=Your remaining credits are insufficient for this generation. Please recharge.
you.haven't.subscribed.to.any.products.yet=You haven't subscribed to any products yet
# 可能会报异常
# Informative:

View File

@@ -143,6 +143,7 @@ remaining.modifications=剩余修改次数为0
you.have.participated.in.the.event=您已经参与活动。
only.original.works.can.participate.in.the.event=抱歉,只有原创作品能参与活动。
remaining.credits.insufficient=您的剩余积分不够本次生成消耗,请充值
you.haven't.subscribed.to.any.products.yet=您还未订阅任何产品
# 可能会报异常
# Informative:

View File

@@ -27,13 +27,13 @@ paypal.webhook_id=1D107312EX592781K
##### Stripe
# developer
#stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
#stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
# kim - test
#stripe.private-key=sk_test_51LwPrxH7nPZ8bkrNj67TFD7sxucaTANs1lf0KGSu1QSJfxYXcnigq2wTaZyZzST7y0fMbhhvaJZ4LjjFhr95M83a00eXrmOTL0
#stripe.webhook-sign-secret=whsec_GoyVEAaBtuGD5Rt55z83JnPnLDAZTN3u
# kim - live
stripe.private-key=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
stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11
#stripe.private-key=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
#stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11