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.enums.*;
import com.ai.da.common.utils.DateUtil;
import com.ai.da.common.utils.RedisUtil;
import com.ai.da.common.utils.SendEmailUtil;
import com.ai.da.mapper.primary.AccountMapper;
import com.ai.da.mapper.primary.PaymentInfoMapper;
import com.ai.da.mapper.primary.ProductCouponsMapper;
import com.ai.da.mapper.primary.SubscriptionInfoMapper;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.CreateCouponDTO;
import com.ai.da.model.dto.ProductPurchaseDTO;
import com.ai.da.model.dto.QueryCouponsPageDTO;
import com.ai.da.model.dto.SubscriptionEmailParamsDTO;
import com.ai.da.model.enums.Language;
import com.ai.da.model.vo.CheckCouponsVO;
import com.ai.da.service.*;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
import com.stripe.Stripe;
import com.stripe.exception.InvalidRequestException;
import com.stripe.exception.SignatureVerificationException;
import com.stripe.exception.StripeException;
import com.stripe.model.*;
import com.stripe.model.Product;
import com.stripe.model.checkout.Session;
import com.stripe.net.Webhook;
import com.stripe.param.*;
import com.stripe.param.checkout.SessionCreateParams;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;

@SuppressWarnings("LoggingSimilarMessage")
@Service
@Slf4j
public class StripeServiceImpl implements StripeService {

    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private PayPalCheckoutService payPalCheckoutService;
    @Resource
    private PaymentInfoService paymentInfoService;
    @Resource
    private CreditsService creditsService;
    @Resource
    private RefundInfoService refundInfoService;
    @Resource
    private AccountService accountService;
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private SubscriptionInfoMapper subscriptionInfoMapper;
    @Resource
    private PaymentInfoMapper paymentInfoMapper;
    @Resource
    private ProductCouponsMapper productCouponsMapper;
    @Resource
    private RedisUtil redisUtil;

    @Value("${stripe.private-key}")
    private String privateKey;

    @Value("${stripe.webhook-sign-secret}")
    private String signSecret;

    @Value("${orderList.link}")
    private String orderListLink;

    @Value("${stripe.paymentMethodConfiguration}")
    private String paymentMethodConfigurationId;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String pay(ProductPurchaseDTO productPurchaseDTO, HttpServletRequest request) {
        Stripe.apiKey = privateKey;

        //创建支付信息得到url
        // 一次性支付和周期扣款，需要区分mode: payment || subscription || setup
        SessionCreateParams.Builder sessionBuilder = new SessionCreateParams.Builder();
        ProductEnum productEnum;
        switch (productPurchaseDTO.getProductName()){
            case "CreditsPurchase":
                productEnum = ProductEnum.CreditsProduct;
                productPurchaseDTO.setAutoRenewal(false);
                break;
            case "Subscription":
                switch (productPurchaseDTO.getSubscribeType()){
                    case "Month":
                        productEnum = ProductEnum.MonthlySubscription;
                        break;
                    case "EcoMonth":
                        productEnum = ProductEnum.Eco_MonthlySubscription;
                        productPurchaseDTO.setSubscribeType("Month");
                        break;
                    case "Year":
                        productEnum = ProductEnum.AnnualSubscription;
                        break;
                    case "Day":
                        productEnum = ProductEnum.DailySubscription;
                        break;
                    default:
                        throw new BusinessException("unknown.subscription.type");
                }

                // 添加优惠券(只允许在订阅时使用优惠券)
                String promotionCode = productPurchaseDTO.getPromotionCode();
                if (!StringUtil.isNullOrEmpty(promotionCode)){
                    ProductCoupons productCoupon = checkProductCoupon(promotionCode);;
                    if (productCoupon != null){
                        sessionBuilder.addDiscount(SessionCreateParams.Discount.builder()
                                .setPromotionCode(productCoupon.getPromotionCodeId()).build());
                    }
                }
                // 只有订阅时才允许使用推广码优惠
//                sessionBuilder.setAllowPromotionCodes(true);
                break;
            default:
                throw new BusinessException("unknown.product.type");
        }
        log.info("生成订单");
        String payType;
        byte autoRenewal;
        if (productPurchaseDTO.getAutoRenewal()){
            payType = "recurring";
            autoRenewal = 1;
        }else {
            payType = "one_time";
            autoRenewal = 0;
        }
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productPurchaseDTO.getQuantity(),
                PayTypeEnum.STRIPE.getType(), productEnum, request, autoRenewal);

