From 1b15aed6a28801b2a6f8f433f01678d7d19ca35a Mon Sep 17 00:00:00 2001 From: xupei Date: Thu, 28 Nov 2024 10:43:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E4=BC=98=E5=8C=96-=E7=BB=AD?= =?UTF-8?q?=E8=AE=A2=E5=A4=B1=E8=B4=A5=E9=82=AE=E4=BB=B6=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/da/common/enums/OrderStatusEnum.java | 9 - .../com/ai/da/common/utils/SendEmailUtil.java | 12 +- .../ai/da/controller/StripeController.java | 42 +++- .../da/mapper/primary/entity/PaymentInfo.java | 3 + .../ai/da/model/dto/ProductPurchaseDTO.java | 2 - .../model/dto/SubscriptionEmailParamsDTO.java | 2 + .../java/com/ai/da/service/StripeService.java | 10 +- .../service/impl/PaymentInfoServiceImpl.java | 1 + .../ai/da/service/impl/StripeServiceImpl.java | 230 ++++++++++++++++-- 9 files changed, 269 insertions(+), 42 deletions(-) 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 8f8699a6..02767686 100644 --- a/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java +++ b/src/main/java/com/ai/da/common/enums/OrderStatusEnum.java @@ -10,43 +10,34 @@ public enum OrderStatusEnum { * 未支付 */ NOT_PAY("未支付"), - - /** * 支付成功 */ SUCCESS("支付成功"), - /** * 支付失败 */ FAILURE("支付失败"), - /** * 已关闭 */ TIMEOUT_CLOSED("超时已关闭"), - /** * 已取消 */ CANCEL("用户已取消"), - /** * 退款中 */ REFUND_PROCESSING("退款中"), - /** * 已退款 */ REFUND_SUCCESS("已退款"), - /** * 退款异常 */ REFUND_ABNORMAL("退款异常"), - /** * paypal订单状态为 APPROVED */ diff --git a/src/main/java/com/ai/da/common/utils/SendEmailUtil.java b/src/main/java/com/ai/da/common/utils/SendEmailUtil.java index c6d61e68..b14cd28a 100644 --- a/src/main/java/com/ai/da/common/utils/SendEmailUtil.java +++ b/src/main/java/com/ai/da/common/utils/SendEmailUtil.java @@ -781,6 +781,8 @@ public class SendEmailUtil { 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{ @@ -816,6 +818,14 @@ public class SendEmailUtil { 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() + ")"); @@ -859,7 +869,7 @@ public class SendEmailUtil { templateMerchant.setTemplateData(JSON.toJSONString(subscriptionEmailParamsDTO)); merchant.setTemplate(templateMerchant); - if (!type.equals("cancel") && !type.equals("fail_new") && !type.equals("fail_renewal") ){ + if (!type.equals("cancel") && !type.equals("fail_new") ){ // 返回的resp是一个SendEmailResponse的实例,与请求对象对应 SendEmailResponse respUser = client.SendEmail(user); log.info("邮件主题:{},发送结果toUser###{}", user.getSubject(), SendEmailResponse.toJsonString(respUser)); diff --git a/src/main/java/com/ai/da/controller/StripeController.java b/src/main/java/com/ai/da/controller/StripeController.java index eac49851..8becf26b 100644 --- a/src/main/java/com/ai/da/controller/StripeController.java +++ b/src/main/java/com/ai/da/controller/StripeController.java @@ -9,19 +9,22 @@ import com.stripe.exception.StripeException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; -import org.simpleframework.xml.core.Validate; 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; @Api(tags = "Stripe模块") @Slf4j @RestController @RequestMapping("/api/stripe") +@ApiIgnore public class StripeController { @Resource @@ -29,7 +32,7 @@ public class StripeController { @ApiOperation("创建支付链接") @PostMapping("/createOrder") - public Response pay(@Validate @RequestBody ProductPurchaseDTO productPurchaseDTO) { + public Response pay(@Valid @RequestBody ProductPurchaseDTO productPurchaseDTO) { return Response.success(stripeService.pay(productPurchaseDTO)); } @@ -45,7 +48,7 @@ public class StripeController { } @ApiOperation("申请退款") - @PostMapping("/trade/refund/{orderNo}/{reason}") + @GetMapping("/trade/refund/{orderNo}/{reason}") public Response> refund(@PathVariable String orderNo, @PathVariable String reason) throws IOException { String response = stripeService.refund(null,orderNo,reason); if (response.equals("退款成功")){ @@ -56,19 +59,44 @@ public class StripeController { } @ApiOperation("获取订阅") - @PostMapping("/getSubscription") - public void getSubscription() { + @GetMapping("/getSubscription") + public Response> getSubscription(@RequestParam String name, @RequestParam String email) { try { - stripeService.getSubscription("xp", "xupei3360@163.com"); + return Response.success(stripeService.getSubscriptionIds(name, email)); } catch (StripeException e) { throw new RuntimeException(e); } } @ApiOperation("取消订阅") - @PostMapping("/cancelSubscription") + @GetMapping("/cancelSubscription") public Response cancelSubscription(@RequestParam String subscriptionId) { stripeService.cancelSubscription(subscriptionId); return Response.success("success"); } + @ApiOperation("临时 取消订阅") + @GetMapping("/cancelSubscriptionTemp") + public Response cancelSubscriptionTemp(@RequestParam String subscriptionId) { + stripeService.cancelSubscriptionTemp(subscriptionId); + return Response.success("success"); + } + + @ApiOperation("创建订阅 临时") + @GetMapping("/createSubscriptionTemp") + public Response createSubscriptionTemp(@RequestParam String name, @RequestParam String email) { + return Response.success(stripeService.createSubscriptionTemp(name, email)); + } + + @ApiOperation("修改用户默认支付方式 临时") + @GetMapping("/changeCustomerPayment") + public Response changeCustomerPayment(@RequestParam String name, @RequestParam String email) { + return Response.success(stripeService.changeCustomerPayment(name, email)); + } + + @ApiOperation("临时 发送续订失败邮件") + @GetMapping("/sendRenewalFailEmail") + public Response sendRenewalFailEmail(@RequestParam String invoiceId, @RequestParam String subscriptionId, @RequestParam String orderNo) { + return Response.success(stripeService.sendRenewalFailEmail(invoiceId, subscriptionId,orderNo)); + } + } diff --git a/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java b/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java index 8172f4bd..d3533078 100644 --- a/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java +++ b/src/main/java/com/ai/da/mapper/primary/entity/PaymentInfo.java @@ -30,4 +30,7 @@ public class PaymentInfo extends BaseEntity{ private String paymentMethod; private String last4; + + // 发票托管页面 + private String hostedInvoiceUrl; } 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 ce1fb595..0e386e8a 100644 --- a/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java +++ b/src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java @@ -27,6 +27,4 @@ public class ProductPurchaseDTO { @ApiModelProperty("是否自动续订 one_time || recurring") private Boolean autoRenewal; - - private String refId; } diff --git a/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java b/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java index 9ecf0241..1f35b705 100644 --- a/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java +++ b/src/main/java/com/ai/da/model/dto/SubscriptionEmailParamsDTO.java @@ -48,5 +48,7 @@ public class SubscriptionEmailParamsDTO { // 付款失败原因 private String failMessage; + private String accountPageRef; + } diff --git a/src/main/java/com/ai/da/service/StripeService.java b/src/main/java/com/ai/da/service/StripeService.java index b517b325..268f08d2 100644 --- a/src/main/java/com/ai/da/service/StripeService.java +++ b/src/main/java/com/ai/da/service/StripeService.java @@ -2,7 +2,6 @@ package com.ai.da.service; import com.ai.da.model.dto.ProductPurchaseDTO; import com.stripe.exception.StripeException; -import com.stripe.model.Subscription; import javax.servlet.http.HttpServletRequest; import java.util.List; @@ -18,10 +17,12 @@ public interface StripeService { void checkOrderStatus(String orderNo); - List getSubscription(String name, String userEmail) throws StripeException; + List getSubscriptionIds(String name, String userEmail) throws StripeException; void cancelSubscription(String orderNo); + void cancelSubscriptionTemp(String subscriptionId); + Map getPaymentMethod(String paymentMethodId); /*void updateSubscription(String subscriptionId); @@ -32,4 +33,9 @@ public interface StripeService { void checkSubscriptionExpiration(); + String createSubscriptionTemp(String name, String email); + + String changeCustomerPayment(String name, String email); + + boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo); } diff --git a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java index 204a9ce4..3459bf93 100644 --- a/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/PaymentInfoServiceImpl.java @@ -236,6 +236,7 @@ public class PaymentInfoServiceImpl extends ServiceImpl getSubscription(String username, String userEmail) { Stripe.apiKey = privateKey; String customerId = null; @@ -642,6 +663,25 @@ public class StripeServiceImpl implements StripeService { } catch (StripeException e) { throw new RuntimeException(e); } + } + + // 获取所有订阅 + public List getSubscriptionIds(String username, String userEmail) { + Stripe.apiKey = privateKey; + String customerId = null; + try { + customerId = getCustomer(username, userEmail); + SubscriptionCollection list = Subscription.list(SubscriptionListParams.builder() + .setCustomer(customerId).build()); + List data = list.getData(); + ArrayList subscriptionIds = new ArrayList<>(); + data.forEach(subscription -> { + subscriptionIds.add(subscription.getId()); + }); + return subscriptionIds; + } catch (StripeException e) { + throw new RuntimeException(e); + } } @@ -747,7 +787,6 @@ public class StripeServiceImpl implements StripeService { } public boolean sendEmail(String subscriptionId, String type) { - SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO(); QueryWrapper qwSI = new QueryWrapper<>(); qwSI.eq("subscription_id", subscriptionId); @@ -784,14 +823,7 @@ public class StripeServiceImpl implements StripeService { emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString()); emailParamsDTO.setLastOrderDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); emailParamsDTO.setEndOfPrepaidTerm(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); - emailParamsDTO.setPaymentMethod(paymentInfo.getPaymentMethod()); - emailParamsDTO.setLast4(paymentInfo.getLast4()); - emailParamsDTO.setSubscriptionId(subscriptionInfo.getId().toString()); - emailParamsDTO.setFailMessage(orderByOrderNo.getNote()); - emailParamsDTO.setSubscriptionType(subscriptionInfo.getType()); - emailParamsDTO.setStartDate(changeTimeStampFormat(orderByOrderNo.getCreateTime())); - emailParamsDTO.setNextPayDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); - emailParamsDTO.setRenewalTime(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO); SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail()); @@ -840,6 +872,79 @@ public class StripeServiceImpl implements StripeService { return true; } + public boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo){ + // 1、确认当前订单最后一笔支付为fail + // 更新支付信息 + PaymentInfo paymentInfo; + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (StringUtil.isNullOrEmpty(invoiceId)){ + queryWrapper.eq("order_no", orderNo).orderByDesc("id"); + List paymentInfos = paymentInfoService.getBaseMapper().selectList(queryWrapper); + if (paymentInfos.isEmpty() || !paymentInfos.get(0).getTradeState().equals("failed")){ + return false; + }else { + paymentInfo = paymentInfos.get(0); + } + }else { + queryWrapper.eq("transaction_id", invoiceId); + paymentInfo = paymentInfoService.getBaseMapper().selectOne(queryWrapper); + if (Objects.isNull(paymentInfo) + || !paymentInfo.getTradeState().equals("failed") + || paymentInfo.getNotified().equals(1)){ + return false; + } + } + + // 2、确认当前订阅的状态为past_due + SubscriptionInfo subscriptionInfo; + QueryWrapper qwSI = new QueryWrapper<>(); + if (StringUtil.isNullOrEmpty(subscriptionId)){ + qwSI.eq("order_no", orderNo); + }else { + qwSI.eq("subscription_id", subscriptionId); + } + subscriptionInfo = subscriptionInfoMapper.selectOne(qwSI); + if (Objects.isNull(subscriptionInfo) || !subscriptionInfo.getStatus().equals("past_due")){ + return false; + } + + // 3、组参数 + com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(subscriptionInfo.getAccountId()); + String userName = account.getUserName(); + String language = account.getLanguage(); + OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(subscriptionInfo.getOrderNo()); + SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO(); + emailParamsDTO.setUsername(userName); + emailParamsDTO.setOrderId(paymentInfo.getId().toString()); + emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " ")); + emailParamsDTO.setQuantity(String.valueOf(1)); + emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString()); + setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO); + // todo + emailParamsDTO.setAccountPageRef("\"https://www.aida.com.hk/home/homePage\""); + + // 4、发邮件 + SendEmailUtil.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail()); + + PaymentInfo payment = new PaymentInfo(); + payment.setId(paymentInfo.getId()); + payment.setNotified(1); + payment.setUpdateTime(LocalDateTime.now()); + paymentInfoMapper.updateById(payment); + return true; + } + + private void setSubscriptionParams(PaymentInfo paymentInfo, SubscriptionInfo subscriptionInfo, OrderInfo orderByOrderNo, SubscriptionEmailParamsDTO emailParamsDTO) { + emailParamsDTO.setPaymentMethod(paymentInfo.getPaymentMethod()); + emailParamsDTO.setLast4(paymentInfo.getLast4()); + emailParamsDTO.setSubscriptionId(subscriptionInfo.getId().toString()); + emailParamsDTO.setFailMessage(orderByOrderNo.getNote()); + emailParamsDTO.setSubscriptionType(subscriptionInfo.getType()); + emailParamsDTO.setStartDate(changeTimeStampFormat(orderByOrderNo.getCreateTime())); + emailParamsDTO.setNextPayDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + emailParamsDTO.setRenewalTime(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy)); + } + public void subscriptionReminder(){ // 提前7天的 00:00:00 和 23:59:59 LocalDateTime startOfDay = LocalDateTime.now().plusDays(7).toLocalDate().atStartOfDay(); @@ -876,4 +981,87 @@ public class StripeServiceImpl implements StripeService { } } + // todo 新建一个订阅 使用不会成功的付款方式 + + public String createSubscriptionTemp(String name, String email){ + Stripe.apiKey = privateKey; + try { + OrderInfo orderInfo = orderInfoService.createOrderByProductId(1, PayTypeEnum.STRIPE.getType(), ProductEnum.DailySubscription); + + String paymentMethodCode = "pm_card_mastercard"; + PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodCode); + + String customerId = getCustomer(name, email); + log.info("customerId: {}", customerId); + + PaymentMethodAttachParams attachParams = PaymentMethodAttachParams.builder() + .setCustomer(customerId) + .build(); + paymentMethod.attach(attachParams); + + // 设置默认付款方式 + Customer updatedCustomer = Customer.retrieve(customerId); + CustomerUpdateParams params = CustomerUpdateParams.builder() + .setInvoiceSettings( + CustomerUpdateParams.InvoiceSettings.builder() + .setDefaultPaymentMethod(paymentMethod.getId()) + .build() + ) + .build(); + updatedCustomer.update(params); + + // 3. 创建订阅 + SubscriptionCreateParams subscriptionParams = SubscriptionCreateParams.builder() + .setCustomer(customerId) + .addItem( + SubscriptionCreateParams.Item.builder() + .setPrice("price_1QFXkf02n1TEydyNtA4TQ3Yz") // 替换为实际的价格 ID + .build() + ) + .setDescription("AiDA - " + orderInfo.getOrderNo()) + .build(); + Subscription subscription = Subscription.create(subscriptionParams); + + // 打印订阅 ID + System.out.println("Subscription created: " + subscription.getId()); + return subscription.getId(); + } catch (StripeException e) { + throw new RuntimeException(e); + } + } + + public String changeCustomerPayment(String name, String email){ + Stripe.apiKey = privateKey; + String paymentMethodCode = "pm_card_chargeCustomerFail"; + try { + PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodCode); + + String customerId = getCustomer(name, email); + log.info("customerId: {}", customerId); + + // 附加支付方式到客户 + PaymentMethodAttachParams attachParams = PaymentMethodAttachParams.builder() + .setCustomer(customerId) + .build(); + paymentMethod.attach(attachParams); + // 更新客户的默认支付方式 + CustomerUpdateParams params = CustomerUpdateParams.builder() + .setInvoiceSettings( + CustomerUpdateParams.InvoiceSettings.builder() + .setDefaultPaymentMethod(paymentMethod.getId()) + .build() + ) + .build(); + + // 更新客户信息 + Customer customer = Customer.retrieve(customerId); + customer.update(params); + + return paymentMethod.getId(); + + } catch (StripeException e) { + throw new RuntimeException(e); + } + } + }