diff --git a/.gitea/workflows/develop_build_manual.yaml b/.gitea/workflows/develop_build_manual.yaml
index 59b2321d..e32f3ddd 100644
--- a/.gitea/workflows/develop_build_manual.yaml
+++ b/.gitea/workflows/develop_build_manual.yaml
@@ -135,8 +135,6 @@ jobs:
cd ${{ env.REMOTE_DEPLOY_PATH }}
echo "停止旧容器..."
docker compose down || true
- echo "清理Docker资源..."
- docker system prune -f
echo "构建镜像..."
docker compose build --no-cache
echo "启动服务..."
diff --git a/pom.xml b/pom.xml
index 9c14ce4b..c875ac6b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -240,7 +240,7 @@
com.stripe
stripe-java
- 26.2.0
+ 32.0.0
diff --git a/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java b/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java
index ab93ba3b..e3185443 100644
--- a/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java
+++ b/src/main/java/com/ai/da/common/enums/CreditsEventsEnum.java
@@ -30,7 +30,7 @@ public enum CreditsEventsEnum {
INIT_QUARTERLY("init_quarterly", "12000"),
INIT_MONTHLY_EDU("init_monthly_edu", "3500"),
INIT_TRIAL("init_trial", "100"),
- INIT_WEEKLY("init_weekly","6000"),
+ INIT_DAILY("init_daily","100"),
RESET_YEAR_CREDITS("reset_year_credits","6000"),
// SUPER_RESOLUTION("Super Resolution","30"),
diff --git a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java
index 02767686..2baed360 100644
--- a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java
+++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java
@@ -34,6 +34,11 @@ public enum OrderStatusEnum {
* 已退款
*/
REFUND_SUCCESS("已退款"),
+
+ /**
+ * 已部分退款
+ */
+ PARTIAL_REFUND_SUCCESS("已部分退款"),
/**
* 退款异常
*/
diff --git a/src/main/java/com/ai/da/common/enums/PaymentInfoType.java b/src/main/java/com/ai/da/common/enums/PaymentInfoType.java
new file mode 100644
index 00000000..a7c20929
--- /dev/null
+++ b/src/main/java/com/ai/da/common/enums/PaymentInfoType.java
@@ -0,0 +1,19 @@
+package com.ai.da.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum PaymentInfoType {
+
+ NEW("new"),
+
+ RENEWAL("renewal"),
+
+ CREDIT("credit"),
+
+ MANUAL("manual");
+
+ private final String type;
+}
diff --git a/src/main/java/com/ai/da/common/enums/ProductEnum.java b/src/main/java/com/ai/da/common/enums/ProductEnum.java
index c68a7385..a37568fd 100644
--- a/src/main/java/com/ai/da/common/enums/ProductEnum.java
+++ b/src/main/java/com/ai/da/common/enums/ProductEnum.java
@@ -3,6 +3,8 @@ package com.ai.da.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
+import java.util.Arrays;
+
@Getter
@AllArgsConstructor
public enum ProductEnum {
@@ -23,11 +25,27 @@ public enum ProductEnum {
;
/**
- * 类型
+ * 显示名称(用于与 orderInfo.title 匹配)
*/
private final String name;
private final Long price;
private final Long credits;
+
+ /**
+ * 根据显示名称获取枚举
+ *
+ * @param name 显示名称(与 orderInfo.title 匹配)
+ * @return 对应的枚举,未找到返回 null
+ */
+ public static ProductEnum getByName(String name) {
+ if (name == null) {
+ return null;
+ }
+ return Arrays.stream(values())
+ .filter(pe -> pe.name.equals(name))
+ .findFirst()
+ .orElse(null);
+ }
}
diff --git a/src/main/java/com/ai/da/common/utils/RedisUtil.java b/src/main/java/com/ai/da/common/utils/RedisUtil.java
index c91411fa..207070a4 100644
--- a/src/main/java/com/ai/da/common/utils/RedisUtil.java
+++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java
@@ -549,6 +549,26 @@ public class RedisUtil {
public final static String STRIPE_EXCEPTION_LOG = "StripeException:";
public final static String SUBSCRIPTION_SENT_EMAIL_TYPE = "SubscriptionEmailSentType:";
+ private static final String STRIPE_WEBHOOK_PROCESSED_PREFIX = "StripeWebhook:processed:";
+
+ /**
+ * 尝试将 webhook eventId 标记为已处理(SETNX 语义)
+ * @return true=该事件之前未处理(本次处理),false=该事件已处理过(跳过)
+ */
+ public boolean tryMarkWebhookProcessed(String eventId, long expireSeconds) {
+ String key = STRIPE_WEBHOOK_PROCESSED_PREFIX + eventId;
+ Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", expireSeconds, TimeUnit.SECONDS);
+ return Boolean.TRUE.equals(result);
+ }
+
+ /**
+ * 检查 webhook eventId 是否已处理
+ */
+ public boolean isWebhookProcessed(String eventId) {
+ String key = STRIPE_WEBHOOK_PROCESSED_PREFIX + eventId;
+ return Boolean.TRUE.equals(redisTemplate.hasKey(key));
+ }
+
public void batchDeleteKeysWithSamePrefix(String prefix){
Set keys = redisTemplate.keys(prefix + "*");
assert keys != null;
diff --git a/src/main/java/com/ai/da/controller/StripeController.java b/src/main/java/com/ai/da/controller/StripeController.java
index dd12385d..149382d1 100644
--- a/src/main/java/com/ai/da/controller/StripeController.java
+++ b/src/main/java/com/ai/da/controller/StripeController.java
@@ -1,5 +1,6 @@
package com.ai.da.controller;
+import com.ai.da.common.context.UserContext;
import com.ai.da.common.response.Response;
import com.ai.da.common.utils.DateUtil;
import com.ai.da.common.utils.RedisUtil;
@@ -10,6 +11,7 @@ import com.ai.da.model.dto.ProductPurchaseDTO;
import com.ai.da.model.dto.QueryCouponsPageDTO;
import com.ai.da.model.vo.CheckCouponsVO;
import com.ai.da.service.StripeService;
+import com.ai.da.service.StripeSubscriptionService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.paypal.http.HttpResponse;
import com.paypal.payments.Refund;
@@ -40,6 +42,8 @@ public class StripeController {
private StripeService stripeService;
@Resource
private RedisUtil redisUtil;
+ @Resource
+ private StripeSubscriptionService stripeSubscriptionService;
@Operation(summary = "创建支付链接")
@PostMapping("/createOrder")
@@ -53,30 +57,29 @@ public class StripeController {
@Operation(summary = "支付通知")
@PostMapping("/trade/notify")
public void callback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- try{
- Boolean result = stripeService.notify(request);
- if (result){
- response.setStatus(HttpServletResponse.SC_OK);
- }else {
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- }catch (Exception e){
- log.error("Stripe Controller层异常捕捉, {}", e.getMessage());
- e.printStackTrace();
+ boolean result;
+ try {
+ result = stripeService.notify(request);
+ } catch (Exception e) {
+ log.error("Stripe Controller层异常捕捉, {}", e.getMessage(), e);
String key_1 = RedisUtil.STRIPE_EXCEPTION_LOG + DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD_HH);
- String key_2 = key_1 + ":" + DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD_hh_mm_ss);
+ String key_2 = key_1 + ":" + DateUtil.dateToStr(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS);
String stackTrace = stripeService.getStackTrace(e, 10);
redisUtil.addToString(key_2, stackTrace);
Long size = redisUtil.getSize(key_1);
- // 给我发送邮件
- if (webhookReminderFlag.equals("1") && size == 3){
+ if ("1".equals(webhookReminderFlag) && size == 3) {
SendEmailUtil.commonExceptionReminder("Stripe Webhook 回调处理出现异常", new String[]{"xupei3360@163.com"});
}
+ result = false;
+ }
+ if (result) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ } else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
- @Operation(summary = "申请退款")
+/* @Operation(summary = "申请退款")
@GetMapping("/trade/refund/{orderNo}/{reason}")
public Response> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException {
String response = stripeService.refund(null,orderNo,reason);
@@ -85,7 +88,7 @@ public class StripeController {
}else {
return Response.fail("Request for refund failed.");
}
- }
+ }*/
@Operation(summary = "获取订阅")
@GetMapping("/getSubscription")
@@ -100,7 +103,8 @@ public class StripeController {
@Operation(summary = "取消订阅")
@GetMapping("/cancelSubscription")
public Response cancelSubscription(@RequestParam String subscriptionId, @RequestParam(required = false) String reason) {
- stripeService.cancelSubscription(subscriptionId, reason);
+ Long accountId = UserContext.getUserHolder().getId();
+ stripeSubscriptionService.cancelSubscription(subscriptionId, reason, accountId);
return Response.success("success");
}
diff --git a/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java
index dd462038..15ae0cee 100644
--- a/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java
+++ b/src/main/java/com/ai/da/mapper/primary/entity/OrderInfo.java
@@ -23,8 +23,6 @@ public class OrderInfo extends BaseEntity{
private String note;
- private byte autoRenewal;
-
private String paymentType;//支付方式
// 可用于标记用户订单是否首次订阅
diff --git a/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java b/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java
index 179dea8a..a6245cad 100644
--- a/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java
+++ b/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java
@@ -1,5 +1,6 @@
package com.ai.da.model.dto;
+import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -24,6 +25,7 @@ public class ProductPurchaseDTO {
@Schema(description = "EcoMonth || Month || Year")
private String subscribeType;
+ @Hidden
@Schema(description = "是否自动续订 one_time || recurring")
private Boolean autoRenewal;
diff --git a/src/main/java/com/ai/da/service/OrderInfoService.java b/src/main/java/com/ai/da/service/OrderInfoService.java
index 7c5deabf..ffe1402c 100644
--- a/src/main/java/com/ai/da/service/OrderInfoService.java
+++ b/src/main/java/com/ai/da/service/OrderInfoService.java
@@ -16,7 +16,7 @@ public interface OrderInfoService extends IService {
OrderInfo createOrderByProductId(Integer productId, String paymentType, HttpServletRequest request);
OrderInfo createOrderByProductId(Integer amount, String paymentType, ProductEnum product,
- HttpServletRequest request, byte autoRenewal);
+ HttpServletRequest request);
void saveCodeUrl(String orderNo, String codeUrl);
diff --git a/src/main/java/com/ai/da/service/PaymentInfoService.java b/src/main/java/com/ai/da/service/PaymentInfoService.java
index a9ef0453..8c68221b 100644
--- a/src/main/java/com/ai/da/service/PaymentInfoService.java
+++ b/src/main/java/com/ai/da/service/PaymentInfoService.java
@@ -9,6 +9,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.paypal.orders.Order;
import com.stripe.model.Charge;
import com.stripe.model.Invoice;
+import com.stripe.model.PaymentMethod;
+import com.stripe.model.checkout.Session;
import java.util.List;
import java.util.Map;
@@ -23,9 +25,15 @@ public interface PaymentInfoService extends IService {
void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO, String type);
- PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice);
+ void createOrUpdatePaymentInfoForStripe(Session session);
- PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge);
+ Map getPaymentMethodInfo(String sessionId, String subscriptionId);
+
+ PaymentMethod getPaymentMethodBySubscriptionId(String subscriptionId);
+
+ PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice, Map paymentMethodInfo, List discounts);
+
+// PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge);
List getPaymentInfoByOrderNo(String orderId, String order);
@@ -35,5 +43,9 @@ public interface PaymentInfoService extends IService {
List getPaymentInfoByPromCode(Long accountId, String promCode);
- PaymentInfo updatePaymentRefundStatus(Charge charge);
+// PaymentInfo updatePaymentRefundStatus(Charge charge);
+
+ void updatePaymentRefundStatusByChargeId(Charge charge, String status);
+
+ void updatePaymentRefundStatusByInvoiceId(String invoiceId, String status);
}
diff --git a/src/main/java/com/ai/da/service/RefundInfoService.java b/src/main/java/com/ai/da/service/RefundInfoService.java
index d1cb0fe7..0a38ded6 100644
--- a/src/main/java/com/ai/da/service/RefundInfoService.java
+++ b/src/main/java/com/ai/da/service/RefundInfoService.java
@@ -24,10 +24,18 @@ public interface RefundInfoService extends IService {
List getByChargeId(String chargeId);
+ RefundInfo getByRefundId(String refundId);
+
RefundInfo createRefundForStripe(Refund refund);
RefundInfo updateRefundStatusForStripe(Refund refund);
RefundInfo updateRefundForStripe(Charge charge);
+ RefundInfo handleRefundCreated(Refund refund);
+
+ RefundInfo handleRefundSucceeded(Refund refund);
+
+ RefundInfo handleRefundFailed(Refund refund);
+
}
diff --git a/src/main/java/com/ai/da/service/StripeService.java b/src/main/java/com/ai/da/service/StripeService.java
index 8ee77ccb..27a2457d 100644
--- a/src/main/java/com/ai/da/service/StripeService.java
+++ b/src/main/java/com/ai/da/service/StripeService.java
@@ -21,42 +21,20 @@ public interface StripeService {
SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId);
- String refund(String amount, String orderId, String reason);
-
void checkOrderStatus(String orderNo);
List getSubscriptionIds(String name, String userEmail) throws StripeException;
- Map getPaymentMethodByInvoiceId(String invoiceId);
-
- void cancelSubscription(String orderNo, String cancelReason);
-
void cancelSubscriptionTemp(String subscriptionId);
- Map getPaymentMethod(String paymentMethodId);
-
boolean sendEmail(String subscriptionId, String type, String orderNo);
String getLanguage(String language, String country, String type);
- /*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