        try {
            Long id = UserContext.getUserHolder().getId();
            com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(id);
            // 获取或创建产品
            String productId = getProduct(productEnum.getName());
            // 获取或创建价格
            String priceId = getPrice(productEnum.getPrice(), productId, payType, productPurchaseDTO.getSubscribeType());
            // 获取或创建customer
            String customerId = getCustomer(account.getUserName(), account.getUserEmail());
            log.info("customerId:{}", customerId);
            // 获取自定义订单号
            String orderId = orderInfo.getOrderNo();


            if (payType.equals("recurring")){
                sessionBuilder.setMode(SessionCreateParams.Mode.SUBSCRIPTION);
                sessionBuilder.setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setDescription("AiDA - " + orderId).build());
            }else {
                sessionBuilder.setMode(SessionCreateParams.Mode.PAYMENT);
                sessionBuilder.setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder().setDescription("AiDA - " + orderId).build());
                // one-time 手动创建发票;订阅会自动创建invoice
                sessionBuilder.setInvoiceCreation(SessionCreateParams.InvoiceCreation.builder().setEnabled(Boolean.TRUE).build());
            }

            sessionBuilder.setPaymentMethodConfiguration(paymentMethodConfigurationId);
            sessionBuilder.setCustomer(customerId);
            sessionBuilder.setSuccessUrl(productPurchaseDTO.getReturnUrl());//可自定义成功页面
            sessionBuilder.setLocale(account.getLanguage().equals("CHINESE_SIMPLIFIED") ? SessionCreateParams.Locale.ZH : SessionCreateParams.Locale.EN);
            sessionBuilder.addLineItem(
                    SessionCreateParams.LineItem.builder()
                            .setQuantity((long) productPurchaseDTO.getQuantity())
                            .setPrice(priceId)
                            .build());
            sessionBuilder.putMetadata("orderId", orderId); //通过订单号关联用于检索支付信息（可选）

            Session session = Session.create(sessionBuilder.build());
            List<String> paymentMethodTypes = session.getPaymentMethodTypes();
            log.info("paymentMethodTypes: {}", paymentMethodTypes);

            Session.PaymentMethodConfigurationDetails paymentMethodConfigurationDetails = session.getPaymentMethodConfigurationDetails();
            log.info("paymentMethodConfigurationDetails ID: {}", paymentMethodConfigurationDetails.getId());
            log.info("sessionId:{}", session.getId());   //退款方式1：拿到sessionId入库，退款的时候根据这个id找到PaymentIntent的id然后发起退款

            // 更新order信息
            orderInfoService.updateOrderNoById(orderInfo.getId(), orderId);
            return session.getUrl();
        } catch (BusinessException e) {
            throw e;
        } catch (InvalidRequestException e) {
            log.info("创建会话出现异常：", e);
            throw new BusinessException(e.getMessage().substring(0, e.getMessage().indexOf(";")));
        } catch (Exception e) {
            log.error("创建支付会话出现异常：", e);
        }
        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());
    }

    @Resource
    private EmailService emailService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean notify(HttpServletRequest request) {
        log.info("stripe异步通知进行中");
        String payload = null;
        String sigHeader = null;
        String endpointSecret = signSecret;
        try {
            sigHeader = request.getHeader("Stripe-Signature");
            payload = payPalCheckoutService.getBody(request);
        } catch (Exception e) {
            log.info("stripe 支付回调参数解析异常：errorMsg {}", e.getMessage());
            log.info("request sigHeader = {}", sigHeader);
            log.info("request body = {}", JSON.toJSONString(payload));
            e.printStackTrace();
            return Boolean.FALSE;
        }

        Event event;
        try {
            assert sigHeader != null;
            event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
        } catch (SignatureVerificationException e) {
            log.info("stripe 验签，获取事件异常， errorMsg={}", e.getMessage());
            log.info("request sigHeader = {}", sigHeader);
            log.info("request body = {}", JSON.toJSONString(payload));
            e.printStackTrace();
            return Boolean.FALSE;
        }

        //获取自定义参数
        // Deserialize the nested object inside the event
        assert event != null;
        EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
        StripeObject stripeObject ;
        if (dataObjectDeserializer.getObject().isPresent()) {
            stripeObject = dataObjectDeserializer.getObject().get();
        } else {
            log.info("stripe 验签失败！");
            log.info("request sigHeader = {}", sigHeader);
            log.info("request body = {}", JSON.toJSONString(payload));
            return Boolean.FALSE;
        }
        log.info("stripe验签成功");
        boolean response = Boolean.TRUE;

        log.info("回调事件 {}", event.getType());
        if (stripeObject instanceof Session){
            Session session = (Session) stripeObject;
            if (event.getType().equals("checkout.session.completed")) {
                response = processOrder(session);
            }else if (event.getType().equals("checkout.session.expired")){
                String orderNo = session.getMetadata().get("orderId");
                // 会话过期 未支付 且之后没有支付成功的订单
                response = processExpiredOrder(orderNo);
            }
        } 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")){
                // 更新订阅信息
                SubscriptionInfo subscriptionInfo = updateSubscription(subscription);
                log.info("订阅更新");
                if (subscription.getStatus().equals("active")){
                    response = sendEmail(subscription.getId(), null, null);
                }
                // 续订支付失败，邮件通知用户
                if (subscription.getStatus().equals("past_due")){
                    // 发送续订失败邮件
                    response = sendRenewalFailEmail(null, subscription.getId(), subscriptionInfo.getOrderNo());

                }
            } else if (event.getType().equals("customer.subscription.deleted")){
                SubscriptionInfo subscriptionInfo = updateSubscription(subscription);
                if (Objects.isNull(subscriptionInfo)){
                    return true;
                }
                log.info("用户 {} 取消连续订阅 {}", subscriptionInfo.getAccountId(), subscription.getId());
                if (subscriptionInfo.getCancelNotified() == (byte)0){
                    log.info("取消订阅 邮件通知商家");
                    response = sendEmail(subscription.getId(), "cancel", null);
                    if (response){
                        subscriptionInfo.setCancelNotified((byte)1);
                        subscriptionInfoMapper.updateById(subscriptionInfo);
                        // 更新订单信息
                        OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(subscriptionInfo.getOrderNo());
                        orderInfo.setAutoRenewal((byte)0);
                    }
                }

            }/* 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，表示，该回调第一次被记录
                PaymentInfo paymentInfo = paymentInfoService.createOrUpdatePaymentInfoForStripe(invoice);

                /* 在sendEmail方法中有做判断，这里的判断取消
                // 当前支付没有被通知时才需要发送通知邮件
                if (paymentInfo.getNotified().equals(0)) {

                }*/
                // 更新t_order_info中的total_fee,记录该订单的累计付款金额
                orderInfoService.updateTotalFeeByOrderNo(paymentInfo.getOrderNo());
                // 邮件通知商家和用户
                String billingReason = invoice.getBillingReason();
                switch (billingReason) {
                    case "subscription_create":
                        response = sendEmail(invoice.getSubscription(), "new", null);
                        break;
                    case "subscription_cycle":
                        response = sendEmail(invoice.getSubscription(), "renewal", null);
                        break;
                    case "manual":
                        boolean b = invoice.getLines().getData().get(0).getDescription().endsWith("Subscription");
                        if (b) {
                            // 非自动续订式订阅，Stripe不会创建Subscription,所以invoice中不会有subscriptionId
                            response = sendEmail(null, "new", paymentInfo.getOrderNo());
                        }
                        break;
                }

            } else if (event.getType().equals("invoice.payment_failed")) {
                // 更新支付信息
                QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("transaction_id", invoice.getId());
                PaymentInfo paymentInfo = paymentInfoService.getBaseMapper().selectOne(queryWrapper);
                if (!Objects.isNull(paymentInfo)){
                    String type = invoice.getBillingReason().equals("subscription_create") ? "new" :
                            invoice.getBillingReason().equals("subscription_cycle") ? "renewal" : invoice.getBillingReason();
                    Gson gson = new Gson();
                    String json = gson.toJson(invoice);
                    paymentInfo.setContent(json);
                    paymentInfo.setType(type);
                    paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl());
                    paymentInfoService.updateById(paymentInfo);

                    // 发送续订失败邮件
                    response = sendRenewalFailEmail(invoice.getId(), null, paymentInfo.getOrderNo());
                }else {
                    // 新增支付信息
                    PaymentInfo paymentInfoFail = paymentInfoService.createOrUpdatePaymentInfoForStripe(invoice);
                    // 发送新订阅失败邮件
                    response = sendEmail(paymentInfoFail.getOrderNo());
                }
            }
        }else if (stripeObject instanceof Charge) {
            Charge charge = (Charge) stripeObject;
            String orderNo = charge.getDescription().replace("AiDA - ", "");
            OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);
            if (Objects.isNull(orderInfo)){
                // 说明该回调不是从AiDA订阅获得
                return true;
            }
            if (event.getType().equals("charge.failed")){
                // 添加支付信息 && 更新支付信息
                // 支付失败时，无法通过invoice_id获取支付方式，所以使用charge.failed回调添加支付信息
                paymentInfoService.createOrUpdatePaymentInfoForStripe(charge);

                orderInfo.setOrderStatus(OrderStatusEnum.FAILURE.getType());
                orderInfo.setNote(charge.getFailureMessage());
                orderInfoService.updateById(orderInfo);
            }else if (event.getType().equals("charge.succeeded")){
                orderInfo.setOrderStatus(OrderStatusEnum.SUCCESS.getType());
                orderInfo.setNote("");
                orderInfoService.updateById(orderInfo);
            }else if (event.getType().equals("charge.refunded")){
                // 更新退款信息
                RefundInfo refundInfo = refundInfoService.updateRefundForStripe(charge);
                // 更新 t_payment_info的支付状态
                if (Objects.nonNull(refundInfo)){
                    paymentInfoService.updatePaymentRefundStatus(charge);
                }
            }
        }else if (stripeObject instanceof Refund){
            Refund refund = (Refund) stripeObject;
            if (event.getType().equals("refund.created")){
                // 新增退款信息
                refundInfoService.createRefundForStripe(refund);
            }else if (event.getType().equals("refund.updated")){
                // 根据***id更新退款记录信息
                RefundInfo refundInfo = refundInfoService.updateRefundStatusForStripe(refund);
                if (Objects.isNull(refundInfo)){
                    // 等事件先创建，再更新。回调事件的顺序随机
                    response =  false;
                }
            }
        }
        log.info("回调事件 {} 处理完成", event.getType());
        return response;
    }

    public boolean processOrder(Session session) {
        Stripe.apiKey = privateKey;
        String orderNo = session.getMetadata().get("orderId");
        float totalAmount = new BigDecimal(session.getAmountTotal()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue();

        boolean resp = true;
        try {
            //处理重复通知
            //接口调用的幂等性：无论接口被调用多少次，以下业务执行一次
//                String orderStatus = orderInfoService.getOrderStatus(orderNo);
            OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
            String orderStatus = orderByOrderNo.getOrderStatus();
            // 当订单状态处于未支付或超时已关闭时，更新订单状态，其他状态均不更新订单状态
            if (!OrderStatusEnum.NOT_PAY.getType().equals(orderStatus) && !OrderStatusEnum.TIMEOUT_CLOSED.getType().equals(orderStatus)) {
                log.info("订单状态 : {}", orderStatus);
            }else {
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.SUCCESS);
                log.info("Stripe 订单：{} 状态更新成功", orderNo);
            }

            if (orderByOrderNo.getTitle().startsWith("积分购买")){
                // 查询当前订单的积分是否已添加
                CreditsDetail creditsDetail = creditsService.queryDetailByTaskId(orderNo);
                if (Objects.isNull(creditsDetail)){
                    float quantity = totalAmount / ProductEnum.CreditsProduct.getPrice();
                    // 更新积分
                    creditsService.buyCredits(orderByOrderNo.getAccountId(), quantity);
                    // 添加积分变更记录
                    creditsService.insertToCreditsDetail(orderByOrderNo.getAccountId(),
                            CreditsEventsEnum.BUY_CREDITS.getName() + "--Stripe",
                            String.valueOf((Long.parseLong(CreditsEventsEnum.BUY_CREDITS.getValue()) * quantity)),
                            "positive", orderNo);
                    log.info("用户：{} 积分信息更新成功", orderByOrderNo.getAccountId());
                }
            }else if (orderByOrderNo.getTitle().endsWith("Subscription") && orderByOrderNo.getAutoRenewal() == (byte)0){
                String invoiceId = session.getInvoice();
                Invoice invoice = Invoice.retrieve(invoiceId);
                InvoiceLineItem invoiceLineItem = invoice.getLines().getData().get(0);
                String description = invoiceLineItem.getDescription();
                Long amount = invoiceLineItem.getAmount();
                log.info("单次订阅 description : {}, amount: {} 分", description, amount);
                boolean b = createSubscriptionAndUpdateAccount(orderNo, orderByOrderNo.getAccountId(), description, amount);
                // 邮件通知用户
                if (b){
                    resp = sendEmail(null, "new", orderNo);
                }
                log.info("单次订阅订单:{} 处理完成", orderNo);
            }
        } catch (Exception e) {
            log.info(e.getMessage());
            resp = false;
        }
        return resp;
    }

    private boolean processExpiredOrder(String orderNo) {
        // 支付失败 通知商家的条件 1、会话过期 2、支付失败 3、这个用户在这个支付失败后再无支付成功的订阅
        // 1、获取当前订单的支付状态
//        String orderNo = session.getMetadata().get("orderId");
        OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
        // 2、确认订单状态为支付失败
        boolean resp = true;
        if (!Objects.isNull(orderByOrderNo) && orderByOrderNo.getOrderStatus().equals(OrderStatusEnum.FAILURE.getType())) {
            // 3、判断失败订单之后再无成功的订单
            QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("account_id", orderByOrderNo.getAccountId());
            queryWrapper.gt("create_time", orderByOrderNo.getCreateTime());
            queryWrapper.eq("order_status", OrderStatusEnum.SUCCESS.getType());
            queryWrapper.likeLeft("title", "Subscription");
            List<OrderInfo> orderInfos = orderInfoService.getBaseMapper().selectList(queryWrapper);
            if (orderInfos.isEmpty()) {
                // 4、判断当前订单有没有订阅信息
                QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
                qw.eq("order_no", orderNo);
                SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw);
                // 发送邮件通知商家用户支付失败
                if (Objects.isNull(subscriptionInfo)
                        || subscriptionInfo.getStatus().equals("incomplete")
                        || subscriptionInfo.getStatus().equals("incomplete_expired")) {
                    resp = sendEmail(orderNo);
                }else {
                    // todo 续订失败 应该不会走这里
                    resp = sendEmail(subscriptionInfo.getSubscriptionId(), "fail_renewal", null);
                }
            }
        }
        return resp;

    }

    @Transactional(rollbackFor = Exception.class)
    public SubscriptionInfo createSubscription(Subscription subscription){
        // 确认当前subscription是否已经记录
        SubscriptionInfo subscriptionInfo = getSubscriptionInfoBySubId(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);
            if (Objects.isNull(orderInfo)){
                log.warn("未知订阅：{}", subscription.getId());
                return null;
            }

            // 从回调信息中获取recurring type
            SubscriptionItem subscriptionItem = subscription.getItems().getData().get(0);
            String interval = subscriptionItem.getPrice().getRecurring().getInterval();

            subscriptionInfo = new SubscriptionInfo();
            subscriptionInfo.setAccountId(orderInfo.getAccountId());
            subscriptionInfo.setOrderNo(orderNo);
            subscriptionInfo.setSubscriptionId(subscription.getId());
            subscriptionInfo.setType(interval);
            subscriptionInfo.setStatus(subscription.getStatus());
            subscriptionInfo.setNextPayDate(DateUtil.changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
            subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart());
            subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd());
            subscriptionInfo.setCreateTime(LocalDateTime.now());

            int rows = subscriptionInfoMapper.insertIgnore(subscriptionInfo);
            log.info("Subscription info insert affect rows : {}", rows);

            if (subscriptionInfo.getStatus().equals("active")){
                log.info("创建订阅更新账号信息");
                // 更新账号到期时间
                boolean b = accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
                // 更新账号身份和积分
                if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), orderNo);
            }
        }
        return subscriptionInfo;
    }

    /**
     * 非自动续订订阅
     * Stripe不会自动创建Subscription,所以没有subscription相关的回调，无法触发订阅相关的处理代码
     */
    public boolean createSubscriptionAndUpdateAccount(String orderNo, Long accountId, String description, Long amount){
        QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
        qw.eq("order_no", orderNo);
        SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw);
        if (Objects.isNull(subscriptionInfo)) {
            String interval;
            // 获取当前时间戳（秒级）
            long currentPeriodStart = Instant.now().getEpochSecond();;
            long currentPeriodEnd;
//        InvoiceLineItem invoiceLineItem = invoice.getLines().getData().get(0);
            if (description.equals(ProductEnum.DailySubscription.getName())
                    && amount.equals(ProductEnum.DailySubscription.getPrice() * 100)){
                interval = "day";
                // 获取一天后的时间戳（秒级）
                ZonedDateTime now = ZonedDateTime.now();
                currentPeriodEnd = now.plusDays(1).toEpochSecond();
            }else if (description.equals(ProductEnum.MonthlySubscription.getName())
                    && amount.equals(ProductEnum.MonthlySubscription.getPrice() * 100)){
                interval = "month";
                // 获取一天后的时间戳（秒级）
                ZonedDateTime now = ZonedDateTime.now();
                currentPeriodEnd = now.plusMonths(1).toEpochSecond();
            } else if (description.equals(ProductEnum.Eco_MonthlySubscription.getName())
                    && amount.equals(ProductEnum.Eco_MonthlySubscription.getPrice() * 100)){
                interval = "month";
                // 获取一天后的时间戳（秒级）
                ZonedDateTime now = ZonedDateTime.now();
                currentPeriodEnd = now.plusMonths(1).toEpochSecond();
            } else if (description.equals(ProductEnum.AnnualSubscription.getName())
                    && amount.equals(ProductEnum.AnnualSubscription.getPrice() * 100)){
                interval = "year";
                // 获取一天后的时间戳（秒级）
                ZonedDateTime now = ZonedDateTime.now();
                currentPeriodEnd = now.plusYears(1).toEpochSecond();
            }else {
                log.error("未知订阅类型");
                return false;
            }
            subscriptionInfo = new SubscriptionInfo();
            subscriptionInfo.setAccountId(accountId);
            subscriptionInfo.setOrderNo(orderNo);
            subscriptionInfo.setType(interval);
            subscriptionInfo.setStatus("canceled");
            subscriptionInfo.setCurrentPeriodStart(currentPeriodStart);
            subscriptionInfo.setCurrentPeriodEnd(currentPeriodEnd);
            subscriptionInfo.setCreateTime(LocalDateTime.now());

            subscriptionInfoMapper.insertIgnore(subscriptionInfo);

            log.info("创建订阅, 更新账号信息");
            // 更新账号到期时间
            boolean b = accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
            // 更新账号身份和积分
            if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), orderNo);
            return true;
        }
        return true;
    }

    public SubscriptionInfo getSubscriptionInfoBySubId(String subId){
        QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
        qw.eq("subscription_id", subId);

        List<SubscriptionInfo> subscriptionInfos = subscriptionInfoMapper.selectList(qw);
        if (subscriptionInfos.size() == 1){
            return subscriptionInfos.get(0);
        }else if (subscriptionInfos.size() > 1) {
            // 如果新建了多个订阅，则筛选出状态为active的订单
            Optional<SubscriptionInfo> activeSubscriptionInfo = subscriptionInfos.stream()
                    .filter(sub -> sub.getStatus().equals("active"))
                    .findFirst();

            return activeSubscriptionInfo.orElseGet(() -> subscriptionInfos.get(0));
        }else {
            return null;
        }
    }

    public SubscriptionInfo getLatestSubscriptionInfoByAccountId(Long accountId){
        QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
        qw.eq("account_id", accountId).orderByDesc("id");
        List<SubscriptionInfo> subscriptionInfos = subscriptionInfoMapper.selectList(qw);
        if (subscriptionInfos.isEmpty()){
            return null;
        }else {
            return subscriptionInfos.get(0);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public SubscriptionInfo updateSubscription(Subscription subscription){
        // 获取当前是否有已经记录的subscriptionInfo
        SubscriptionInfo subscriptionInfo = createSubscription(subscription);
        // 用于标志数据有没有变动，避免在没有改动的情况下频繁的更新数据库
        boolean flag = false;
        if (!subscriptionInfo.getStatus().equals(subscription.getStatus())){
            subscriptionInfo.setStatus(subscription.getStatus());
            flag = true;
        }
        if (!subscriptionInfo.getCurrentPeriodStart().equals(subscription.getCurrentPeriodStart())){
            subscriptionInfo.setCurrentPeriodStart(subscription.getCurrentPeriodStart());
            flag = true;
        }
        if (!subscriptionInfo.getCurrentPeriodEnd().equals(subscription.getCurrentPeriodEnd())){
            subscriptionInfo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd());
            subscriptionInfo.setNextPayDate(DateUtil.changeTimeStampFormat(subscription.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
            log.info("更新订阅更新账号信息");
            // 更新账号到期时间
            accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
            // 更新账号身份和积分
            accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getOrderNo());
            log.info("更新 {} 账号到期时间为：{}", subscriptionInfo.getAccountId(), DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
            flag = true;
        }
        if (subscriptionInfo.getStatus().equals("active")){
            // 更新账号到期时间
            boolean b = accountService.updateAccountValidity(subscriptionInfo.getAccountId(), subscriptionInfo.getCurrentPeriodEnd());
            // 更新账号身份和积分
            if (b) accountService.updateUserRoleAndCredits(subscriptionInfo.getAccountId(), subscriptionInfo.getOrderNo());
        }
        if (flag){
            subscriptionInfo.setUpdateTime(LocalDateTime.now());
            subscriptionInfoMapper.updateById(subscriptionInfo);
        }
        return subscriptionInfo;
    }

    // 取消连续订阅 将订阅从pause状态转为cancel状态（使用定时器，定期检索DB中，过期且不续订的订阅）
    public void cancelSubscription(String subscriptionId, String cancelReason) {
        Stripe.apiKey = privateKey;
        log.info("cancel subscription");
        Long accountId = UserContext.getUserHolder().getId();
        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();
                    log.info("用户 {} 申请取消连续订阅 {}", accountId, subscriptionId);
                    // 更新数据库
                    updateCancelReason(subscriptionId, cancelReason);
                } catch (StripeException e) {
                    log.error("订阅 {} 取消失败， error message : {}", subscription.getId(), e.getMessage());
                }
            }
        });
    }

    public void cancelSubscriptionTemp(String subscriptionId) {
        Stripe.apiKey = privateKey;
        try {
            log.info("申请取消连续订阅 {}", subscriptionId);
            Subscription subscription = Subscription.retrieve(subscriptionId);
            Subscription cancel = subscription.cancel();
            cancel.getStatus();
        } catch (StripeException e) {
            log.error(e.getMessage());
//            throw new RuntimeException(e);
        }
    }

    public String refund(String amount, String orderNo, String reason) {
        Refund refund;
        RefundInfo refundByOrderNo = refundInfoService.createRefundByOrderNo(orderNo, reason);

        try {
            Stripe.apiKey = privateKey;
            // todo transactionId不再是sessionId而是invoiceId，所以这里需要更新
            // 根据orderId找到对应的sessionId
            String sessionId = paymentInfoService.getPaymentInfoByOrderNo(orderNo, "DESC").get(0).getTransactionId();

            if (StringUtils.isNotEmpty(sessionId)) {  //根据会话编号退款
                Session session = Session.retrieve(sessionId);
                RefundCreateParams params;
                if (amount != null && !amount.equals("0")) {  //指定退款金额
                    BigDecimal actualAmount = new BigDecimal(amount).multiply(BigDecimal.valueOf(100));  //api默认单位分
                    params = RefundCreateParams.builder()
                            .setPaymentIntent(session.getPaymentIntent())
                            .setAmount(actualAmount.longValue())
                            .build();
                } else {  //全额退款
                    params = RefundCreateParams.builder()
                            .setPaymentIntent(session.getPaymentIntent())
                            .build();
                }
                refund = Refund.create(params);
                log.info("根据会话编号退款成功");

            } else {
                log.error("当前订单不存在");
                return "退款异常";
            }
        } catch (Exception e) {
            //e.getMessage.contain("charge_already_refunded") 已退款
            //e.getMessage.contain("resource_missing") 退款编号错误
            //e.getMessage.contain("amount on charge ($n)") 金额应小于n
            log.error("退款异常：", e);
            return "退款异常";
        }

        if ("succeeded".equals(refund.getStatus())) {
            //进行数据库操作，修改状态为已退款（配合回调和退款查询确定退款成功）
            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_SUCCESS);

            refundInfoService.updateRefundForPayPal(
                    refundByOrderNo.getId(),
                    refund.getId(),
                    new Gson().toJson(refund),
                    AliPayTradeStateEnum.REFUND_SUCCESS.getType()); //退款成功

            // 更新积分状态
            OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
            creditsService.creditsRefund(orderByOrderNo.getAccountId(), (int) (orderByOrderNo.getTotalFee() / Float.parseFloat(CreditsEventsEnum.PRICE.getValue())), orderNo);
        } else {
            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.REFUND_ABNORMAL);

            //更新退款单
            refundInfoService.updateRefundForPayPal(
                    refundByOrderNo.getId(),
                    refund.getId(),
                    new Gson().toJson(refund),
                    AliPayTradeStateEnum.REFUND_ERROR.getType()); //退款失败
        }
        log.info("记录退款订单");
        return "退款成功";
    }

    public void checkOrderStatus(String orderNo) {
        Stripe.apiKey = privateKey;
        // 1、通过orderNo 查询sessionId
        // todo transactionId不再是sessionId而是invoiceId，所以这里需要更新
        PaymentInfo paymentInfo = paymentInfoService.getPaymentInfoByOrderNo(orderNo, "DESC").get(0);
        try {
            Session session = Session.retrieve(paymentInfo.getTransactionId());
            if (Objects.isNull(session)) {
                log.warn("核实订单未创建 ===> {}", orderNo);
                return;
            } else if (session.getStatus().equals("open") || session.getStatus().equals("expired")) {
                // 订单未支付 || 订单过期 --->  均设置为超时未支付
                log.info("订单超时未支付 ===> {}", orderNo);
                //更新本地订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatusEnum.TIMEOUT_CLOSED);
                paymentInfoService.updatePaymentStatusById(paymentInfo.getId(),
                        session.getStatus(),
                        new Gson().toJson(session));
            } else if (session.getStatus().equals("complete")) {
                // 订单已完成
                processOrder(session);
            }
        } catch (StripeException e) {
            log.error("根据sessionId获取Stripe Session失败");
            throw new RuntimeException(e);
        }

    }

    public List<Subscription> getSubscription(String username, String userEmail) {
        Stripe.apiKey = privateKey;
        String customerId = null;
        try {
            customerId = getCustomer(username, userEmail);
            SubscriptionCollection list = Subscription.list(SubscriptionListParams.builder()
                    .setCustomer(customerId).setLimit(20L)
                    .build());
            return list.getData();
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    // 获取所有订阅
    public List<String> 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<Subscription> data = list.getData();
            ArrayList<String> subscriptionIds = new ArrayList<>();
            data.forEach(subscription -> {
                subscriptionIds.add(subscription.getId());
            });
            return subscriptionIds;
        } 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;

        // Customer允许重复使用
        CustomerCreateParams params =
                CustomerCreateParams.builder()
                        .setName(name)
                        .setEmail(userEmail)
                        .build();
        Customer customer = Customer.create(params);

        return customer.getId();
    }

    /**
     * 使用连续订阅的订单，回调中没有paymentIntentId,所以通过invoiceId间接获取
     * @param invoiceId 发票Id
     */
    public Map<String, String> getPaymentMethodByInvoiceId(String invoiceId) {
        try {
            Stripe.apiKey = privateKey;
            Invoice invoice = Invoice.retrieve(invoiceId);
            if (!StringUtil.isNullOrEmpty(invoice.getPaymentIntent())){
                PaymentIntent paymentIntent = PaymentIntent.retrieve(invoice.getPaymentIntent());
                if (!StringUtil.isNullOrEmpty(paymentIntent.getPaymentMethod())){
                    PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentIntent.getPaymentMethod());
                    return getPaymentMethod(paymentMethod.getId());
                }
            }
            HashMap<String, String> resp = new HashMap<>();
            resp.put("paymentMethod", "N/A");
            resp.put("last4", "N/A");
            return resp;
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    public Map<String, String> getPaymentMethod(String paymentMethodId){
        Stripe.apiKey = privateKey;
        String paymentMethod = null;
        String last4 = null;

        try {
            PaymentMethod retrieve = PaymentMethod.retrieve(paymentMethodId);
            switch (retrieve.getType()){
                case "alipay":
                    paymentMethod = "Alipay";
                    last4 = "N/A";
                    break;
                case "bancontact":
                    paymentMethod = "BanContact";
                    break;
                case "card":
                    PaymentMethod.Card card = retrieve.getCard();
                    String brand = card.getBrand();
                    brand = brand.substring(0, 1).toUpperCase() + brand.substring(1);
                    paymentMethod = brand + " " + card.getFunding() + "card";
                    last4 = card.getLast4();
                    break;
                case "eps":
                    PaymentMethod.Eps eps = retrieve.getEps();
                    paymentMethod = eps.getBank();
                    last4 = "N/A";
                    break;
                case "giropay":
                    paymentMethod = "GiroPay";
                    last4 = "N/A";
                    break;
                case "ideal":
                    PaymentMethod.Ideal ideal = retrieve.getIdeal();
                    paymentMethod = ideal.getBank();
                    last4 = "N/A";
                    break;
                case "link":
                    paymentMethod = "Link";
                    last4 = "N/A";
                    break;
                default:
                    paymentMethod = "N/A";
                    last4 = "N/A";
            }
            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 boolean sendEmail(String subscriptionId, String type, String orderNo) {
        SubscriptionInfo subscriptionInfo;
        long secondsTimestamp = System.currentTimeMillis() / 1000;
        log.info("当前时间戳：{}", secondsTimestamp);
        QueryWrapper<SubscriptionInfo> qwSI = new QueryWrapper<>();
        if (!StringUtil.isNullOrEmpty(subscriptionId)) {
            // 区分当前订阅的时段，从而确定是新订单还是续订订单的记录
            qwSI.lambda().eq(SubscriptionInfo::getSubscriptionId, subscriptionId)
                    .lt(SubscriptionInfo::getCurrentPeriodStart, secondsTimestamp)
                    .gt(SubscriptionInfo::getCurrentPeriodEnd, secondsTimestamp);
            List<SubscriptionInfo> subscriptionInfoList = subscriptionInfoMapper.selectList(qwSI);

            if (subscriptionInfoList.isEmpty()){
                log.info("不发送邮件，原因：【subscriptionInfoList 为空】");
                return false;
            }else {
                List<SubscriptionInfo> activeSubscriptions = subscriptionInfoList.stream()
                        .filter(subscription -> "active".equals(subscription.getStatus()))
                        .collect(Collectors.toList());
                if (!StringUtil.isNullOrEmpty(type) && type.equals("cancel")){
                    subscriptionInfo = subscriptionInfoList.get(0);
                }else if (activeSubscriptions.isEmpty()){
                    log.info("不发送邮件，原因：【当前邮件类型：{}, 但是状态为active的subscriptionInfo为空】", type);
                    return false;
                }else {
                    subscriptionInfo = activeSubscriptions.get(0);
                }
            }
        }else if (!StringUtil.isNullOrEmpty(orderNo)) {
            qwSI.lambda().eq(SubscriptionInfo::getOrderNo, orderNo)
                    .lt(SubscriptionInfo::getCurrentPeriodStart, secondsTimestamp)
                    .gt(SubscriptionInfo::getCurrentPeriodEnd, secondsTimestamp);
            subscriptionInfo = subscriptionInfoMapper.selectOne(qwSI);
            if (Objects.isNull(subscriptionInfo)){
                log.info("不发送邮件，原因：【根据order_no：{}，查询到的subscriptionInfo为空】", orderNo);
                return false;
            }
        }else {
            log.info("不发送邮件，原因：【入参中的subscriptionId，orderNo均为空】");
            return false;
        }

        QueryWrapper<PaymentInfo> qwPI = new QueryWrapper<>();
        String periodStart = DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);
        String periodEnd = DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);

        qwPI.lambda().eq(PaymentInfo::getOrderNo, subscriptionInfo.getOrderNo())
                .between(PaymentInfo::getCreateTime, periodStart, periodEnd)
                .orderByDesc(PaymentInfo::getId);
        List<PaymentInfo> paymentInfos = paymentInfoMapper.selectList(qwPI);
        if (paymentInfos.isEmpty()) {
            log.info("不发送邮件，原因：【根据order_no:{},查询到的paymentInfos为空】", orderNo);
            return false;
        }
        PaymentInfo paymentInfo = paymentInfos.get(0);
        if (StringUtil.isNullOrEmpty(type)){
            // 如果没有传入type,则使用paymentInfo中记录的类型
            // （其实这里也可以通过invoiceId查询stripe，但是记录在自己的db中可以不用每次都查，且方便查看）
            type = StringUtil.isNullOrEmpty(paymentInfo.getType()) ? "new" : paymentInfo.getType();
        }

        // todo 之后这种改成通过email-log来判断
        String key = RedisUtil.SUBSCRIPTION_SENT_EMAIL_TYPE + subscriptionInfo.getId();
        // 先判断当前订单 这个类型的邮件是否已发送过
        Boolean elementExistsInSet = redisUtil.isElementExistsInSet(key, type);
        if (!type.startsWith("reminder")  && !type.equals("cancel") && paymentInfo.getNotified() == 1 && elementExistsInSet){
            // 已经邮件通知过，直接返回
            log.info("不发送邮件，原因：【type为：{}，order_no为：{}，已经进行邮件通知】", type, orderNo);
            return true;
        }

        com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(subscriptionInfo.getAccountId());
        String userName = account.getUserName();
        String language = getLanguage(account.getLanguage(), account.getCountry(), type);
        OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(subscriptionInfo.getOrderNo());

        SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO();
        emailParamsDTO.setUsername(userName);
        emailParamsDTO.setEmail(account.getUserEmail());
        emailParamsDTO.setCountry(paymentInfo.getCountry());
        emailParamsDTO.setOrderId(paymentInfo.getId().toString());
        emailParamsDTO.setOrderRef("\"" + orderListLink + paymentInfo.getId().toString() + "\"");
        emailParamsDTO.setCreateDate(String.valueOf(paymentInfo.getCreateTime()).replace("T", " "));
        emailParamsDTO.setQuantity(String.valueOf(1));
        emailParamsDTO.setTotalFee(paymentInfo.getPayerTotal().toString());
        emailParamsDTO.setLastOrderDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodStart(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
        emailParamsDTO.setEndOfPrepaidTerm(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
        setSubscriptionParams(paymentInfo, subscriptionInfo, orderByOrderNo, emailParamsDTO, language);

        boolean b = SendEmailUtil.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
//        boolean b = emailService.subscriptionEmailReminder(type, emailParamsDTO, language, account.getUserEmail());
        if (!b) return false;

        // 邮件通知成功后，更新标志
        if (!type.startsWith("reminder") && !type.equals("cancel")){
            PaymentInfo payment = new PaymentInfo();
            payment.setId(paymentInfo.getId());
            payment.setNotified(1);
            payment.setUpdateTime(LocalDateTime.now());
            paymentInfoMapper.updateById(payment);
        }

        // 将发成功的邮件类型存入redis 避免同一个订阅重复发送相同类型的邮件  subId：type
        redisUtil.addToSet(key, type, CommonConstant.REDIS_SET_EXPIRE_TIME);
        return true;
    }

    public String getLanguage(String language, String country, String type) {
        if (StringUtil.isNullOrEmpty(language)){
            return Language.ENGLISH.name();
        } else {
            if (!StringUtil.isNullOrEmpty(type) && type.startsWith("reminder")
                    && language.equals(Language.CHINESE_SIMPLIFIED.name())
                    && !StringUtil.isNullOrEmpty(country)
                    && (country.equals("Hong Kong, China") || country.equals("Taiwan, China"))) {
                return "zh-Hant";
            }
            return language;
        }
    }

    public boolean sendEmail(String orderNo){
        SubscriptionEmailParamsDTO emailParamsDTO = new SubscriptionEmailParamsDTO();
        OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);

        com.ai.da.mapper.primary.entity.Account account = accountMapper.selectById(orderInfo.getAccountId());
        String userName = account.getUserName();
        String language = account.getLanguage();
        QueryWrapper<PaymentInfo> qwPI = new QueryWrapper<>();
        qwPI.eq("order_no", orderNo);
        List<PaymentInfo> paymentInfos = paymentInfoMapper.selectList(qwPI);
        if (paymentInfos.isEmpty()) {
            log.info("不发送邮件，原因：【根据order_no:{},查询到的paymentInfos为空】", orderNo);
            return false;
        }
        PaymentInfo paymentInfo = paymentInfos.get(0);
        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());
        emailParamsDTO.setFailMessage(orderInfo.getNote());
        emailParamsDTO.setPaymentMethod(paymentInfo.getPaymentMethod());
        emailParamsDTO.setLast4(paymentInfo.getLast4());

        boolean b = SendEmailUtil.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail());
//        boolean b = emailService.subscriptionEmailReminder("fail_new", emailParamsDTO, language, account.getUserEmail());
        if (!b) return false;

        // 邮件通知成功后，更新标志
        PaymentInfo payment = new PaymentInfo();
        payment.setId(paymentInfo.getId());
        payment.setNotified(1);
        payment.setUpdateTime(LocalDateTime.now());
        paymentInfoMapper.updateById(payment);
        return true;
    }

    public boolean sendRenewalFailEmail(String invoiceId, String subscriptionId, String orderNo){
        // 1、确认当前订单最后一笔支付为fail
        // 更新支付信息
        PaymentInfo paymentInfo;
        QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();
        if (StringUtil.isNullOrEmpty(invoiceId)){
            queryWrapper.eq("order_no", orderNo).orderByDesc("id");
            List<PaymentInfo> 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<SubscriptionInfo> 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, language);

        // 4、发邮件
        boolean b = SendEmailUtil.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail());
