新增功能 -- 产品订阅 年度/月度
This commit is contained in:
@@ -75,5 +75,9 @@ public class CommonConstant {
|
|||||||
|
|
||||||
public static final String PORTFOLIO_DELETED_CN = "作品已删除";
|
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";
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/main/java/com/ai/da/common/enums/ProductEnum.java
Normal file
25
src/main/java/com/ai/da/common/enums/ProductEnum.java
Normal 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;
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
96
src/main/java/com/ai/da/common/task/PaymentTask.java
Normal file
96
src/main/java/com/ai/da/common/task/PaymentTask.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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.AliPayService;
|
||||||
|
import com.ai.da.service.OrderInfoService;
|
||||||
|
import com.ai.da.service.PayPalCheckoutService;
|
||||||
|
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 PaymentTask {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private OrderInfoService orderInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StripeService stripeService;
|
||||||
|
|
||||||
|
// @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,10 @@ package com.ai.da.common.utils;
|
|||||||
|
|
||||||
import com.ai.da.mapper.primary.entity.Account;
|
import com.ai.da.mapper.primary.entity.Account;
|
||||||
import com.ai.da.mapper.primary.entity.TrialOrder;
|
import com.ai.da.mapper.primary.entity.TrialOrder;
|
||||||
|
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.ai.da.common.config.exception.BusinessException;
|
import com.ai.da.common.config.exception.BusinessException;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.tencentcloudapi.common.Credential;
|
import com.tencentcloudapi.common.Credential;
|
||||||
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
||||||
import com.tencentcloudapi.common.profile.ClientProfile;
|
import com.tencentcloudapi.common.profile.ClientProfile;
|
||||||
@@ -767,4 +769,94 @@ public class SendEmailUtil {
|
|||||||
throw new BusinessException("failed.to.send.mail");
|
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;
|
||||||
|
|
||||||
|
public static void subscriptionEmailReminder(String type, SubscriptionEmailParamsDTO subscriptionEmailParamsDTO, String language, String receiverAddress){
|
||||||
|
try{
|
||||||
|
String kimEmail = "kimwong@code-create.com.hk";
|
||||||
|
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[]{kimEmail});
|
||||||
|
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 "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);
|
||||||
|
|
||||||
|
// 返回的resp是一个SendEmailResponse的实例,与请求对象对应
|
||||||
|
SendEmailResponse respUser = client.SendEmail(user);
|
||||||
|
// todo 暂时先不发商家邮件
|
||||||
|
// SendEmailResponse respMerchant = client.SendEmail(merchant);
|
||||||
|
log.info("邮件发送结果toUser###{}", SendEmailResponse.toJsonString(respUser));
|
||||||
|
// log.info("邮件发送结果toMerchant###{}", SendEmailResponse.toJsonString(respMerchant));
|
||||||
|
} catch (TencentCloudSDKException e) {
|
||||||
|
log.info("邮件发送失败###{}", e.toString());
|
||||||
|
throw new BusinessException("failed.to.send.mail");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
package com.ai.da.controller;
|
package com.ai.da.controller;
|
||||||
|
|
||||||
import com.ai.da.common.response.Response;
|
import com.ai.da.common.response.Response;
|
||||||
|
import com.ai.da.model.dto.ProductPurchaseDTO;
|
||||||
import com.ai.da.service.StripeService;
|
import com.ai.da.service.StripeService;
|
||||||
import com.paypal.http.HttpResponse;
|
import com.paypal.http.HttpResponse;
|
||||||
import com.paypal.payments.Refund;
|
import com.paypal.payments.Refund;
|
||||||
|
import com.stripe.exception.StripeException;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.simpleframework.xml.core.Validate;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@Api(tags = "Stripe模块")
|
@Api(tags = "Stripe模块")
|
||||||
@@ -24,19 +28,19 @@ public class StripeController {
|
|||||||
private StripeService stripeService;
|
private StripeService stripeService;
|
||||||
|
|
||||||
@ApiOperation("创建支付链接")
|
@ApiOperation("创建支付链接")
|
||||||
@PostMapping("/createOrder/{amount}")
|
@PostMapping("/createOrder")
|
||||||
public Response<String> pay(@PathVariable Integer amount, @RequestParam String returnUrl) {
|
public Response<String> pay(@Validate @RequestBody ProductPurchaseDTO productPurchaseDTO) {
|
||||||
return Response.success(stripeService.pay(amount, returnUrl));
|
return Response.success(stripeService.pay(productPurchaseDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation("支付通知")
|
@ApiOperation("支付通知")
|
||||||
@PostMapping("/trade/notify")
|
@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);
|
Boolean result = stripeService.notify(request);
|
||||||
if (result){
|
if (result){
|
||||||
return Response.success(200,"success");
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
}else {
|
}else {
|
||||||
return Response.fail(400,"failure");
|
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,4 +55,20 @@ public class StripeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation("获取订阅")
|
||||||
|
@PostMapping("/getSubscription")
|
||||||
|
public void getSubscription() {
|
||||||
|
try {
|
||||||
|
stripeService.getSubscription("xp", "xupei3360@163.com");
|
||||||
|
} catch (StripeException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("取消订阅")
|
||||||
|
@PostMapping("/cancelSubscription")
|
||||||
|
public Response<String> cancelSubscription(@RequestParam String subscriptionId) {
|
||||||
|
stripeService.cancelSubscription(subscriptionId);
|
||||||
|
return Response.success("success");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ package com.ai.da.mapper.primary.entity;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Data
|
@Data
|
||||||
@TableName("t_payment_info")
|
@TableName("t_payment_info")
|
||||||
public class PaymentInfo extends BaseEntity{
|
public class PaymentInfo extends BaseEntity{
|
||||||
@@ -13,11 +15,15 @@ public class PaymentInfo extends BaseEntity{
|
|||||||
|
|
||||||
private String paymentType;//支付类型
|
private String paymentType;//支付类型
|
||||||
|
|
||||||
private String tradeType;//交易类型
|
|
||||||
|
|
||||||
private String tradeState;//交易状态
|
private String tradeState;//交易状态
|
||||||
|
|
||||||
private Float payerTotal;//支付金额(元)
|
private Float payerTotal;//支付金额(元)
|
||||||
|
|
||||||
private String content;//通知参数
|
private String content;//通知参数
|
||||||
|
|
||||||
|
// 支付类型 new || renewal
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
// 当前支付是否已邮件通知 0 || 1
|
||||||
|
private Integer notified;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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 autoRenewal = (byte)1;
|
||||||
|
|
||||||
|
// 支付方式
|
||||||
|
private String paymentMethod;
|
||||||
|
|
||||||
|
// 如果是用卡支付,可以看到银行卡最后四位
|
||||||
|
private String last4;
|
||||||
|
|
||||||
|
// 续订的下一个付款日
|
||||||
|
private String nextPayDate;
|
||||||
|
|
||||||
|
// 当前订阅订单有效期开始时间
|
||||||
|
private Long currentPeriodStart;
|
||||||
|
|
||||||
|
// 当前订阅订单有效期结束时间
|
||||||
|
private Long currentPeriodEnd;
|
||||||
|
|
||||||
|
}
|
||||||
32
src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java
Normal file
32
src/main/java/com/ai/da/model/dto/ProductPurchaseDTO.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
private String refId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// 订阅Id
|
||||||
|
private String subscriptionId;
|
||||||
|
|
||||||
|
// 订阅方式
|
||||||
|
private String subscriptionType;
|
||||||
|
|
||||||
|
// 订阅开始时间
|
||||||
|
private String startDate;
|
||||||
|
|
||||||
|
// 下一个支付日期
|
||||||
|
private String nextPayDate;
|
||||||
|
|
||||||
|
// 下次付款时间(reminder)
|
||||||
|
private String renewalTime;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.ai.da.service;
|
|||||||
|
|
||||||
|
|
||||||
import com.ai.da.common.enums.OrderStatusEnum;
|
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.response.PageBaseResponse;
|
||||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
import com.ai.da.mapper.primary.entity.OrderInfo;
|
||||||
import com.ai.da.model.dto.QueryPageByTimeDTO;
|
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 productId, String paymentType);
|
||||||
|
|
||||||
|
OrderInfo createOrderByProductId(Integer amount, String paymentType, ProductEnum product);
|
||||||
|
|
||||||
void saveCodeUrl(String orderNo, String codeUrl);
|
void saveCodeUrl(String orderNo, String codeUrl);
|
||||||
|
|
||||||
List<OrderInfo> listOrderByCreateTimeDesc();
|
List<OrderInfo> listOrderByCreateTimeDesc();
|
||||||
@@ -28,4 +31,7 @@ public interface OrderInfoService extends IService<OrderInfo> {
|
|||||||
PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO);
|
PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO);
|
||||||
|
|
||||||
void updateOrderNoById(Long id, String orderNo);
|
void updateOrderNoById(Long id, String orderNo);
|
||||||
|
|
||||||
|
void updateTotalFeeByOrderNo(String orderNo);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package com.ai.da.service;
|
|||||||
|
|
||||||
import com.ai.da.mapper.primary.entity.PaymentInfo;
|
import com.ai.da.mapper.primary.entity.PaymentInfo;
|
||||||
import com.ai.da.model.dto.AlipayHKCallbackDTO;
|
import com.ai.da.model.dto.AlipayHKCallbackDTO;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.paypal.orders.Order;
|
import com.paypal.orders.Order;
|
||||||
import com.stripe.model.checkout.Session;
|
import com.stripe.model.Invoice;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface PaymentInfoService {
|
public interface PaymentInfoService extends IService<PaymentInfo> {
|
||||||
|
|
||||||
void createPaymentInfo(String plainText);
|
void createPaymentInfo(String plainText);
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ public interface PaymentInfoService {
|
|||||||
|
|
||||||
void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO);
|
void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO);
|
||||||
|
|
||||||
void createPaymentInfoForStripe(Session session);
|
String createPaymentInfoForStripe(Invoice invoice);
|
||||||
|
|
||||||
PaymentInfo getPaymentInfoByOrderId(String orderId);
|
PaymentInfo getPaymentInfoByOrderId(String orderId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,35 @@
|
|||||||
package com.ai.da.service;
|
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 javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public interface StripeService {
|
public interface StripeService {
|
||||||
|
|
||||||
String pay(Integer quantity, String returnUrl);
|
String pay(ProductPurchaseDTO productPurchaseDTO);
|
||||||
|
|
||||||
Boolean notify(HttpServletRequest request);
|
Boolean notify(HttpServletRequest request);
|
||||||
|
|
||||||
String refund(String amount, String orderId, String reason);
|
String refund(String amount, String orderId, String reason);
|
||||||
|
|
||||||
void checkOrderStatus(String orderNo);
|
void checkOrderStatus(String orderNo);
|
||||||
|
|
||||||
|
List<Subscription> getSubscription(String name, String userEmail) throws StripeException;
|
||||||
|
|
||||||
|
void cancelSubscription(String orderNo);
|
||||||
|
|
||||||
|
Map<String, String> getPaymentMethod(String paymentMethodId);
|
||||||
|
|
||||||
|
/*void updateSubscription(String subscriptionId);
|
||||||
|
|
||||||
|
void resume(String subscriptionId);*/
|
||||||
|
|
||||||
|
void subscriptionReminder();
|
||||||
|
|
||||||
|
void checkSubscriptionExpiration();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ package com.ai.da.service.impl;
|
|||||||
import com.ai.da.common.context.UserContext;
|
import com.ai.da.common.context.UserContext;
|
||||||
import com.ai.da.common.enums.CreditsEventsEnum;
|
import com.ai.da.common.enums.CreditsEventsEnum;
|
||||||
import com.ai.da.common.enums.OrderStatusEnum;
|
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.response.PageBaseResponse;
|
||||||
import com.ai.da.common.utils.OrderNoUtils;
|
import com.ai.da.common.utils.OrderNoUtils;
|
||||||
import com.ai.da.mapper.primary.OrderInfoMapper;
|
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.ProductMapper;
|
||||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
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.dto.QueryPageByTimeDTO;
|
||||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||||
import com.ai.da.service.OrderInfoService;
|
import com.ai.da.service.OrderInfoService;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
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.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
@@ -34,9 +38,13 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
@Resource
|
@Resource
|
||||||
private ProductMapper productMapper;
|
private ProductMapper productMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PaymentInfoMapper paymentInfoMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OrderInfo createOrderByProductId(Integer amount, String paymentType) {
|
public OrderInfo createOrderByProductId(Integer amount, String paymentType) {
|
||||||
|
|
||||||
|
|
||||||
//查找已存在但未支付的订单
|
//查找已存在但未支付的订单
|
||||||
/*OrderInfo orderInfo = this.getNoPayOrderByProductId(amount, paymentType);
|
/*OrderInfo orderInfo = this.getNoPayOrderByProductId(amount, paymentType);
|
||||||
if( orderInfo != null){
|
if( orderInfo != null){
|
||||||
@@ -51,7 +59,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
//生成订单
|
//生成订单
|
||||||
OrderInfo orderInfo = new OrderInfo();
|
OrderInfo orderInfo = new OrderInfo();
|
||||||
orderInfo.setAccountId(accountId);
|
orderInfo.setAccountId(accountId);
|
||||||
orderInfo.setTitle("积分购买 X" + amount );
|
orderInfo.setTitle("积分购买 X" + amount);
|
||||||
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号 ??
|
orderInfo.setOrderNo(OrderNoUtils.getOrderNo()); //订单号 ??
|
||||||
// orderInfo.setProductId(amount);
|
// orderInfo.setProductId(amount);
|
||||||
// orderInfo.setTotalFee(Integer.parseInt(CreditsEventsEnum.PRICE.getValue()) * amount); // 元 HKD
|
// orderInfo.setTotalFee(Integer.parseInt(CreditsEventsEnum.PRICE.getValue()) * amount); // 元 HKD
|
||||||
@@ -63,8 +71,37 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
return 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 orderNo
|
||||||
* @param codeUrl
|
* @param codeUrl
|
||||||
*/
|
*/
|
||||||
@@ -82,6 +119,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询订单列表,并倒序查询
|
* 查询订单列表,并倒序查询
|
||||||
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@@ -93,6 +131,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据订单号更新订单状态
|
* 根据订单号更新订单状态
|
||||||
|
*
|
||||||
* @param orderNo
|
* @param orderNo
|
||||||
* @param orderStatus
|
* @param orderStatus
|
||||||
*/
|
*/
|
||||||
@@ -112,6 +151,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据订单号获取订单状态
|
* 根据订单号获取订单状态
|
||||||
|
*
|
||||||
* @param orderNo
|
* @param orderNo
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -121,7 +161,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("order_no", orderNo);
|
queryWrapper.eq("order_no", orderNo);
|
||||||
OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
|
OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
|
||||||
if(orderInfo == null){
|
if (orderInfo == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return orderInfo.getOrderStatus();
|
return orderInfo.getOrderStatus();
|
||||||
@@ -129,6 +169,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询创建超过minutes分钟并且未支付的订单
|
* 查询创建超过minutes分钟并且未支付的订单
|
||||||
|
*
|
||||||
* @param minutes
|
* @param minutes
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -149,6 +190,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据订单号获取订单
|
* 根据订单号获取订单
|
||||||
|
*
|
||||||
* @param orderNo
|
* @param orderNo
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -166,6 +208,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
/**
|
/**
|
||||||
* 根据商品id查询未支付订单
|
* 根据商品id查询未支付订单
|
||||||
* 防止重复创建订单对象
|
* 防止重复创建订单对象
|
||||||
|
*
|
||||||
* @param productId
|
* @param productId
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@@ -181,16 +224,16 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO){
|
public PageBaseResponse<OrderInfo> getOrderByPage(QueryPageByTimeDTO queryPageByTimeDTO) {
|
||||||
QueryWrapper<OrderInfo> qw = new QueryWrapper<>();
|
QueryWrapper<OrderInfo> qw = new QueryWrapper<>();
|
||||||
qw.eq("account_id",UserContext.getUserHolder().getId());
|
qw.eq("account_id", UserContext.getUserHolder().getId());
|
||||||
|
|
||||||
String startTime = queryPageByTimeDTO.getStartTime();
|
String startTime = queryPageByTimeDTO.getStartTime();
|
||||||
String endTime = queryPageByTimeDTO.getEndTime();
|
String endTime = queryPageByTimeDTO.getEndTime();
|
||||||
if (StringUtil.isNullOrEmpty(startTime)){
|
if (StringUtil.isNullOrEmpty(startTime)) {
|
||||||
startTime = "2024-02-01 00:00:00";
|
startTime = "2024-02-01 00:00:00";
|
||||||
}
|
}
|
||||||
if (StringUtil.isNullOrEmpty(endTime)){
|
if (StringUtil.isNullOrEmpty(endTime)) {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
endTime = now.format(dateTimeFormatter);
|
endTime = now.format(dateTimeFormatter);
|
||||||
@@ -206,11 +249,28 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
|
|||||||
return PageBaseResponse.success(orderInfo);
|
return PageBaseResponse.success(orderInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateOrderNoById(Long id, String orderNo){
|
public void updateOrderNoById(Long id, String orderNo) {
|
||||||
OrderInfo orderInfo = new OrderInfo();
|
OrderInfo orderInfo = new OrderInfo();
|
||||||
orderInfo.setId(id);
|
orderInfo.setId(id);
|
||||||
orderInfo.setOrderNo(orderNo);
|
orderInfo.setOrderNo(orderNo);
|
||||||
|
|
||||||
baseMapper.updateById(orderInfo);
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,20 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.paypal.orders.Order;
|
import com.paypal.orders.Order;
|
||||||
|
import com.stripe.Stripe;
|
||||||
|
import com.stripe.exception.StripeException;
|
||||||
|
import com.stripe.model.Invoice;
|
||||||
|
import com.stripe.model.Subscription;
|
||||||
import com.stripe.model.checkout.Session;
|
import com.stripe.model.checkout.Session;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -50,7 +56,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
paymentInfo.setOrderNo(orderNo);
|
paymentInfo.setOrderNo(orderNo);
|
||||||
paymentInfo.setPaymentType(PayTypeEnum.WXPAY.getType());
|
paymentInfo.setPaymentType(PayTypeEnum.WXPAY.getType());
|
||||||
paymentInfo.setTransactionId(transactionId);
|
paymentInfo.setTransactionId(transactionId);
|
||||||
paymentInfo.setTradeType(tradeType);
|
|
||||||
paymentInfo.setTradeState(tradeState);
|
paymentInfo.setTradeState(tradeState);
|
||||||
// 原来的单位是:分 Int 现改为:元 Float
|
// 原来的单位是:分 Int 现改为:元 Float
|
||||||
paymentInfo.setPayerTotal(payerTotal / 100.0F);
|
paymentInfo.setPayerTotal(payerTotal / 100.0F);
|
||||||
@@ -83,7 +88,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
paymentInfo.setOrderNo(orderNo);
|
paymentInfo.setOrderNo(orderNo);
|
||||||
paymentInfo.setPaymentType(PayTypeEnum.ALIPAY.getType());
|
paymentInfo.setPaymentType(PayTypeEnum.ALIPAY.getType());
|
||||||
paymentInfo.setTransactionId(transactionId);
|
paymentInfo.setTransactionId(transactionId);
|
||||||
paymentInfo.setTradeType("电脑网站支付");
|
|
||||||
paymentInfo.setTradeState(tradeStatus);
|
paymentInfo.setTradeState(tradeStatus);
|
||||||
// 原来的单位是分 Int 现改为元 Long
|
// 原来的单位是分 Int 现改为元 Long
|
||||||
paymentInfo.setPayerTotal(totalAmountInt / 100.0F);
|
paymentInfo.setPayerTotal(totalAmountInt / 100.0F);
|
||||||
@@ -107,7 +111,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
paymentInfo.setOrderNo(order.id());
|
paymentInfo.setOrderNo(order.id());
|
||||||
paymentInfo.setPaymentType(PayTypeEnum.PAYPAL.getType());
|
paymentInfo.setPaymentType(PayTypeEnum.PAYPAL.getType());
|
||||||
paymentInfo.setTransactionId(order.id());
|
paymentInfo.setTransactionId(order.id());
|
||||||
paymentInfo.setTradeType("电脑网站支付");
|
|
||||||
paymentInfo.setTradeState(order.status());
|
paymentInfo.setTradeState(order.status());
|
||||||
// todo 确认这里的数据单位是不是元
|
// todo 确认这里的数据单位是不是元
|
||||||
paymentInfo.setPayerTotal(totalAmountFloat);
|
paymentInfo.setPayerTotal(totalAmountFloat);
|
||||||
@@ -139,7 +142,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
paymentInfo.setOrderNo(orderNo);
|
paymentInfo.setOrderNo(orderNo);
|
||||||
paymentInfo.setPaymentType(PayTypeEnum.ALIPAY_HK.getType());
|
paymentInfo.setPaymentType(PayTypeEnum.ALIPAY_HK.getType());
|
||||||
paymentInfo.setTransactionId(transactionId);
|
paymentInfo.setTransactionId(transactionId);
|
||||||
paymentInfo.setTradeType("电脑网站支付");
|
|
||||||
paymentInfo.setTradeState(tradeStatus);
|
paymentInfo.setTradeState(tradeStatus);
|
||||||
paymentInfo.setPayerTotal(totalAmountFloat);
|
paymentInfo.setPayerTotal(totalAmountFloat);
|
||||||
|
|
||||||
@@ -150,10 +152,10 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
baseMapper.insert(paymentInfo);
|
baseMapper.insert(paymentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void createPaymentInfoForStripe(Session session){
|
public void createPaymentInfoForStripe(Session session){
|
||||||
String orderId = session.getMetadata().get("orderId");
|
String orderId = session.getMetadata().get("orderId");
|
||||||
String status = session.getStatus();
|
String status = session.getStatus();
|
||||||
|
// 获取transactionId,从sessionId更改为invoiceId
|
||||||
String sessionId = session.getId();
|
String sessionId = session.getId();
|
||||||
Long amountTotal = session.getAmountTotal();
|
Long amountTotal = session.getAmountTotal();
|
||||||
// stripe 的支付金额单位是分
|
// stripe 的支付金额单位是分
|
||||||
@@ -163,7 +165,6 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
paymentInfo.setOrderNo(orderId);
|
paymentInfo.setOrderNo(orderId);
|
||||||
paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
|
paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
|
||||||
paymentInfo.setTransactionId(sessionId);
|
paymentInfo.setTransactionId(sessionId);
|
||||||
paymentInfo.setTradeType("电脑网站支付");
|
|
||||||
paymentInfo.setTradeState(status);
|
paymentInfo.setTradeState(status);
|
||||||
paymentInfo.setPayerTotal(divide);
|
paymentInfo.setPayerTotal(divide);
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
@@ -173,6 +174,54 @@ public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, Payme
|
|||||||
baseMapper.insert(paymentInfo);
|
baseMapper.insert(paymentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Value("${stripe.private-key}")
|
||||||
|
private String privateKey;
|
||||||
|
public String createPaymentInfoForStripe(Invoice invoice){
|
||||||
|
Stripe.apiKey = privateKey;
|
||||||
|
// 获取transactionId,从sessionId更改为invoiceId
|
||||||
|
String invoiceId = invoice.getId();
|
||||||
|
|
||||||
|
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
|
||||||
|
qw.eq("transaction_id", invoiceId);
|
||||||
|
PaymentInfo paymentInfo = baseMapper.selectOne(qw);
|
||||||
|
// 判断当前支付是否已经被记录,确保同一个支付不会被重复记录
|
||||||
|
if (Objects.isNull(paymentInfo)){
|
||||||
|
String orderNo;
|
||||||
|
String subscriptionId = invoice.getSubscription();
|
||||||
|
try {
|
||||||
|
// 从subscription中获取orderNo
|
||||||
|
orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", "");
|
||||||
|
} catch (StripeException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
String status = invoice.getStatus();
|
||||||
|
Long amountTotal = invoice.getAmountPaid();
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
baseMapper.insert(paymentInfo);
|
||||||
|
return orderNo;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentInfo getPaymentInfoByOrderId(String orderId){
|
public PaymentInfo getPaymentInfoByOrderId(String orderId){
|
||||||
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
|
QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
package com.ai.da.service.impl;
|
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.context.UserContext;
|
||||||
import com.ai.da.common.enums.AliPayTradeStateEnum;
|
import com.ai.da.common.enums.*;
|
||||||
import com.ai.da.common.enums.CreditsEventsEnum;
|
import com.ai.da.common.utils.SendEmailUtil;
|
||||||
import com.ai.da.common.enums.OrderStatusEnum;
|
|
||||||
import com.ai.da.common.enums.PayTypeEnum;
|
|
||||||
import com.ai.da.common.utils.OrderNoUtils;
|
|
||||||
import com.ai.da.mapper.primary.AccountMapper;
|
import com.ai.da.mapper.primary.AccountMapper;
|
||||||
|
import com.ai.da.mapper.primary.PaymentInfoMapper;
|
||||||
|
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
|
||||||
import com.ai.da.mapper.primary.entity.OrderInfo;
|
import com.ai.da.mapper.primary.entity.OrderInfo;
|
||||||
import com.ai.da.mapper.primary.entity.PaymentInfo;
|
import com.ai.da.mapper.primary.entity.PaymentInfo;
|
||||||
import com.ai.da.mapper.primary.entity.RefundInfo;
|
import com.ai.da.mapper.primary.entity.RefundInfo;
|
||||||
|
import com.ai.da.mapper.primary.entity.SubscriptionInfo;
|
||||||
|
import com.ai.da.model.dto.ProductPurchaseDTO;
|
||||||
|
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
|
||||||
import com.ai.da.service.*;
|
import com.ai.da.service.*;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.stripe.Stripe;
|
import com.stripe.Stripe;
|
||||||
@@ -21,6 +27,7 @@ import com.stripe.model.checkout.Session;
|
|||||||
import com.stripe.net.Webhook;
|
import com.stripe.net.Webhook;
|
||||||
import com.stripe.param.*;
|
import com.stripe.param.*;
|
||||||
import com.stripe.param.checkout.SessionCreateParams;
|
import com.stripe.param.checkout.SessionCreateParams;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -30,10 +37,13 @@ import javax.annotation.Resource;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.HashMap;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Map;
|
import java.time.Instant;
|
||||||
import java.util.Objects;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@SuppressWarnings("LoggingSimilarMessage")
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class StripeServiceImpl implements StripeService {
|
public class StripeServiceImpl implements StripeService {
|
||||||
@@ -51,6 +61,10 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private AccountMapper accountMapper;
|
private AccountMapper accountMapper;
|
||||||
|
@Resource
|
||||||
|
private SubscriptionInfoMapper subscriptionInfoMapper;
|
||||||
|
@Resource
|
||||||
|
private PaymentInfoMapper paymentInfoMapper;
|
||||||
|
|
||||||
@Value("${stripe.private-key}")
|
@Value("${stripe.private-key}")
|
||||||
private String privateKey;
|
private String privateKey;
|
||||||
@@ -60,53 +74,78 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public String pay(Integer quantity, String returnUrl) {
|
public String pay(ProductPurchaseDTO productPurchaseDTO) {
|
||||||
Stripe.apiKey = privateKey;
|
Stripe.apiKey = privateKey;
|
||||||
log.info("生成订单");
|
|
||||||
OrderInfo orderInfo = orderInfoService.createOrderByProductId(quantity, PayTypeEnum.STRIPE.getType());
|
|
||||||
|
|
||||||
Long id = UserContext.getUserHolder().getId();
|
ProductEnum productEnum;
|
||||||
com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(id);
|
switch (productPurchaseDTO.getProductName()){
|
||||||
|
case "Credits Purchase":
|
||||||
|
productEnum = ProductEnum.CreditsProduct;
|
||||||
|
break;
|
||||||
|
case "Subscription":
|
||||||
|
switch (productPurchaseDTO.getSubscribeType()){
|
||||||
|
case "Month":
|
||||||
|
productEnum = ProductEnum.MonthlySubscription;
|
||||||
|
break;
|
||||||
|
case "Year":
|
||||||
|
productEnum = ProductEnum.AnnualSubscription;
|
||||||
|
break;
|
||||||
|
case "Day":
|
||||||
|
productEnum = ProductEnum.DailySubscription;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new BusinessException("unknown subscription type");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new BusinessException("unknown product type");
|
||||||
|
}
|
||||||
|
log.info("生成订单");
|
||||||
|
OrderInfo orderInfo = orderInfoService.createOrderByProductId(productPurchaseDTO.getQuantity(), PayTypeEnum.STRIPE.getType(), productEnum);
|
||||||
|
String payType;
|
||||||
|
if (productPurchaseDTO.getAutoRenewal()){
|
||||||
|
payType = "recurring";
|
||||||
|
}else {
|
||||||
|
payType = "one_time";
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//创建产品
|
Long id = UserContext.getUserHolder().getId();
|
||||||
Map<String, Object> params = new HashMap<>();
|
com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(id);
|
||||||
params.put("name", "AiDA credits purchase");
|
// 获取或创建产品
|
||||||
Product product = Product.create(params);
|
String productId = getProduct(productEnum.getName());
|
||||||
String orderId = OrderNoUtils.getOrderNo();
|
// 获取或创建价格
|
||||||
|
String priceId = getPrice(productEnum.getPrice(), productId, payType, productPurchaseDTO.getSubscribeType());
|
||||||
BigDecimal actualAmount = new BigDecimal(Long.parseLong(CreditsEventsEnum.PRICE.getValue()) * 100); //stripe的默认单位是分,即传入的amount实际上小数点会被左移两位
|
// 获取或创建customer
|
||||||
//给price绑定元数据并更新price用于检索
|
String customerId = getCustomer(account.getUserName(), account.getUserEmail());
|
||||||
Map<String, Object> metadata = new HashMap<>();
|
// 获取自定义订单号
|
||||||
metadata.put("orderId", orderId);
|
String orderId = orderInfo.getOrderNo();
|
||||||
//创建价格
|
|
||||||
Map<String, Object> priceParams = new HashMap<>();
|
|
||||||
priceParams.put("metadata", metadata); //通过订单号关联用于检索price信息(可选)
|
|
||||||
priceParams.put("unit_amount", actualAmount.intValue());
|
|
||||||
priceParams.put("currency", "HKD");
|
|
||||||
priceParams.put("product", product.getId());
|
|
||||||
Price price = Price.create(priceParams);
|
|
||||||
|
|
||||||
//创建支付信息得到url
|
//创建支付信息得到url
|
||||||
SessionCreateParams params3 = SessionCreateParams.builder()
|
// 一次性支付和周期扣款,需要区分mode: payment || subscription || setup
|
||||||
.setMode(SessionCreateParams.Mode.PAYMENT)
|
SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder();
|
||||||
.setSuccessUrl(returnUrl)//可自定义成功页面
|
if (payType.equals("recurring")){
|
||||||
.setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setDescription("AiDA - " + orderId).build())
|
sessionBuilder.setMode(SessionCreateParams.Mode.SUBSCRIPTION);
|
||||||
.setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN)
|
sessionBuilder.setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setDescription("AiDA - " + orderId).build());
|
||||||
.addLineItem(
|
}else {
|
||||||
SessionCreateParams.LineItem.builder()
|
sessionBuilder.setMode(SessionCreateParams.Mode.PAYMENT);
|
||||||
.setQuantity(Long.valueOf(quantity))
|
sessionBuilder.setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setDescription("AiDA - " + orderId).build());
|
||||||
.setPrice(price.getId())
|
}
|
||||||
.build())
|
sessionBuilder.setCustomer(customerId);
|
||||||
.putMetadata("orderId", orderId) //通过订单号关联用于检索支付信息(可选)
|
sessionBuilder.setSuccessUrl(productPurchaseDTO.getReturnUrl());//可自定义成功页面
|
||||||
.build();
|
sessionBuilder.setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN);
|
||||||
Session session = Session.create(params3);
|
sessionBuilder.addLineItem(
|
||||||
|
SessionCreateParams.LineItem.builder()
|
||||||
|
.setQuantity((long) productPurchaseDTO.getQuantity())
|
||||||
|
.setPrice(priceId)
|
||||||
|
.build());
|
||||||
|
sessionBuilder.putMetadata("orderId", orderId); //通过订单号关联用于检索支付信息(可选)
|
||||||
|
|
||||||
|
Session session = Session.create(sessionBuilder.build());
|
||||||
log.info("sessionId:" + session.getId()); //退款方式1:拿到sessionId入库,退款的时候根据这个id找到PaymentIntent的id然后发起退款
|
log.info("sessionId:" + session.getId()); //退款方式1:拿到sessionId入库,退款的时候根据这个id找到PaymentIntent的id然后发起退款
|
||||||
|
|
||||||
// 更新order信息
|
// 更新order信息
|
||||||
orderInfoService.updateOrderNoById(orderInfo.getId(), orderId);
|
orderInfoService.updateOrderNoById(orderInfo.getId(), orderId);
|
||||||
//记录支付日志
|
|
||||||
paymentInfoService.createPaymentInfoForStripe(session);
|
|
||||||
return session.getUrl();
|
return session.getUrl();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("创建支付会话出现异常:", e);
|
log.error("创建支付会话出现异常:", e);
|
||||||
@@ -114,6 +153,69 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取产品ID
|
||||||
|
private String getProduct(String productName) throws StripeException {
|
||||||
|
Stripe.apiKey = privateKey;
|
||||||
|
// 1、获取所有的产品
|
||||||
|
ProductCollection productCollection = Product.list(ProductListParams.builder().setActive(Boolean.TRUE).build());
|
||||||
|
// 2、取一个指定名称的产品
|
||||||
|
for (Product product : productCollection.getData()) {
|
||||||
|
if (product.getName().equals(productName)) {
|
||||||
|
return product.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3、在现有产品中没有找到指定产品,新建产品
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("name", productName);
|
||||||
|
Product product = Product.create(params);
|
||||||
|
return product.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取价格
|
||||||
|
*
|
||||||
|
* @param priceValue 价格值
|
||||||
|
* @param payType recurring || one_time
|
||||||
|
* @param recurringType monthly || yearly
|
||||||
|
*/
|
||||||
|
private String getPrice(Long priceValue, String productId, String payType, String recurringType) throws StripeException {
|
||||||
|
Stripe.apiKey = privateKey;
|
||||||
|
PriceCollection priceCollection = Price.list(PriceListParams.builder()
|
||||||
|
.setActive(Boolean.TRUE)
|
||||||
|
.setProduct(productId).build());
|
||||||
|
for (Price price : priceCollection.getData()) {
|
||||||
|
// stripe的金额单位为分,所以这里需要 ×100
|
||||||
|
if (price.getUnitAmount().equals(priceValue * 100) && price.getType().equals(payType)) {
|
||||||
|
return price.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Price price = createPrice(priceValue, productId, payType, recurringType);
|
||||||
|
return price.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Price createPrice(Long priceValue, String productId, String payType, String recurringType) throws StripeException {
|
||||||
|
BigDecimal actualAmount = new BigDecimal(priceValue * 100); //stripe的默认单位是分,即传入的amount实际上小数点会被左移两位
|
||||||
|
|
||||||
|
PriceCreateParams.Builder priceCreateParams = new PriceCreateParams.Builder();
|
||||||
|
priceCreateParams.setBillingScheme(PriceCreateParams.BillingScheme.PER_UNIT);
|
||||||
|
priceCreateParams.setCurrency("HKD");
|
||||||
|
priceCreateParams.setProduct(productId);
|
||||||
|
priceCreateParams.setUnitAmount(actualAmount.longValue());
|
||||||
|
|
||||||
|
if (payType.equals("recurring")) {
|
||||||
|
PriceCreateParams.Recurring.Builder recurring = new PriceCreateParams.Recurring.Builder();
|
||||||
|
// One of day, week, month or year.
|
||||||
|
recurring.setInterval(PriceCreateParams.Recurring.Interval.valueOf(recurringType.toUpperCase()));
|
||||||
|
// The number of intervals (specified in the interval attribute) between subscription billings.
|
||||||
|
// For example, interval=month and interval_count=3 bills every 3 months.
|
||||||
|
recurring.setIntervalCount(1L);
|
||||||
|
priceCreateParams.setRecurring(recurring.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Price.create(priceCreateParams.build());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Boolean notify(HttpServletRequest request) {
|
public Boolean notify(HttpServletRequest request) {
|
||||||
@@ -127,19 +229,19 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("stripe 支付回调参数解析异常:errorMsg {}", e.getMessage());
|
log.info("stripe 支付回调参数解析异常:errorMsg {}", e.getMessage());
|
||||||
log.info("request sigHeader = {}", sigHeader);
|
log.info("request sigHeader = {}", sigHeader);
|
||||||
log.info("request body = {}", payload);
|
log.info("request body = {}", JSON.toJSONString(payload));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
Event event = null;
|
Event event;
|
||||||
try {
|
try {
|
||||||
assert sigHeader != null;
|
assert sigHeader != null;
|
||||||
event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
|
event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
|
||||||
} catch (SignatureVerificationException e) {
|
} catch (SignatureVerificationException e) {
|
||||||
log.info("stripe 验签,获取事件异常, errorMsg=" + e.getMessage());
|
log.info("stripe 验签,获取事件异常, errorMsg={}", e.getMessage());
|
||||||
log.info("request sigHeader = {}", sigHeader);
|
log.info("request sigHeader = {}", sigHeader);
|
||||||
log.info("request body = {}", payload);
|
log.info("request body = {}", JSON.toJSONString(payload));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
@@ -148,23 +250,65 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
// Deserialize the nested object inside the event
|
// Deserialize the nested object inside the event
|
||||||
assert event != null;
|
assert event != null;
|
||||||
EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
|
EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
|
||||||
StripeObject stripeObject = null;
|
StripeObject stripeObject ;
|
||||||
if (dataObjectDeserializer.getObject().isPresent()) {
|
if (dataObjectDeserializer.getObject().isPresent()) {
|
||||||
stripeObject = dataObjectDeserializer.getObject().get();
|
stripeObject = dataObjectDeserializer.getObject().get();
|
||||||
} else {
|
} else {
|
||||||
log.info("stripe 验签失败!");
|
log.info("stripe 验签失败!");
|
||||||
log.info("request sigHeader = {}", sigHeader);
|
log.info("request sigHeader = {}", sigHeader);
|
||||||
log.info("request body = {}", payload);
|
log.info("request body = {}", JSON.toJSONString(payload));
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
}
|
}
|
||||||
log.info("stripe验签成功");
|
log.info("stripe验签成功");
|
||||||
|
Boolean response = Boolean.TRUE;
|
||||||
|
|
||||||
if (event.getType().equals("checkout.session.completed")) {
|
log.info("回调事件 {}", event.getType());
|
||||||
|
if (stripeObject instanceof Session){
|
||||||
Session session = (Session) stripeObject;
|
Session session = (Session) stripeObject;
|
||||||
processOrder(session);
|
if (event.getType().equals("checkout.session.completed")) {
|
||||||
}
|
processOrder(session);
|
||||||
|
}
|
||||||
|
} else if (stripeObject instanceof Subscription){
|
||||||
|
Subscription subscription = (Subscription) stripeObject;
|
||||||
|
if (event.getType().equals("customer.subscription.created")){
|
||||||
|
// 添加数据到t_subscription_info表 需记录订阅id。需要判断订阅的状态是否active吗 ??
|
||||||
|
createSubscription(subscription);
|
||||||
|
log.info("创建连续订阅");
|
||||||
|
} else if (event.getType().equals("customer.subscription.updated")){
|
||||||
|
// 更新订阅信息
|
||||||
|
response = updateSubscription(subscription);
|
||||||
|
log.info("订阅更新");
|
||||||
|
if (subscription.getStatus().equals("active")){
|
||||||
|
response = sendEmail(subscription.getId(), null);
|
||||||
|
}
|
||||||
|
} else if (event.getType().equals("customer.subscription.deleted")){
|
||||||
|
response = updateSubscription(subscription);
|
||||||
|
log.info("用户取消连续订阅");
|
||||||
|
} else if (event.getType().equals("customer.subscription.paused")){
|
||||||
|
updateSubscription(subscription);
|
||||||
|
} else if (event.getType().equals("customer.subscription.resumed")){
|
||||||
|
updateSubscription(subscription);
|
||||||
|
log.info("用户订阅恢复");
|
||||||
|
}
|
||||||
|
} else if (stripeObject instanceof Invoice) {
|
||||||
|
Invoice invoice = (Invoice) stripeObject;
|
||||||
|
if (event.getType().equals("invoice.paid")) {
|
||||||
|
// 新增支付成功的信息,返回orderNo,表示,该回调第一次被记录
|
||||||
|
String orderNo = paymentInfoService.createPaymentInfoForStripe(invoice);
|
||||||
|
|
||||||
return Boolean.TRUE;
|
if (!StringUtil.isNullOrEmpty(orderNo)) {
|
||||||
|
// 更新t_order_info中的total_fee,记录该订单的累计付款金额
|
||||||
|
orderInfoService.updateTotalFeeByOrderNo(orderNo);
|
||||||
|
// 邮件通知商家和用户
|
||||||
|
if (invoice.getBillingReason().equals("subscription_create")){
|
||||||
|
response = sendEmail(invoice.getSubscription(), "new");
|
||||||
|
} else if (invoice.getBillingReason().equals("subscription_cycle")){
|
||||||
|
response = sendEmail(invoice.getSubscription(), "renewal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processOrder(Session session) {
|
public void processOrder(Session session) {
|
||||||
@@ -186,31 +330,135 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS);
|
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.SUCCESS);
|
||||||
log.info("Stripe 订单:{} 状态更新成功", orderId);
|
log.info("Stripe 订单:{} 状态更新成功", orderId);
|
||||||
|
|
||||||
// 更新支付日志
|
if (orderByOrderNo.getTitle().startsWith("积分购买")){
|
||||||
paymentInfoService.updatePaymentStatusById(
|
float quantity = totalAmount / ProductEnum.CreditsProduct.getPrice();
|
||||||
paymentInfoService.getPaymentInfoByOrderId(orderId).getId(),
|
// 更新积分
|
||||||
session.getStatus(),
|
creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity);
|
||||||
new Gson().toJson(session));
|
// 添加积分变更记录
|
||||||
|
creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
|
||||||
log.info("Stripe 订单:{} 支付信息状态更新成功", orderId);
|
CreditsEventsEnum.BUY_CREDITS.getName() + "--Stripe",
|
||||||
float quantity = totalAmount / Float.parseFloat(CreditsEventsEnum.PRICE.getValue());
|
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
|
||||||
// 更新积分
|
"positive");
|
||||||
creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity);
|
log.info("用户:{} 积分信息更新成功", orderByOrderNo.getAccountId());
|
||||||
// 添加积分变更记录
|
}
|
||||||
creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
|
} catch (Exception e) {
|
||||||
CreditsEventsEnum.BUY_CREDITS.getName() + "--Stripe",
|
|
||||||
String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
|
|
||||||
"positive");
|
|
||||||
log.info("用户:{} 积分信息更新成功", orderByOrderNo.getAccountId());
|
|
||||||
}catch (Exception e){
|
|
||||||
log.info(e.getMessage());
|
log.info(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public SubscriptionInfo createSubscription(Subscription subscription){
|
||||||
|
// 确认当前subscription是否已经记录
|
||||||
|
QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
|
||||||
|
qw.eq("subscription_id", subscription.getId());
|
||||||
|
|
||||||
|
SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw);
|
||||||
|
if (Objects.isNull(subscriptionInfo)){
|
||||||
|
String description = subscription.getDescription();
|
||||||
|
String orderNo = description.replace("AiDA - ", "");
|
||||||
|
OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
|
||||||
|
|
||||||
|
// 从回调信息中获取recurring type
|
||||||
|
SubscriptionItem subscriptionItem = subscription.getItems().getData().get(0);
|
||||||
|
String interval = subscriptionItem.getPrice().getRecurring().getInterval();
|
||||||
|
Map<String, String> paymentMethod = getPaymentMethodByInvoiceId(subscription.getLatestInvoice());
|
||||||
|
|
||||||
|
subscriptionInfo = new SubscriptionInfo();
|
||||||
|
subscriptionInfo.setAccountId(orderInfo.getAccountId());
|
||||||
|
subscriptionInfo.setOrderNo(orderNo);
|
||||||
|
subscriptionInfo.setSubscriptionId(subscription.getId());
|
||||||
|
subscriptionInfo.setType(interval);
|
||||||
|
subscriptionInfo.setStatus(subscription.getStatus());
|
||||||
|
subscriptionInfo.setPaymentMethod(paymentMethod.get("paymentMethod"));
|
||||||
|
subscriptionInfo.setLast4(paymentMethod.get("last4"));
|
||||||
|
subscriptionInfo.setNextPayDate(changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
|
||||||
|
subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart());
|
||||||
|
subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd());
|
||||||
|
subscriptionInfo.setCreateTime(LocalDateTime.now());
|
||||||
|
subscriptionInfoMapper.insert(subscriptionInfo);
|
||||||
|
|
||||||
|
// 更新账号到期时间
|
||||||
|
updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
|
||||||
|
}
|
||||||
|
return subscriptionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean updateSubscription(Subscription subscription){
|
||||||
|
// 获取当前是否有已经记录的subscriptionInfo
|
||||||
|
SubscriptionInfo subscriptionInfo = createSubscription(subscription);
|
||||||
|
// 用于标志数据有没有变动,避免在没有改动的情况下频繁的更新数据库
|
||||||
|
boolean flag = false;
|
||||||
|
if (!subscriptionInfo.getStatus().equals(subscription.getStatus())){
|
||||||
|
subscriptionInfo.setStatus(subscription.getStatus());
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
if (subscription.getStatus().equals("canceled")){
|
||||||
|
subscriptionInfo.setAutoRenewal((byte) 0);
|
||||||
|
}
|
||||||
|
if (!subscriptionInfo.getCurrentPeriodStart().equals(subscription.getCurrentPeriodStart())){
|
||||||
|
subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart());
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
if (!subscriptionInfo.getCurrentPeriodEnd().equals(subscription.getCurrentPeriodEnd())){
|
||||||
|
subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd());
|
||||||
|
subscriptionInfo.setNextPayDate(changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
|
||||||
|
// 更新账号到期时间
|
||||||
|
updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
|
||||||
|
log.info("更新 {} 账号到期时间为:{}", subscriptionInfo.getAccountId(), changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
if (flag){
|
||||||
|
subscriptionInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
// todo 这里需要再检查支付方式吗?
|
||||||
|
subscriptionInfoMapper.updateById(subscriptionInfo);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAccountValidity(Long accountId, Long currentPeriodEnd){
|
||||||
|
// 不管当前用户的账号是否到期,都根据付款信息重置账号到期时间
|
||||||
|
com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(accountId);
|
||||||
|
account.setValidEndTime(currentPeriodEnd * 1000);
|
||||||
|
|
||||||
|
accountMapper.updateById(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消连续订阅 将订阅从pause状态转为cancel状态(使用定时器,定期检索DB中,过期且不续订的订阅)
|
||||||
|
public void cancelSubscription(String subscriptionId) {
|
||||||
|
Stripe.apiKey = privateKey;
|
||||||
|
Long accountId = UserContext.getUserHolder().getId();
|
||||||
|
log.info("用户 {} 申请取消连续订阅 {}", accountId, subscriptionId);
|
||||||
|
com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(accountId);
|
||||||
|
List<Subscription> subscriptions = getSubscription(account.getUserName(), account.getUserEmail());
|
||||||
|
// 获取status = active的订阅
|
||||||
|
subscriptions.forEach(subscription -> {
|
||||||
|
if (subscription.getId().equals(subscriptionId)) {
|
||||||
|
try {
|
||||||
|
Subscription cancel = subscription.cancel();
|
||||||
|
cancel.getStatus();
|
||||||
|
} catch (StripeException e) {
|
||||||
|
log.error("订阅 {} 取消失败, error message : {}", subscription.getId(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public String refund(String amount, String orderId, String reason) {
|
public String refund(String amount, String orderId, String reason) {
|
||||||
Refund refund ;
|
Refund refund;
|
||||||
RefundInfo refundByOrderNo = refundInfoService.createRefundByOrderNo(orderId, reason);
|
RefundInfo refundByOrderNo = refundInfoService.createRefundByOrderNo(orderId, reason);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -235,7 +483,7 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
refund = Refund.create(params);
|
refund = Refund.create(params);
|
||||||
log.info("根据会话编号退款成功");
|
log.info("根据会话编号退款成功");
|
||||||
|
|
||||||
}else {
|
} else {
|
||||||
log.error("当前订单不存在");
|
log.error("当前订单不存在");
|
||||||
return "退款异常";
|
return "退款异常";
|
||||||
}
|
}
|
||||||
@@ -260,7 +508,7 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
|
|
||||||
// 更新积分状态
|
// 更新积分状态
|
||||||
OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId);
|
OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId);
|
||||||
creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int)(orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())));
|
creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int) (orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())));
|
||||||
} else {
|
} else {
|
||||||
//更新订单状态
|
//更新订单状态
|
||||||
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL);
|
orderInfoService.updateStatusByOrderNo(orderId, OrderStatusEnum.REFUND_ABNORMAL);
|
||||||
@@ -275,17 +523,17 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
log.info("记录退款订单");
|
log.info("记录退款订单");
|
||||||
return "退款成功";
|
return "退款成功";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkOrderStatus(String orderNo){
|
public void checkOrderStatus(String orderNo) {
|
||||||
Stripe.apiKey = privateKey;
|
Stripe.apiKey = privateKey;
|
||||||
// 1、通过orderNo 查询sessionId
|
// 1、通过orderNo 查询sessionId
|
||||||
PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderId(orderNo);
|
PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderId(orderNo);
|
||||||
try {
|
try {
|
||||||
Session session = Session.retrieve(paymentInfo.getTransactionId());
|
Session session = Session.retrieve(paymentInfo.getTransactionId());
|
||||||
if (Objects.isNull(session)){
|
if (Objects.isNull(session)) {
|
||||||
log.warn("核实订单未创建 ===> {}", orderNo);
|
log.warn("核实订单未创建 ===> {}", orderNo);
|
||||||
return;
|
return;
|
||||||
} else if (session.getStatus().equals("open") || session.getStatus().equals("expired")){
|
} else if (session.getStatus().equals("open") || session.getStatus().equals("expired")) {
|
||||||
// 订单未支付 || 订单过期 ---> 均设置为超时未支付
|
// 订单未支付 || 订单过期 ---> 均设置为超时未支付
|
||||||
log.info("订单超时未支付 ===> {}", orderNo);
|
log.info("订单超时未支付 ===> {}", orderNo);
|
||||||
//更新本地订单状态
|
//更新本地订单状态
|
||||||
@@ -304,96 +552,195 @@ public class StripeServiceImpl implements StripeService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1、创建customer,获取customerId
|
// 获取所有订阅
|
||||||
// 2、创建客户支付方式 (从前端获取)
|
public List<Subscription> getSubscription(String username, String userEmail) {
|
||||||
// 3、创建支付 paymentIntent
|
Stripe.apiKey = privateKey;
|
||||||
// 4、确认订单
|
String customerId = null;
|
||||||
// 5、捕获金额(执行扣款操作)
|
try {
|
||||||
public String createCustomer() throws StripeException {
|
customerId = getCustomer(username, userEmail);
|
||||||
|
SubscriptionCollection list = Subscription.list(SubscriptionListParams.builder()
|
||||||
|
.setCustomer(customerId).build());
|
||||||
|
return list.getData();
|
||||||
|
} catch (StripeException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCustomer(String username, String userEmail) throws StripeException {
|
||||||
|
CustomerCollection list = Customer.list(CustomerListParams.builder().setEmail(userEmail).build());
|
||||||
|
List<Customer> data = list.getData();
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
return data.get(0).getId();
|
||||||
|
}
|
||||||
|
return createCustomer(username, userEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createCustomer(String name, String userEmail) throws StripeException {
|
||||||
Stripe.apiKey = privateKey;
|
Stripe.apiKey = privateKey;
|
||||||
|
|
||||||
// Customer允许重复使用
|
// Customer允许重复使用
|
||||||
CustomerCreateParams params =
|
CustomerCreateParams params =
|
||||||
CustomerCreateParams.builder()
|
CustomerCreateParams.builder()
|
||||||
.setName("xp")
|
.setName(name)
|
||||||
.setEmail("xupei3360@163.com")
|
.setEmail(userEmail)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Customer customer = Customer.create(params);
|
Customer customer = Customer.create(params);
|
||||||
|
|
||||||
return customer.getId();
|
return customer.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String createPaymentMethod(String customerId) throws StripeException {
|
/**
|
||||||
|
* 使用连续订阅的订单,回调中没有paymentIntentId,所以通过invoiceId间接获取
|
||||||
Stripe.apiKey = privateKey;
|
* @param invoiceId 发票Id
|
||||||
|
*/
|
||||||
PaymentMethodCreateParams params =
|
public Map<String, String> getPaymentMethodByInvoiceId(String invoiceId) {
|
||||||
PaymentMethodCreateParams.builder()
|
try {
|
||||||
.setType(PaymentMethodCreateParams.Type.CARD)
|
Stripe.apiKey = privateKey;
|
||||||
.setCard(
|
Invoice invoice = Invoice.retrieve(invoiceId);
|
||||||
// 测试中,不建议使用卡号,会不安全的异常,必须使用token(https://docs.stripe.com/testing?testing-method=tokens#visa)
|
PaymentIntent paymentIntent = PaymentIntent.retrieve(invoice.getPaymentIntent());
|
||||||
PaymentMethodCreateParams.Token.builder().setToken("tok_visa").build()
|
PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentIntent.getPaymentMethod());
|
||||||
// PaymentMethodCreateParams.CardDetails.builder()
|
return getPaymentMethod(paymentMethod.getId());
|
||||||
// .setNumber("4242424242424242")
|
} catch (StripeException e) {
|
||||||
// .setExpMonth(8L)
|
throw new RuntimeException(e);
|
||||||
// .setExpYear(2026L)
|
}
|
||||||
// .setCvc("314")
|
|
||||||
// .build()
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
PaymentMethod paymentMethod = PaymentMethod.create(params);
|
|
||||||
return paymentMethod.getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String createPaymentIntent(String paymentMethodId, String customerId) throws StripeException {
|
public Map<String, String> getPaymentMethod(String paymentMethodId){
|
||||||
Stripe.apiKey = privateKey;
|
Stripe.apiKey = privateKey;
|
||||||
|
String paymentMethod = null;
|
||||||
|
String last4 = null;
|
||||||
|
|
||||||
Long amount = 600L;
|
try {
|
||||||
PaymentIntentCreateParams params =
|
PaymentMethod retrieve = PaymentMethod.retrieve(paymentMethodId);
|
||||||
PaymentIntentCreateParams.builder()
|
switch (retrieve.getType()){
|
||||||
.setAmount(amount)
|
case "alipay":
|
||||||
// .setPaymentMethod(paymentMethodId)
|
paymentMethod = "Alipay";
|
||||||
.setCustomer(customerId)
|
break;
|
||||||
.setCurrency("hkd")
|
case "bancontact":
|
||||||
.setAutomaticPaymentMethods(
|
paymentMethod = "BanContact";
|
||||||
PaymentIntentCreateParams.AutomaticPaymentMethods.builder()
|
break;
|
||||||
.setEnabled(true)
|
case "card":
|
||||||
.build()
|
PaymentMethod.Card card = retrieve.getCard();
|
||||||
)
|
String brand = card.getBrand();
|
||||||
.build();
|
brand = brand.substring(0, 1).toUpperCase() + brand.substring(1);
|
||||||
|
paymentMethod = brand + " " + card.getFunding() + "card";
|
||||||
PaymentIntent paymentIntent = PaymentIntent.create(params);
|
last4 = card.getLast4();
|
||||||
return paymentIntent.getId();
|
break;
|
||||||
|
case "eps":
|
||||||
|
PaymentMethod.Eps eps = retrieve.getEps();
|
||||||
|
paymentMethod = eps.getBank();
|
||||||
|
break;
|
||||||
|
case "giropay":
|
||||||
|
paymentMethod = "GiroPay";
|
||||||
|
break;
|
||||||
|
case "ideal":
|
||||||
|
PaymentMethod.Ideal ideal = retrieve.getIdeal();
|
||||||
|
paymentMethod = ideal.getBank();
|
||||||
|
break;
|
||||||
|
case "link":
|
||||||
|
paymentMethod = "Link";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
HashMap<String, String> resp = new HashMap<>();
|
||||||
|
resp.put("paymentMethod", paymentMethod);
|
||||||
|
resp.put("last4", last4);
|
||||||
|
return resp;
|
||||||
|
} catch (StripeException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
// return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String confirmPaymentIntent(String clientSecret) throws StripeException {
|
public boolean sendEmail(String subscriptionId, String type){
|
||||||
Stripe.apiKey = privateKey;
|
|
||||||
|
|
||||||
PaymentIntent resource = PaymentIntent.retrieve(clientSecret);
|
SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO();
|
||||||
|
QueryWrapper<SubscriptionInfo> qwSI = new QueryWrapper<>();
|
||||||
|
qwSI.eq("subscription_id", subscriptionId);
|
||||||
|
SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qwSI);
|
||||||
|
if (Objects.isNull(subscriptionInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QueryWrapper<PaymentInfo> qwPI = new QueryWrapper<>();
|
||||||
|
qwPI.eq("order_no", subscriptionInfo.getOrderNo()).orderByDesc("id");
|
||||||
|
List<PaymentInfo> paymentInfos = paymentInfoMapper.selectList(qwPI);
|
||||||
|
if (paymentInfos.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PaymentInfo paymentInfo = paymentInfos.get(0);
|
||||||
|
if (paymentInfo.getNotified() == 1){
|
||||||
|
// 已经邮件通知过,直接返回
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (StringUtil.isNullOrEmpty(type)){
|
||||||
|
// 如果没有传入type,则使用paymentInfo中记录的类型
|
||||||
|
// (其实这里也可以通过invoiceId查询stripe,但是记录在自己的db中可以不用每次都查,且方便查看)
|
||||||
|
type = paymentInfo.getType();
|
||||||
|
}
|
||||||
|
|
||||||
PaymentIntentConfirmParams params =
|
com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(subscriptionInfo.getAccountId());
|
||||||
PaymentIntentConfirmParams.builder()
|
String userName = account.getUserName();
|
||||||
.setPaymentMethod("pm_card_visa")
|
String language = account.getLanguage();
|
||||||
.setReturnUrl("https://www.example.com")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
PaymentIntent paymentIntent = resource.confirm(params);
|
emailParamsDTO.setUsername(userName);
|
||||||
return paymentIntent.getId();
|
emailParamsDTO.setOrderId(paymentInfo.getId().toString());
|
||||||
|
emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " "));
|
||||||
|
emailParamsDTO.setQuantity(String.valueOf(1));
|
||||||
|
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(subscriptionInfo.getPaymentMethod());
|
||||||
|
emailParamsDTO.setSubscriptionId(subscriptionInfo.getId().toString());
|
||||||
|
emailParamsDTO.setSubscriptionType(subscriptionInfo.getType());
|
||||||
|
emailParamsDTO.setStartDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
|
||||||
|
emailParamsDTO.setNextPayDate(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
|
||||||
|
emailParamsDTO.setRenewalTime(changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
|
||||||
|
|
||||||
|
SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
|
||||||
|
|
||||||
|
// 邮件通知成功后,更新标志
|
||||||
|
PaymentInfo payment = new PaymentInfo();
|
||||||
|
payment.setId(paymentInfo.getId());
|
||||||
|
payment.setNotified(1);
|
||||||
|
payment.setUpdateTime(LocalDateTime.now());
|
||||||
|
paymentInfoMapper.updateById(payment);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String capturePaymentIntent(String clientSecret) throws StripeException {
|
public void subscriptionReminder(){
|
||||||
|
// 提前7天的 00:00:00 和 23:59:59
|
||||||
|
LocalDateTime startOfDay = LocalDateTime.now().plusDays(7).toLocalDate().atStartOfDay();
|
||||||
|
LocalDateTime endOfDay = LocalDateTime.now().plusDays(7).toLocalDate().atTime(23, 59, 59);
|
||||||
|
|
||||||
Stripe.apiKey = privateKey;
|
// 转为时间戳
|
||||||
|
long startTimestamp = startOfDay.toEpochSecond(ZoneOffset.UTC);
|
||||||
|
long endTimestamp = endOfDay.toEpochSecond(ZoneOffset.UTC);
|
||||||
|
|
||||||
PaymentIntent resource = PaymentIntent.retrieve(clientSecret);
|
QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
|
||||||
|
qw.ge("current_period_end", startTimestamp);
|
||||||
|
qw.lt("current_period_end", endTimestamp);
|
||||||
|
qw.eq("status", "active");
|
||||||
|
|
||||||
PaymentIntentCaptureParams params = PaymentIntentCaptureParams.builder().build();
|
List<SubscriptionInfo> subscriptionInfos = subscriptionInfoMapper.selectList(qw);
|
||||||
|
for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
|
||||||
PaymentIntent paymentIntent = resource.capture(params);
|
boolean b = sendEmail(subscriptionInfo.getSubscriptionId(), null);
|
||||||
return null;
|
if (b) log.info("提前7天向用户 {} 发送续订通知邮件", subscriptionInfo.getAccountId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkSubscriptionExpiration(){
|
||||||
|
long epochSecond = Instant.now().getEpochSecond();
|
||||||
|
QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
|
||||||
|
qw.lt("current_period_end", epochSecond);
|
||||||
|
qw.eq("status", "active");
|
||||||
|
List<SubscriptionInfo> subscriptionInfos = subscriptionInfoMapper.selectList(qw);
|
||||||
|
|
||||||
|
for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
|
||||||
|
subscriptionInfo.setStatus("expired");
|
||||||
|
subscriptionInfo.setUpdateTime(LocalDateTime.now());
|
||||||
|
subscriptionInfoMapper.updateById(subscriptionInfo);
|
||||||
|
log.info("用户 {} 的订阅 {} 已过期", subscriptionInfo.getAccountId(), subscriptionInfo.getOrderNo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ paypal.webhook_id=1D107312EX592781K
|
|||||||
##### Stripe
|
##### Stripe
|
||||||
|
|
||||||
# developer
|
# developer
|
||||||
#stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
|
stripe.private-key=sk_test_51P4ZZL02n1TEydyN8qQHjOA9imsFU7Oxs2HMHGy2urHnnQgSHnZuu5vVP6pKhEACwUpsKNyrbZpdcg5TJWJLRHcY008dEO1fn2
|
||||||
#stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
|
stripe.webhook-sign-secret=whsec_e0dBiJngx6qqgJj6yPyJ2A9ouh1Cjv5w
|
||||||
|
|
||||||
# kim - test
|
# kim - test
|
||||||
#stripe.private-key=sk_test_51LwPrxH7nPZ8bkrNj67TFD7sxucaTANs1lf0KGSu1QSJfxYXcnigq2wTaZyZzST7y0fMbhhvaJZ4LjjFhr95M83a00eXrmOTL0
|
#stripe.private-key=sk_test_51LwPrxH7nPZ8bkrNj67TFD7sxucaTANs1lf0KGSu1QSJfxYXcnigq2wTaZyZzST7y0fMbhhvaJZ4LjjFhr95M83a00eXrmOTL0
|
||||||
#stripe.webhook-sign-secret=whsec_GoyVEAaBtuGD5Rt55z83JnPnLDAZTN3u
|
#stripe.webhook-sign-secret=whsec_GoyVEAaBtuGD5Rt55z83JnPnLDAZTN3u
|
||||||
|
|
||||||
# kim - live
|
# kim - live
|
||||||
stripe.private-key=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
|
#stripe.private-key=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
|
||||||
stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11
|
#stripe.webhook-sign-secret=whsec_hhGDgdelQRHSg4LmChtQe41crj41eb11
|
||||||
Reference in New Issue
Block a user