//        boolean b = emailService.subscriptionEmailReminder("fail_renewal", emailParamsDTO, language, account.getUserEmail());
        if (!b) return false;

        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, String language) {
        emailParamsDTO.setPaymentMethod(paymentInfo.getPaymentMethod());
        emailParamsDTO.setLast4(paymentInfo.getLast4());
        emailParamsDTO.setSubscriptionId(subscriptionInfo.getId().toString());
        emailParamsDTO.setFailMessage(orderByOrderNo.getNote());
        emailParamsDTO.setSubscriptionType(subscriptionInfo.getType());
        emailParamsDTO.setStartDate(DateUtil.changeTimeStampFormat(orderByOrderNo.getCreateTime()));
        /*if (subscriptionInfo.getType().equals("month")){
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.MonthlySubscription.getPrice()));
        } else if (subscriptionInfo.getType().equals("year")){
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.AnnualSubscription.getPrice()));
        } else if (subscriptionInfo.getType().equals("day")){
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.DailySubscription.getPrice()));
        } else {
            emailParamsDTO.setRenewalFee("?");
        }*/

        if (orderByOrderNo.getTitle().equals(ProductEnum.MonthlySubscription.getName())){
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.MonthlySubscription.getPrice()));
        } else if (orderByOrderNo.getTitle().equals(ProductEnum.Eco_MonthlySubscription.getName())){
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.Eco_MonthlySubscription.getPrice()));
        } else if (orderByOrderNo.getTitle().equals(ProductEnum.AnnualSubscription.getName())) {
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.AnnualSubscription.getPrice()));
        } else if (orderByOrderNo.getTitle().equals(ProductEnum.DailySubscription.getName())) {
            emailParamsDTO.setRenewalFee(String.valueOf(ProductEnum.DailySubscription.getPrice()));
        } else {
            emailParamsDTO.setRenewalFee("?");
        }
        if (subscriptionInfo.getStatus().equals("active")){
            if (language.equals("ENGLISH")){
                emailParamsDTO.setEndDate("When cancelled");
            }else {
                emailParamsDTO.setEndDate("手动取消订阅时");
            }
        }else {
            emailParamsDTO.setEndDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy_EEEE));
        }
        if (StringUtil.isNullOrEmpty(subscriptionInfo.getNextPayDate())){
            emailParamsDTO.setNextPayDate("N/A");
        } else {
            emailParamsDTO.setNextPayDate(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
        }
        emailParamsDTO.setRenewalTime(DateUtil.changeTimeStampFormat(subscriptionInfo.getCurrentPeriodEnd(), "seconds", CommonConstant.TIME_FORMAT_MMM_dd_yyyy));
        emailParamsDTO.setDays(subscriptionInfo.getType().equals("month") ? "7" : subscriptionInfo.getType().equals("year") ? "14" : "N/A");
    }

    /*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);

        // 转为时间戳
        long startTimestamp = startOfDay.toEpochSecond(ZoneOffset.UTC);
        long endTimestamp = endOfDay.toEpochSecond(ZoneOffset.UTC);

        QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
        qw.ge("current_period_end", startTimestamp);
        qw.lt("current_period_end", endTimestamp);
        qw.eq("status", "active");

        List<SubscriptionInfo> subscriptionInfos = subscriptionInfoMapper.selectList(qw);
        for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
            boolean b = sendEmail(subscriptionInfo.getSubscriptionId(), "reminder", 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());
        }
    }

    // 新建一个订阅 使用不会成功的付款方式(仅供测试使用)
    public String createSubscriptionTemp(String name, String email){
        Stripe.apiKey = privateKey;
        try {
            OrderInfo orderInfo = orderInfoService.createOrderByProductId(1, PayTypeEnum.STRIPE.getType(), ProductEnum.DailySubscription, null, (byte)0);

//            String customerId = getCustomer(name, email);
            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);
        }
    }

    public List<Map<String,String>> getCustomerPaymentMethod(String name, String email){
        Stripe.apiKey = privateKey;
        try {
            String customerId = getCustomer(name, email);
            Customer customer = Customer.retrieve(customerId);
            PaymentMethodCollection paymentMethodCollection = customer.listPaymentMethods();
            List<PaymentMethod> data = paymentMethodCollection.getData();
            ArrayList<Map<String,String>> resp = new ArrayList<>();
            data.forEach(paymentMethod -> {
                Map<String,String> map = new HashMap<>();
                if (paymentMethod.getType().equals("card")){
                    map.put(paymentMethod.getId(),paymentMethod.getCard().getLast4());
                }else {
                    map.put(paymentMethod.getId(),null);
                }
                resp.add(map);
            });

            return resp;
            // 方向： 向用户添加了多种付款方式，更改默认的付款方式后，默认付款方式付款失败后是否自动使用其他付款方式付款？
            // 如果是的，则需要删除能成功的付款方式，保留唯一失败的付款方式进行续订付款失败测试
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }

    }

    public String detachCustomerAllPaymentMethod(String name, String email){
        Stripe.apiKey = privateKey;
        // 方向： 向用户添加了多种付款方式，更改默认的付款方式后，默认付款方式付款失败后是否自动使用其他付款方式付款？
        // 如果是的，则需要删除能成功的付款方式，保留唯一失败的付款方式进行续订付款失败测试
        try {
            String customerId = getCustomer(name, email);
            Customer customer = Customer.retrieve(customerId);
            PaymentMethodCollection paymentMethodCollection = customer.listPaymentMethods();
            List<PaymentMethod> data = paymentMethodCollection.getData();
            data.forEach(paymentMethod -> {
                try {
                    PaymentMethod retrieve = PaymentMethod.retrieve(paymentMethod.getId());
                    PaymentMethodDetachParams params = PaymentMethodDetachParams.builder().build();
                    retrieve.detach(params);
                } catch (StripeException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    public void updateCancelReason(String subscriptionId, String reason){
        QueryWrapper<SubscriptionInfo> qw = new QueryWrapper<>();
        qw.eq("subscription_id", subscriptionId);
        SubscriptionInfo subscriptionInfo = subscriptionInfoMapper.selectOne(qw);

        if (!Objects.isNull(subscriptionInfo)) {
            subscriptionInfo.setCancelReason(reason);
            subscriptionInfoMapper.updateById(subscriptionInfo);
        }
    }

//    public String getIp(HttpServletRequest request) {
//        String ipAddress = RequestInfoUtil.getIpAddress(request);
//        if (!StringUtil.isNullOrEmpty(ipAddress)) {
//            return getIPLocation(ipAddress);
//        }
//
//        return request.getRemoteAddr();
//    }


    public String getStackTrace(Exception e, int maxLines) {
        StringBuilder sb = new StringBuilder();
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        int lines = Math.min(maxLines, stackTraceElements.length);

        for (int i = 0; i < lines; i++) {
            sb.append(stackTraceElements[i].toString()).append("\n");
        }
        // 如果堆栈信息超过 maxLines 行，添加提示
        if (stackTraceElements.length > maxLines) {
            sb.append("... (More stack trace lines truncated)\n");
        }
        return sb.toString();
    }


    /*
     * 优惠券实施：
     * 1、由管理员创建优惠券，手动设置该优惠券对应的折扣
     * 2、用户在订阅时，手动输入优惠码
     * 3、使用stripe的字段 redeem_by 管理优惠券的有效期
     * 4、promotion code与 coupons需要绑定
     * 5、用户只能使用一次优惠券
     *
     * 问题：对于一次付款和订阅，优惠码是否可以混用
     */
    public String createCoupon(CreateCouponDTO createCouponDTO){
        Stripe.apiKey = privateKey;
        CouponCreateParams.Builder couponParams = CouponCreateParams.builder()
                // 任何客户只能用一次这个优惠券
                .setDuration(CouponCreateParams.Duration.ONCE)
                // percent_off 与 amount_off 不能同时设置
                .setPercentOff(BigDecimal.valueOf(createCouponDTO.getPercentOff()));
        if (Objects.nonNull(createCouponDTO.getEndTime())){
            couponParams.setRedeemBy(createCouponDTO.getEndTime());
        }
        try {
            // 1、创建优惠券
            Coupon coupon = Coupon.create(couponParams.build());
            // 2、创建一个推广码
            PromotionCode promotionCode = createPromotionCode(coupon.getId(), createCouponDTO.getMaxRedemptions());
            // 3、落库
            ProductCoupons productCoupons = new ProductCoupons(coupon.getId(), createCouponDTO.getEndTime(), promotionCode.getId(),
                    promotionCode.getCode(), createCouponDTO.getMaxRedemptions(), createCouponDTO.getPercentOff(),
                    createCouponDTO.getCommissionRate(), createCouponDTO.getCooperator(), createCouponDTO.getRemark());
            productCoupons.setStartTime(createCouponDTO.getStartTime());
            productCoupons.setCreateTime(LocalDateTime.now());
            productCouponsMapper.insert(productCoupons);

            return promotionCode.getCode();
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    public PromotionCode createPromotionCode(String couponId, Long maxRedemption){
        Stripe.apiKey = privateKey;
        PromotionCodeCreateParams.Builder promotionCodeParams = PromotionCodeCreateParams.builder()
                .setCoupon(couponId)
                .setRestrictions(PromotionCodeCreateParams.Restrictions.builder().build());
        if (Objects.nonNull(maxRedemption)){
            promotionCodeParams.setMaxRedemptions(maxRedemption);
        }

        try {
            return PromotionCode.create(promotionCodeParams.build());
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    public ProductCoupons updateCouponsInfo(Long id, String paidCommission, String cooperator, String remark, Long startTime){
        ProductCoupons productCoupons = productCouponsMapper.selectById(id);
        if (Objects.isNull(productCoupons)){
            throw new BusinessException("unknown.Promotion.Code");
        }
        boolean flag = false;
        if (!StringUtil.isNullOrEmpty(cooperator)){
            productCoupons.setCooperator(cooperator);
            flag = true;
        }
        if (!StringUtil.isNullOrEmpty(remark)){
            productCoupons.setRemark(remark);
            flag = true;
        }
        if (Objects.nonNull(paidCommission)){
            // 将 paidCommission 从 String 转换为 BigDecimal
            if (!paidCommission.matches("-?\\d+(\\.\\d+)?")) {
                throw new BusinessException("Invalid paidCommission value: " + paidCommission);
            }
            BigDecimal paidCommissionBigDecimal = new BigDecimal(paidCommission);
            // 设置已支付佣金
            productCoupons.setPaidCommission(paidCommissionBigDecimal);
            BigDecimal commission =  Objects.isNull(productCoupons.getCommission()) ? new BigDecimal(0) : productCoupons.getCommission();
            // 计算未支付佣金
            BigDecimal unpaidCommission = commission.subtract(paidCommissionBigDecimal);
            // 设置未支付佣金，确保其不为负数
            productCoupons.setUnpaidCommission(unpaidCommission.compareTo(BigDecimal.ZERO) > 0 ? unpaidCommission : BigDecimal.ZERO);
            flag = true;
        }
        if (Objects.nonNull(startTime)){
            productCoupons.setStartTime(startTime);
            flag = true;
        }
        if (flag){
            productCoupons.setUpdateTime(LocalDateTime.now());
            productCouponsMapper.updateById(productCoupons);
        }
        return productCoupons;
    }

    public ProductCoupons checkProductCoupon(String promotionCode){
        Stripe.apiKey = privateKey;
        Long accountId = UserContext.getUserHolder().getId();
        String language = UserContext.getUserHolder().getLanguage();
        // 1、从数据库查找promotionCode对应的promotionCodeId
        ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
        if (Objects.nonNull(productCoupons)){
            // 2、查绑定的Coupons是否存在以及是否过期
            long epochSecondNow = Instant.now().getEpochSecond();
            Long redeemBy = productCoupons.getRedeemBy();
            if (Objects.nonNull(redeemBy) && redeemBy < epochSecondNow){
                throw new BusinessException("this.promotion.code.has.expired");
            } else if (Objects.nonNull(productCoupons.getStartTime()) && productCoupons.getStartTime() > epochSecondNow) {
                String startTime = DateUtil.changeTimeStampFormat(productCoupons.getStartTime(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);
                String en = "This coupon will become active on " + startTime + ". Please try again then!";
                String cn = "该优惠券尚未到生效时间，请在 " + startTime + " 后使用。";
                throw new BusinessException(language.equals("ENGLISH") ?  en : cn);
            } else {
                // 判断该用户是否有成功使用过这个推广码
                List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
                if (!paymentInfoByPromCode.isEmpty())  {
                    // 已使用过推广码，状态无效
                    if (paymentInfoByPromCode.size() > 1) {
                        log.error("用户[{}]多次成功使用优惠码[{}]", accountId, promotionCode);
                    }
                    log.info("用户[{}]已成功使用过优惠码[{}]", accountId, promotionCode);
                    throw new BusinessException("one.time.limit.per.customer");
                }
            }
        }else {
            throw new BusinessException("this.promotion.code.is.invalid");
        }
        return productCoupons;
    }

    public CheckCouponsVO checkProductCoupon(String promotionCode, Long price){
        Stripe.apiKey = privateKey;
        Long accountId = UserContext.getUserHolder().getId();
        String language = UserContext.getUserHolder().getLanguage();
        CheckCouponsVO checkCouponsVO = new CheckCouponsVO();
        // 1、从数据库查找promotionCode对应的promotionCodeId
        ProductCoupons productCoupons = productCouponsMapper.selectOne(new QueryWrapper<ProductCoupons>().eq("promotion_code", promotionCode));
        if (Objects.nonNull(productCoupons)){
            // 2、查绑定的Coupons是否存在以及是否过期
            long epochSecondNow = Instant.now().getEpochSecond();
//            Long redeemBy = productCoupons.getRedeemBy();
            if (Objects.nonNull(productCoupons.getRedeemBy()) && productCoupons.getRedeemBy() < epochSecondNow){
                String msg = BusinessException.getMessageFromResource("this.promotion.code.has.expired");
                checkCouponsVO.setMessage(msg);
                checkCouponsVO.setStatus("expired");
            }else if (Objects.nonNull(productCoupons.getStartTime()) && productCoupons.getStartTime() > epochSecondNow) {
                String startTime = DateUtil.changeTimeStampFormat(productCoupons.getStartTime(), "seconds", CommonConstant.TIME_FORMAT_yyyy_MM_dd_HH_mm_ss);
                String en = "This coupon will become active on " + startTime + ". Please try again then!";
                String cn = "该优惠券尚未到生效时间，请在 " + startTime + " 后使用。";
                checkCouponsVO.setMessage(language.equals("ENGLISH") ?  en : cn);
                checkCouponsVO.setStatus("pending");
            } else {
                // 判断该用户是否有成功使用过这个推广码
                List<PaymentInfo> paymentInfoByPromCode = paymentInfoService.getPaymentInfoByPromCode(accountId, promotionCode);
                if (paymentInfoByPromCode.isEmpty()) {
                    // 未使用过推广码，状态有效
                    checkCouponsVO.setStatus("valid");
                    checkCouponsVO.setDiscountedPrice(price * (1 - productCoupons.getPercentOff() / 100));
                } else {
                    // 已使用过推广码，状态无效
                    if (paymentInfoByPromCode.size() > 1) {
                        log.error("用户[{}]多次成功使用优惠码[{}]", accountId, promotionCode);
                    }
                    log.info("用户[{}]已成功使用过优惠码[{}]", accountId, promotionCode);
                    String msg = BusinessException.getMessageFromResource("one.time.limit.per.customer");
                    checkCouponsVO.setMessage(msg);
                    checkCouponsVO.setStatus("invalid");
                }
            }
        }else {
            String msg = BusinessException.getMessageFromResource("this.promotion.code.is.invalid");
            checkCouponsVO.setMessage(msg);
            checkCouponsVO.setStatus("invalid");
        }
        return checkCouponsVO;
    }

    public ProductCoupons getProductCoupon(String promotionCode, String promotionCodeId){
        Stripe.apiKey = privateKey;
        QueryWrapper<ProductCoupons> qw = new QueryWrapper<>();
        // 1、从数据库查找promotionCode对应的promotionCodeId
        if (!StringUtil.isNullOrEmpty(promotionCode)){
            qw.eq("promotion_code", promotionCode);
        }
        if (!StringUtil.isNullOrEmpty(promotionCodeId)){
            qw.eq("promotion_code_id", promotionCodeId);
        }
        return productCouponsMapper.selectOne(qw);
    }

    public String retrieveCoupon(String couponId){
        Stripe.apiKey = privateKey;
        try {
            Coupon coupon = Coupon.retrieve(couponId);
            log.info("retrieve的coupon: {}", coupon);
            return JSON.toJSONString(coupon);
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    public String retrievePromotionCode(String promotionCode){
        Stripe.apiKey = privateKey;
        try {
            ProductCoupons productCoupon = getProductCoupon(promotionCode, null);
            PromotionCode retrieve = PromotionCode.retrieve(productCoupon.getPromotionCodeId());
            log.info("retrieve的promotionCode: {}", retrieve);
            return JSON.toJSONString(retrieve);
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    public IPage<ProductCoupons> getAllCoupons(QueryCouponsPageDTO queryCouponsPageDTO){
        // 分页 + 按条件查询
        QueryWrapper<ProductCoupons> queryWrapper = new QueryWrapper<>();
        if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getOrderById()) && queryCouponsPageDTO.getOrderById().equals("DESC")){
            queryWrapper.orderByDesc("id");
        }
        if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getPromotionCode())){
            queryWrapper.like("promotion_code", queryCouponsPageDTO.getPromotionCode());
        }
        if (Objects.nonNull(queryCouponsPageDTO.getIsExpired())){
            long epochSecond = Instant.now().getEpochSecond();
            if (queryCouponsPageDTO.getIsExpired()){
                queryWrapper.lt("redeem_by", epochSecond);
            }else {
                queryWrapper.gt("redeem_by", epochSecond);
            }
        }
        if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getCooperator())){
            queryWrapper.like("cooperator", queryCouponsPageDTO.getCooperator());
        }
        if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getStartTime())){
            queryWrapper.gt("create_time", queryCouponsPageDTO.getStartTime());
        }
        if (!StringUtil.isNullOrEmpty(queryCouponsPageDTO.getEndTime())){
            queryWrapper.lt("create_time", queryCouponsPageDTO.getEndTime());
        }

        return productCouponsMapper.selectPage(new Page<>(queryCouponsPageDTO.getPage(), queryCouponsPageDTO.getSize()), queryWrapper);
    }

    @Transactional
    public void deleteCoupon(Long id){
        Stripe.apiKey = privateKey;
        ProductCoupons productCoupons = productCouponsMapper.selectById(id);
        if (Objects.isNull(productCoupons)){
            throw new BusinessException("unknown.Promotion.Code");
        }
        try {
            Coupon coupon = Coupon.retrieve(productCoupons.getCouponId());
            coupon.delete();
            log.info("coupon {} 删除成功", productCoupons.getCouponId());
            productCouponsMapper.deleteById(id);
        } catch (StripeException e) {
            log.error("未知coupons,无法通过couponId: {} 获得Coupons", productCoupons.getCouponId());
        }
    }

}
