package com.yida.data.mall.service.impl;

import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yida.data.common.core.common.ResultMsgType;
import com.yida.data.common.core.entity.CurrentUser;
import com.yida.data.common.core.entity.apply.EduApplyProductOrder;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
import com.yida.data.common.core.entity.constant.StringConstant;
import com.yida.data.common.core.entity.constant.UnionPayConstant;
import com.yida.data.common.core.entity.mall.*;
import com.yida.data.common.core.entity.mall.enums.BillItemOptionEnum;
import com.yida.data.common.core.entity.mall.enums.BillItemPayStatusEnum;
import com.yida.data.common.core.entity.mall.enums.MustPayEnum;
import com.yida.data.common.core.entity.pay.UnionPayOrder;
import com.yida.data.common.core.entity.pay.UnionRefund;
import com.yida.data.common.core.entity.system.EduDeptPayType;
import com.yida.data.common.core.entity.system.EduPayWxConfig;
import com.yida.data.common.core.entity.system.UnionPayConfig;
import com.yida.data.common.core.entity.user.EduParent;
import com.yida.data.common.core.entity.user.EduStudent;
import com.yida.data.common.core.enums.*;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.*;
import com.yida.data.common.service.CommonService;
import com.yida.data.mall.dto.h5.CreateBillOrderDTO;
import com.yida.data.mall.dto.h5.H5BillInfoDTO;
import com.yida.data.mall.dto.h5.H5BillOrderPageDTO;
import com.yida.data.mall.dto.h5.H5BillSelectDTO;
import com.yida.data.mall.mapper.EduBillMapper;
import com.yida.data.mall.mapper.EduBillOrderMapper;
import com.yida.data.mall.service.*;
import com.yida.data.mall.utils.ThreadPoolUtil;
import com.yida.data.mall.vo.h5.*;
import com.yida.data.user.feign.RemoteStudentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

/**
 * 缴费账单前端 Service接口
 *
 * @author ZYJ
 * @date 2022-3-30 19:47:02
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class H5EduBillServiceImpl implements H5EduBillService {

    private final CommonService commonService;
    private final RemoteStudentService remoteStudentService;

    private final EduBillService eduBillService;
    private final EduBillItemService eduBillItemService;
    private final EduBillItemSubService eduBillItemSubService;
    private final EduBillOrderService eduBillOrderService;
    private final EduBillOrderItemService eduBillOrderItemService;
    private final EduBillOrderItemSubService eduBillOrderItemSubService;
    private final EduBillOrderStudentService eduBillOrderStudentService;
    private final EduBillOrderRefundService eduBillOrderRefundService;

    private final EduBillMapper eduBillMapper;
    private final EduBillOrderMapper eduBillOrderMapper;

    private final RedisService redisService;
    private final UnionPayPublicUtil unionPayPublicUtil;

    private static final ExecutorService BILL_ORDER_SUCCESS_POOL = ThreadPoolUtil.getBillOrderThreadPool();
    private static final Snowflake ORDER_CODE_GENERATOR = new Snowflake(1, 1);

    private static final String BILL_WX_PAY_NOTIFY_URL = "mall/in/eduBillOrder/wxPayNotify";
    private static final String BILL_WX_REFUND_NOTIFY_URL = "mall/in/eduBillOrder/wxRefundNotify";

    @Value("${febs.apiUrl}")
    private String apiUrl;

    @Value("${febs.pageUrl}")
    private String pageUrl;

    @Override
    public List<H5BillSelectVO> listH5Bill(H5BillSelectDTO h5BillSelectDTO) {
        // 查询前台账单列表
        List<H5BillSelectVO> list = eduBillMapper.listH5Bill(h5BillSelectDTO);
        list.forEach(h5BillSelectVO -> {
            // 查询已支付金额
            BigDecimal payMoney = eduBillOrderService.sumPayOrderPrice(h5BillSelectVO.getBillId(), h5BillSelectDTO.getStudentId());
            if (Objects.isNull(payMoney)) {
                payMoney = BigDecimal.ZERO;
            }
            h5BillSelectVO.setPayMoney(payMoney);
        });
        return list;
    }

    @Override
    public H5BillSelectVO getH5BillInfo(H5BillInfoDTO h5BillInfoDTO) {
        H5BillSelectVO h5BillSelectVO = eduBillMapper.getH5BillInfo(h5BillInfoDTO);
        if (Objects.nonNull(h5BillSelectVO)) {
            // 查询学生信息
            EduStudent student = remoteStudentService.getStudentNoPermission(h5BillInfoDTO.getStudentId()).getData();
            if (Objects.isNull(student)) {
                throw new FebsException("学生信息错误");
            }
            h5BillSelectVO.setStudentFamilyType(student.getFamilyType());

            // TODO 2022/4/22 需要优化查询学生已缴费数据的查询
            // 判断支付数量
            int payNumber = 0;
            // 判断已支付选项
            for (H5BillItemVO itemVO : h5BillSelectVO.getItemList()) {
                H5BillItemVO itemPayInfo = eduBillOrderItemService.getItemPayInfo(h5BillInfoDTO.getBillId(), h5BillInfoDTO.getStudentId(), itemVO.getItemId());
                // 没有支付数据
                if (Objects.isNull(itemPayInfo)) {
                    itemVO.setChecked(MustPayEnum.MUST_PAY_STATUS.getStatus().equals(itemVO.getMustPay()));
                    itemVO.setItemPayStatus(BillItemPayStatusEnum.NOT_PAY.getStatus());
                    // 设置是否选中
                    itemVO.getSubItemList().forEach(subItemVO -> subItemVO.setChecked(false));
                    continue;
                }
                payNumber++;
                // 单选
                if (BillItemOptionEnum.ONE_SELECT_ONE.getStatus().equals(itemVO.getItemOption())) {
                    itemVO.setItemPayStatus(BillItemPayStatusEnum.PAY_SUCCESS.getStatus());
                } else {
                    // 多项选一项、多项选多项
                    List<Long> subItemIdList = itemPayInfo.getSubItemList().stream().map(H5BillItemSubVO::getSubItemId).collect(Collectors.toList());
                    itemVO.getSubItemList().forEach(subItemVO -> {
                        // 设置是否选中
                        subItemVO.setChecked(false);
                        // 设置支付状态
                        subItemVO.setSubItemPayStatus(subItemIdList.contains(subItemVO.getSubItemId()) ?
                                BillItemPayStatusEnum.PAY_SUCCESS.getStatus() :
                                BillItemPayStatusEnum.NOT_PAY.getStatus());
                    });
                    // 判断单项选择一项是否购买
                    if (CollUtil.isNotEmpty(subItemIdList)
                            && BillItemOptionEnum.MANY_SELECT_ONE.getStatus().equals(itemVO.getItemOption())) {
                        itemVO.setItemPayStatus(BillItemPayStatusEnum.PAY_SUCCESS.getStatus());
                    }
                }
            }
            // 判断是否显示支付按钮 TODO 2022/4/22 需要判断是否还有未完成的订单
            boolean showPay = payNumber != h5BillSelectVO.getItemList().size();
            h5BillSelectVO.setShowPay(showPay);
            // 判断支付方式
            EduDeptPayType payType = commonService.getDeptPayWay(h5BillInfoDTO.getDeptId());
            h5BillSelectVO.setPayType(payType);
        } else {
            throw new FebsException("当前账单暂时无法缴费");
        }
        return h5BillSelectVO;
    }

    @Override
    public BillPayInfoVO createBillOrder(CreateBillOrderDTO createBillOrderDTO) {

        CurrentUser currentUser = FebsUtil.getCurrentUser();
        // 判断是否有下单缓存信息
        BillOrderStudentCacheVO existBillOrderStudent = (BillOrderStudentCacheVO) redisService.get(
                CachePrefixConstant.BILL_ORDER_STUDENT + createBillOrderDTO.getStudentId().toString()
                        + StringConstant.DOT + createBillOrderDTO.getBillId().toString());
        if (Objects.nonNull(existBillOrderStudent)) {
            throw new FebsException(String.format("%s%s正在支付中, 请耐心等待",
                    existBillOrderStudent.getStudentName(), existBillOrderStudent.getParentType()));
        }
        if (StringUtils.isEmpty(createBillOrderDTO.getPayWay()) ||
                PayWay.ALIPAY.getValue().equals(createBillOrderDTO.getPayWay())) {
            throw new FebsException(ResultMsgType.PAY_TYPE_ERROR.getValue());
        }
        // 查询账单信息
        EduBill bill = eduBillService.getById(createBillOrderDTO.getBillId());
        if (Objects.isNull(bill)
                || BillStatusEnum.DISABLE_STATUS.getStatus().equals(bill.getEnableStatus())
                || bill.getBillEndDate().isBefore(LocalDateTime.now())) {
            throw new FebsException("当前账单暂时无法缴费");
        }
        // 查询学生信息
        EduStudent student = remoteStudentService.getStudentNoPermission(createBillOrderDTO.getStudentId()).getData();
        if (Objects.isNull(student)) {
            throw new FebsException("学生信息错误");
        }
        // 家长信息
        List<EduParent> parents = student.getParents();
        EduParent orderParent = parents.stream().filter(
                x -> currentUser.getMobile().equals(x.getMobile())).collect(Collectors.toList()).get(0);
        if (Objects.isNull(orderParent)) {
            throw new FebsException("家长信息错误");
        }

        String orderCode = "";
        try {
            // 订单号 需要根据支付方式生成不同的订单号
            String orderNo = "";
            if (PayWay.WEI_XIN.getValue().equals(createBillOrderDTO.getPayWay())) {
                // 微信
                orderNo = String.valueOf(ORDER_CODE_GENERATOR.nextId());
            } else if (PayWay.UNION.getValue().equals(createBillOrderDTO.getPayWay())) {
                // 银联
                // 获取银联商户号相关信息
                UnionPayConfig config = commonService.getUnionPayConfigByDept(student.getSchoolId());
                orderNo = UnionPayUtil.getOrderNumber(Objects.nonNull(config.getParentConfig())
                        ? config.getParentConfig().getSourceNumber() : config.getSourceNumber());
            }
            if (StringUtils.isEmpty(orderNo)) {
                log.error("生成订单号失败！");
                throw new FebsException(ResultMsgType.ORDER_FAIL.getValue());
            }
            orderCode = orderNo;
            BillOrderStudentCacheVO billOrderStudentCacheVO = BillOrderStudentCacheVO.builder()
                    .billId(createBillOrderDTO.getBillId())
                    .studentId(createBillOrderDTO.getStudentId())
                    .studentName(student.getStuName())
                    .parentId(orderParent.getId())
                    .parentType(orderParent.getParentType())
                    .mobile(currentUser.getMobile())
                    .orderNo(orderNo)
                    .build();
            // 添加学生账单订单缓存
            redisService.set(CachePrefixConstant.BILL_ORDER_STUDENT + createBillOrderDTO.getStudentId().toString()
                    + StringConstant.DOT + createBillOrderDTO.getBillId().toString(), billOrderStudentCacheVO, 900L);

            return checkBillOrder(createBillOrderDTO, bill, student, orderParent, currentUser, orderNo);
        } catch (Exception e) {
            // 删除学生账单订单缓存
            redisService.del(CachePrefixConstant.BILL_ORDER_STUDENT + createBillOrderDTO.getStudentId()
                    + StringConstant.DOT + createBillOrderDTO.getBillId());
            // 删除账单订单对应缓存
            if (StringUtils.isNotBlank(orderCode)) {
                redisService.del(CachePrefixConstant.BILL_ORDER + orderCode);
                redisService.del(CachePrefixConstant.BILL_ORDER_FLAG + orderCode);
            }
            throw new FebsException(e.getMessage());
        }
    }

    /**
     * 设置订单基本信息
     *
     * @param createBillOrderDTO 生成账单订单请求类
     * @param bill               账单信息
     * @param student            学生信息
     * @param orderParent        下单家长
     * @param currentUser        登录用户信息
     * @param orderNo            订单号
     * @return com.yida.data.mall.vo.h5.BillPayInfoVO
     * @author ZYJ
     * @date 2022/4/1 14:56
     */
    private BillPayInfoVO checkBillOrder(CreateBillOrderDTO createBillOrderDTO, EduBill bill,
                                         EduStudent student, EduParent orderParent,
                                         CurrentUser currentUser, String orderNo) {
        // 订单总金额
        BigDecimal totalMoney = BigDecimal.ZERO;
        // 订单减免金额
        BigDecimal reductionMoney = BigDecimal.ZERO;
        // 学生家庭类型
        String studentFamilyType = student.getFamilyType();
        // 保存的订单项目集合
        List<EduBillOrderItem> orderItemList = new ArrayList<>();

        // 判断账单项目
        for (CreateBillOrderDTO.CreateBillOrderItemDTO itemDTO : createBillOrderDTO.getItemList()) {
            // 根据项目id查询具体信息
            EduBillItem billItem = eduBillItemService.getById(itemDTO.getItemId());
            String itemFamilyType = billItem.getFamilyType();
            // 判断当前学生能否减免
            boolean reductionFlag = false;
            if (ReductionStatusEnum.IS_REDUCTION_STATUS.getStatus().equals(billItem.getReductionStatus()) &&
                    StringUtils.isNotBlank(studentFamilyType) && StringUtils.isNotBlank(itemFamilyType)) {
                String[] studentFamilyArray = studentFamilyType.split(StringConstant.COMMA);
                for (String studentFamily : studentFamilyArray) {
                    if (itemFamilyType.contains(studentFamily)) {
                        reductionFlag = true;
                        break;
                    }
                }
            }
            // 不定费项时的自定义金额
            if (Objects.nonNull(itemDTO.getItemPrice())) {
                billItem.setItemPrice(itemDTO.getItemPrice());
            }
            // 账单订单项目信息
            EduBillOrderItem eduBillOrderItem = new EduBillOrderItem();
            eduBillOrderItem.setBillId(createBillOrderDTO.getBillId());
            eduBillOrderItem.setStudentId(student.getId());
            eduBillOrderItem.setItemId(billItem.getId());
            eduBillOrderItem.setItemName(billItem.getItemName());
            eduBillOrderItem.setItemPrice(billItem.getItemPrice());
            eduBillOrderItem.setReductionPrice(BigDecimal.ZERO);
            // 保存的订单项目选项集合
            List<EduBillOrderItemSub> orderItemSubList = new ArrayList<>();

            // 前端选择的项目选项列表
            List<CreateBillOrderDTO.CreateBillOrderSubItemDTO> subItemList = itemDTO.getSubItemList();
            // 单选
            if (BillItemOptionEnum.ONE_SELECT_ONE.getStatus().equals(billItem.getItemOption())) {
                totalMoney = NumberUtil.add(totalMoney, billItem.getItemPrice());
                // 减免
                if (reductionFlag) {
                    reductionMoney = NumberUtil.add(reductionMoney, billItem.getReductionPrice());
                    eduBillOrderItem.setReductionPrice(billItem.getReductionPrice());
                }
            } else {
                if (BillItemOptionEnum.MANY_SELECT_ONE.getStatus().equals(billItem.getItemOption())) {
                    // 多项选一项
                    if (CollUtil.isEmpty(subItemList) || subItemList.size() > 1) {
                        throw new FebsException(String.format("%s仅支持选择一个选项", billItem.getItemName()));
                    }
                    // 减免
                    if (reductionFlag) {
                        reductionMoney = NumberUtil.add(reductionMoney, billItem.getReductionPrice());
                        eduBillOrderItem.setReductionPrice(billItem.getReductionPrice());
                    }
                }
                // 多项选多项 TODO 2022/4/12 暂时没有多项选多项
//                else {
//                    if (CollUtil.isEmpty(subItemList)) {
//                        throw new FebsException(String.format("请选择%s缴费选项", billItem.getItemName()));
//                    }
//                    // 查询是否有缴费信息  不能重复减免
//                    H5BillItemVO itemPayInfo = eduBillOrderItemService.getItemPayInfo(
//                            createBillOrderDTO.getBillId(), createBillOrderDTO.getStudentId(), billItem.getId());
//                    // 减免
//                    if ((Objects.isNull(itemPayInfo) || CollUtil.isEmpty(itemPayInfo.getSubItemList())) && reductionFlag) {
//                        reductionMoney = NumberUtil.add(reductionMoney, billItem.getReductionPrice());
//                        eduBillOrderItem.setReductionPrice(billItem.getReductionPrice());
//                    }
//                }

                // 查询子选项信息
                for (CreateBillOrderDTO.CreateBillOrderSubItemDTO subItemDTO : subItemList) {
                    EduBillItemSub subItem = eduBillItemSubService.getById(subItemDTO.getSubItemId());
                    totalMoney = NumberUtil.add(totalMoney, subItem.getSubItemPrice());
                    // 设置订单项目子选项信息
                    EduBillOrderItemSub eduBillOrderItemSub = new EduBillOrderItemSub();
                    eduBillOrderItemSub.setBillId(createBillOrderDTO.getBillId());
                    eduBillOrderItemSub.setStudentId(student.getId());
                    eduBillOrderItemSub.setItemId(billItem.getId());
                    eduBillOrderItemSub.setSubItemId(subItem.getId());
                    eduBillOrderItemSub.setSubItemName(subItem.getSubItemName());
                    eduBillOrderItemSub.setSubItemPrice(subItem.getSubItemPrice());
                    orderItemSubList.add(eduBillOrderItemSub);
                }
            }
            eduBillOrderItem.setOrderItemSubList(orderItemSubList);
            orderItemList.add(eduBillOrderItem);
        }
        return createOrder(orderItemList, createBillOrderDTO, student, totalMoney,
                reductionMoney, bill, orderParent, currentUser, orderNo);
    }

    /**
     * 生成订单并返回支付信息
     *
     * @param orderItemList      订单项目集合
     * @param createBillOrderDTO 生成账单订单请求类
     * @param student            学生信息
     * @param totalMoney         订单总金额
     * @param reductionMoney     减免金额
     * @param bill               账单信息
     * @param orderParent        下单家长
     * @param currentUser        登录用户信息
     * @param orderNo            订单号
     * @return com.yida.data.mall.vo.h5.BillPayInfoVO
     * @author ZYJ
     * @date 2022/4/1 14:50
     */
    private BillPayInfoVO createOrder(List<EduBillOrderItem> orderItemList, CreateBillOrderDTO createBillOrderDTO,
                                      EduStudent student, BigDecimal totalMoney, BigDecimal reductionMoney,
                                      EduBill bill, EduParent orderParent, CurrentUser currentUser, String orderNo) {
        // 生成账单订单信息
        EduBillOrder eduBillOrder = new EduBillOrder();
        eduBillOrder.setOrderCode(orderNo);
        // 流水号
        eduBillOrder.setTransactionNumber(PayUtil.generateSerialNumber("P", student.getSchoolId(), ""));
        eduBillOrder.setStudentId(student.getId());
        eduBillOrder.setBillId(bill.getId());
        eduBillOrder.setBillTitle(bill.getBillTitle());
        eduBillOrder.setTotalMoney(totalMoney);
        // 判断实际金额
        eduBillOrder.setPracticalMoney(NumberUtil.isLessOrEqual(totalMoney, reductionMoney)
                ? BigDecimal.ZERO : NumberUtil.sub(totalMoney, reductionMoney));
        eduBillOrder.setPhone(currentUser.getMobile());
        // 订单状态和支付状态
        eduBillOrder.setOrderStatus(OrderStatusEnum.NOT_PAY.getStatus());
        eduBillOrder.setPayStatus(PayStatusEnum.NOT_PAY.getStatus());
        eduBillOrder.setPayWay(createBillOrderDTO.getPayWay());
        eduBillOrder.setPayType(createBillOrderDTO.getPayType());
        eduBillOrder.setDeptId(student.getSchoolId());
        // 保存订单主体信息
        eduBillOrderService.save(eduBillOrder);
        // 保存账单订单相关信息
        saveBillOrderInfo(eduBillOrder, orderItemList, student, orderParent);

        // 返回值
        BillPayInfoVO payInfoVO = new BillPayInfoVO();
        // 金额为0，直接为成功订单
        if (NumberUtil.isLessOrEqual(eduBillOrder.getPracticalMoney(), BigDecimal.ZERO)) {
            billOrderPaySuccess(eduBillOrder.getOrderCode(), LocalDateTime.now());
            payInfoVO.setNeedToPay(false);
            payInfoVO.setOrderCode(eduBillOrder.getOrderCode());
        } else {
            // 下单返回信息
            String result = placeOrder(bill, student, eduBillOrder, orderParent);
            // 添加账单订单缓存
            redisService.set(CachePrefixConstant.BILL_ORDER + eduBillOrder.getOrderCode(), eduBillOrder, 900L);
            redisService.set(CachePrefixConstant.BILL_ORDER_FLAG + eduBillOrder.getOrderCode(), result, 900L);

            payInfoVO = getPayInfo(eduBillOrder.getOrderCode(), result, eduBillOrder.getDeptId(), true,
                    createBillOrderDTO.getPayWay(), createBillOrderDTO.getPayType());
        }
        return payInfoVO;
    }

    /**
     * 保存账单订单相关信息
     *
     * @param eduBillOrder  账单订单
     * @param orderItemList 订单项目集合
     * @param student       学生信息
     * @param parent        下单家长信息
     * @author ZYJ
     * @date 2022/4/1 15:13
     */
    private void saveBillOrderInfo(EduBillOrder eduBillOrder, List<EduBillOrderItem> orderItemList,
                                   EduStudent student, EduParent parent) {
        // 保存缴费项目信息
        orderItemList.forEach(eduBillOrderItem -> {
            eduBillOrderItem.setOrderId(eduBillOrder.getOrderId());
            eduBillOrderItemService.save(eduBillOrderItem);
            // 保存选项信息
            if (CollUtil.isNotEmpty(eduBillOrderItem.getOrderItemSubList())) {
                eduBillOrderItem.getOrderItemSubList().forEach(eduBillOrderItemSub -> {
                    eduBillOrderItemSub.setOrderItemId(eduBillOrderItem.getId());
                    eduBillOrderItemSub.setOrderId(eduBillOrder.getOrderId());
                    eduBillOrderItemSubService.save(eduBillOrderItemSub);
                });
            }
        });
        // 保存学生信息
        EduBillOrderStudent eduBillOrderStudent = new EduBillOrderStudent();
        BeanUtils.copyProperties(student, eduBillOrderStudent);
        eduBillOrderStudent.setId(null);
        eduBillOrderStudent.setOrderId(eduBillOrder.getOrderId());
        eduBillOrderStudent.setBillId(eduBillOrder.getBillId());
        eduBillOrderStudent.setStudentId(student.getId());
        eduBillOrderStudent.setStudentName(student.getStuName());
        eduBillOrderStudent.setStudentNumber(student.getStuNumber());
        eduBillOrderStudent.setStudentFamilyType(student.getFamilyType());
        eduBillOrderStudent.setStudentParentType(parent.getParentType());
        eduBillOrderStudentService.save(eduBillOrderStudent);
    }

    /**
     * 调用第三方接口生成支付信息
     *
     * @param student      学生信息
     * @param bill         账单信息
     * @param eduBillOrder 订单信息
     * @param orderParent  下单家长
     * @return java.lang.String
     * @author ZYJ
     * @date 2022/6/23 17:35
     */
    private String placeOrder(EduBill bill, EduStudent student,
                              EduBillOrder eduBillOrder, EduParent orderParent) {
        // 下单返回信息
        String result = "";
        try {
            if (PayWay.WEI_XIN.getValue().equals(eduBillOrder.getPayWay())) {
                // 微信支付
                EduPayWxConfig config = commonService.getPayWxConfigBySchool(student.getSchoolId());
                EduApplyProductOrder order = new EduApplyProductOrder();
                order.setProductName(bill.getBillTitle());
                order.setOrderCode(eduBillOrder.getOrderCode());
                order.setPraccticalMoney(eduBillOrder.getPracticalMoney().doubleValue());
                String ip = FebsUtil.getHttpServletRequestIpAddress();
                // js api下单
                if (WxPayTypeEnum.JS_TYPE.getStatus().equals(eduBillOrder.getPayType())) {
                    result = WxPayUtil.placeOrderJs(config, order, orderParent.getWxPublicOpenId(), ip,
                            apiUrl + BILL_WX_PAY_NOTIFY_URL);
                } else {
                    // h5下单
                    result = WxPayUtil.placeOrderH5(config, order, ip, apiUrl + BILL_WX_PAY_NOTIFY_URL);
                }
            } else if (PayWay.UNION.getValue().equals(eduBillOrder.getPayWay())) {
                // 获取对应支付类型枚举
                UnionPayTypeEnum unionPayTypeEnum = EnumUtils.valueOf(UnionPayTypeEnum.class, eduBillOrder.getPayType(),
                        UnionPayTypeEnum.class.getMethod("getType"));
                // 银联云闪付
                result = UnionPayUtil.placeOrder(commonService.getUnionPayConfigByDept(student.getSchoolId()),
                        UnionPayOrder.builder()
                                .orderNo(eduBillOrder.getOrderCode())
                                .transactionNumber(eduBillOrder.getTransactionNumber())
                                .payMoney(eduBillOrder.getPracticalMoney())
                                .notifyUrl(apiUrl + UnionPayConstant.BILL_UNION_PAY_NOTIFY_URL)
                                .returnUrl(pageUrl + UnionPayConstant.BILL_UNION_PAY_RETURN_URL)
                                .payTypeEnum(unionPayTypeEnum)
                                .build());
            }
        } catch (Exception e) {
            log.error("账单下单失败", e);
            throw new FebsException(ResultMsgType.ORDER_FAIL.getValue());
        }
        if (StringUtils.isEmpty(result)) {
            log.error("账单下单失败, 没有支付信息");
            throw new FebsException(ResultMsgType.ORDER_FAIL.getValue());
        }
        return result;
    }

    /**
     * 获取订单支付信息
     *
     * @param orderCode     订单号
     * @param billOrderFlag 微信js支付prepayId, h5支付跳转地址
     * @param deptId        学校id
     * @param needToPay     是否需要跳转支付
     * @param payWay        支付方式 {@link com.yida.data.common.core.enums.PayWay}
     * @param payType       支付类型
     * @return com.yida.data.mall.vo.h5.BillPayInfoVO
     * @author ZYJ
     * @date 2022/6/24 14:37
     */
    public BillPayInfoVO getPayInfo(String orderCode, String billOrderFlag, Long deptId,
                                    boolean needToPay, String payWay, Integer payType) {
        BillPayInfoVO billPayInfoVO = null;
        if (PayWay.WEI_XIN.getValue().equals(payWay)) {
            // 微信
            billPayInfoVO = getWxJsSign(orderCode, billOrderFlag, deptId, needToPay, payType);
        } else if (PayWay.UNION.getValue().equals(payWay)) {
            // 银联
            billPayInfoVO = BillPayInfoVO.builder()
                    .orderCode(orderCode)
                    .needToPay(needToPay)
                    .payWay(payWay)
                    .payType(payType)
                    .payDomain(UnionPayConstant.URL)
                    .payUrl(billOrderFlag)
                    .build();
        }
        return billPayInfoVO;
    }

    /**
     * 生成微信支付信息
     *
     * @param orderCode     订单号
     * @param billOrderFlag 微信js支付prepayId, h5支付跳转地址
     * @param deptId        学校id
     * @param needToPay     是否需要跳转支付
     * @param wxPayType     微信支付类型 {@link WxPayTypeEnum}
     * @return com.yida.data.mall.vo.h5.BillPayInfoVO
     * @author ZYJ
     * @date 2022/6/24 14:39
     */
    public BillPayInfoVO getWxJsSign(String orderCode, String billOrderFlag, Long deptId,
                                     boolean needToPay, Integer wxPayType) {
        BillPayInfoVO infoVO = new BillPayInfoVO();
        infoVO.setOrderCode(orderCode);
        infoVO.setNeedToPay(needToPay);
        infoVO.setPayWay(PayWay.WEI_XIN.getValue());
        infoVO.setPayType(wxPayType);

        // js api类型
        if (Objects.isNull(wxPayType) || WxPayTypeEnum.JS_TYPE.getStatus().equals(wxPayType)) {
            EduPayWxConfig config = commonService.getPayWxConfigBySchool(deptId);
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = RandomUtil.randomString(32);
            String packages = "prepay_id=" + billOrderFlag;
            String code = config.getWxPublicId() + "\n" + timestamp + "\n" + nonceStr + "\n" + packages + "\n";
            Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, config.getApiCreditPrivateKeyNoHead(),
                    null);
            byte[] res = sign.sign(code.getBytes());
            String signRes = Base64.encode(res);
            infoVO.setWxAppId(config.getWxPublicId());
            infoVO.setWxSign(signRes);
            infoVO.setWxTimestamp(timestamp);
            infoVO.setWxPackage(packages);
            infoVO.setWxNonceStr(nonceStr);
        } else {
            infoVO.setPayUrl(billOrderFlag);
        }
        return infoVO;
    }

    @Override
    public void billOrderSuccess(String orderCode) {

        BILL_ORDER_SUCCESS_POOL.execute(() -> {
            EduBillOrder order = eduBillOrderService.getOne(new LambdaQueryWrapper<>(new EduBillOrder())
                    .eq(EduBillOrder::getOrderCode, orderCode));

            if (Objects.nonNull(order)
                    && (OrderStatusEnum.NOT_PAY.getStatus().equals(order.getOrderStatus()) || OrderStatusEnum.USER_PAYING.getStatus().equals(order.getOrderStatus()))) {
                if (PayWay.WEI_XIN.getValue().equals(order.getPayWay())) {
                    // 微信
                    wxBillOrderSuccess(order);
                } else if (PayWay.UNION.getValue().equals(order.getPayWay())) {
                    // 银联
                    unionBillOrderSuccess(order);
                }
            }
        });
    }

    /**
     * 处理微信查询订单
     *
     * @param order 订单信息
     * @author ZYJ
     * @date 2022/6/27 17:51
     */
    private void wxBillOrderSuccess(EduBillOrder order) {
        boolean flag = false;
        // 订单号
        String orderCode = order.getOrderCode();
        // 查询学校微信配置信息
        EduPayWxConfig config = commonService.getPayWxConfigBySchool(order.getDeptId());
        while (LocalDateTimeUtil.between(order.getCreateDate(), LocalDateTime.now()).getSeconds() < 900L) {
            try {
                JSONObject resJson = JSONUtil.parseObj(WxPayUtil.queryOrder(config, orderCode));
                String tradeState = resJson.getStr("trade_state");
                if (WxTradeStateEnum.NOT_PAY.getStatus().equals(tradeState)) {
                    continue;
                }
                WxTradeStateEnum wxTradeStateEnum = EnumUtils.valueOf(WxTradeStateEnum.class, tradeState,
                        WxTradeStateEnum.class.getMethod("getStatus"));
                switch (Objects.requireNonNull(wxTradeStateEnum)) {
                    // 支付成功
                    case SUCCESS:
                        billOrderPaySuccess(orderCode, DateUtil.parseLocalDateTime(resJson.getStr("success_time"), "yyyy-MM" + "-dd'T" + "'HH:mm" + ":ssXXX"));
                        flag = true;
                        break;
                    // 作废订单
                    case CLOSED:
                    case REVOKED:
                    case PAY_ERROR:
                        order.setOrderStatus(OrderStatusEnum.CLOSED.getStatus());
                        order.setPayStatus(PayStatusEnum.CANCEL.getStatus());
                        eduBillOrderService.updateById(order);
                        flag = true;
                        break;
                    default:
                        break;
                }
                if (flag) {
                    break;
                }
                Thread.sleep(1000L);
            } catch (Exception e) {
                log.error("账单订单(微信支付方式)完成失败, orderNo: {}", orderCode, e);
            }
        }
    }

    /**
     * 处理银联查询订单
     *
     * @param order 订单信息
     * @author ZYJ
     * @date 2022/6/27 17:51
     */
    private void unionBillOrderSuccess(EduBillOrder order) {
        boolean flag = false;
        // 订单号
        String orderCode = order.getOrderCode();
        // 查询学校银联配置信息
        UnionPayConfig config = commonService.getUnionPayConfigByDept(order.getDeptId());
        // 主商户信息
        UnionPayConfig parentConfig = config.getParentConfig();

        while (LocalDateTimeUtil.between(order.getCreateDate(), LocalDateTime.now()).getSeconds() < 900L) {
            try {
                JSONObject resJson = UnionPayUtil.queryOrder(config,
                        unionPayPublicUtil.getAccessToken(
                                Objects.nonNull(parentConfig) ? parentConfig.getAppId() : config.getAppId(),
                                Objects.nonNull(parentConfig) ? parentConfig.getAppKey() : config.getAppKey()
                        ), orderCode);
                // 支付状态
                String status = (String) resJson.get("status");
                if (UnionTradeStateEnum.NEW_ORDER.getStatus().equals(status)
                        || UnionTradeStateEnum.UNKNOWN.getStatus().equals(status)
                        || UnionTradeStateEnum.WAIT_BUYER_PAY.getStatus().equals(status)) {
                    return;
                }
                UnionTradeStateEnum tradeStateEnum = EnumUtils.valueOf(UnionTradeStateEnum.class, status,
                        UnionTradeStateEnum.class.getMethod("getStatus"));
                switch (Objects.requireNonNull(tradeStateEnum)) {
                    // 支付成功
                    case TRADE_SUCCESS:
                        billOrderPaySuccess(order.getOrderCode(),
                                LocalDateTimeUtil.parse((String) resJson.get("payTime"), DatePattern.NORM_DATETIME_PATTERN));
                        flag = true;
                        break;
                    // 订单关闭
                    case TRADE_CLOSED:
                        order.setOrderStatus(OrderStatusEnum.CLOSED.getStatus());
                        order.setPayStatus(PayStatusEnum.CANCEL.getStatus());
                        eduBillOrderService.updateById(order);
                        flag = true;
                        break;
                    default:
                        break;
                }
                if (flag) {
                    break;
                }
                Thread.sleep(1000L);
            } catch (Exception e) {
                log.error("账单订单(云闪付支付方式)完成失败, orderNo: {}", orderCode, e);
            }
        }
    }

    @Override
    public void billOrderPaySuccess(String orderCode, LocalDateTime payDate) {
        EduBillOrder order = eduBillOrderService.getOne(new LambdaQueryWrapper<>(new EduBillOrder()).eq(EduBillOrder::getOrderCode, orderCode));
        order.setPayDate(payDate);
        order.setOrderStatus(OrderStatusEnum.PAY_SUCCESS.getStatus());
        order.setPayStatus(PayStatusEnum.PAY_SUCCESS.getStatus());
        eduBillOrderService.updateById(order);

        // 删除学生账单订单缓存
        redisService.del(CachePrefixConstant.BILL_ORDER_STUDENT + order.getStudentId().toString() + StringConstant.DOT + order.getBillId().toString());
        // 删除账单订单对应缓存
        redisService.del(CachePrefixConstant.BILL_ORDER + orderCode);
        redisService.del(CachePrefixConstant.BILL_ORDER_FLAG + orderCode);
    }

    @Override
    public IPage<H5BillOrderPageVO> listH5BillOrderPage(H5BillOrderPageDTO dto) {
        return eduBillOrderMapper.listH5BillOrderPage(dto.toPage(), dto);
    }

    @Override
    public H5BillOrderInfoVO getH5BillOrderInfo(Long billOrderId, String orderCode) {
        H5BillOrderInfoVO h5BillOrderInfo = eduBillOrderMapper.getH5BillOrderInfo(billOrderId, orderCode);
        if (Objects.nonNull(h5BillOrderInfo)) {
            h5BillOrderInfo.setDeptName(commonService.getDept(h5BillOrderInfo.getDeptId()).getDeptName());
        }
        return h5BillOrderInfo;
    }

    @Override
    public BillPayInfoVO continuePayOrder(String orderCode, Long deptId) {
        EduBillOrder order = eduBillOrderService.getOne(new LambdaQueryWrapper<>(new EduBillOrder()).eq(EduBillOrder::getOrderCode, orderCode));
        if (Objects.isNull(order) || OrderStatusEnum.CLOSED.getStatus().equals(order.getOrderStatus())) {
            throw new FebsException(ResultMsgType.ORDER_INVALID.getValue());
        }
        // 判断当前订单能否被当前用户支付
        BillOrderStudentCacheVO existBillOrderStudent = (BillOrderStudentCacheVO) redisService.get(
                CachePrefixConstant.BILL_ORDER_STUDENT + order.getStudentId().toString()
                        + StringConstant.DOT + order.getBillId().toString());
        if (Objects.nonNull(existBillOrderStudent)
                && !FebsUtil.getCurrentUser().getMobile().equals(existBillOrderStudent.getMobile())) {
            throw new FebsException(String.format("%s%s正在支付中, 请耐心等待",
                    existBillOrderStudent.getStudentName(),
                    existBillOrderStudent.getParentType()));
        }
        // 获取对应待支付信息缓存
        Object billOrderFlag = redisService.get(CachePrefixConstant.BILL_ORDER_FLAG + orderCode);
        if (Objects.isNull(billOrderFlag)) {
            throw new FebsException("当前订单已失效！");
        }
        return getPayInfo(orderCode, billOrderFlag.toString(), deptId, true, order.getPayWay(), order.getPayType());
    }

    @Override
    public void cancelOrder(String orderCode) {
        EduBillOrder order = eduBillOrderService.getOne(new LambdaQueryWrapper<>(new EduBillOrder()).eq(EduBillOrder::getOrderCode, orderCode));
        if (OrderStatusEnum.CLOSED.getStatus().equals(order.getOrderStatus())) {
            throw new FebsException(ResultMsgType.ORDER_INVALID.getValue());
        }
        // 判断当前订单能否被当前用户取消
        BillOrderStudentCacheVO existBillOrderStudent = (BillOrderStudentCacheVO) redisService.get(
                CachePrefixConstant.BILL_ORDER_STUDENT + order.getStudentId().toString()
                        + StringConstant.DOT + order.getBillId().toString());
        if (Objects.nonNull(existBillOrderStudent)
                && !FebsUtil.getCurrentUser().getMobile().equals(existBillOrderStudent.getMobile())) {
            throw new FebsException(String.format("%s%s正在支付中, 请耐心等待",
                    existBillOrderStudent.getStudentName(), existBillOrderStudent.getParentType()));
        }
        order.setOrderStatus(OrderStatusEnum.CLOSED.getStatus());
        order.setPayStatus(PayStatusEnum.CANCEL.getStatus());
        eduBillOrderService.updateById(order);
        try {
            // 判断支付方式
            if (PayWay.WEI_XIN.getValue().equals(order.getPayWay())) {
                // 关闭微信订单
                WxPayUtil.closeOrder(commonService.getPayWxConfigBySchool(order.getDeptId()), order.getOrderCode());
            } else if (PayWay.UNION.getValue().equals(order.getPayWay())
                    && !UnionPayTypeEnum.UNION_H5_TYPE.getType().equals(order.getPayType())) {
                // 关闭银联订单 (银联云闪付不支付关闭订单)
                UnionPayConfig unionPayConfig = commonService.getUnionPayConfigByDept(order.getDeptId());
                UnionPayConfig parentConfig = unionPayConfig.getParentConfig();
                UnionPayUtil.closeOrder(unionPayConfig,
                        unionPayPublicUtil.getAccessToken(
                                Objects.nonNull(parentConfig) ? parentConfig.getAppId() : unionPayConfig.getAppId(),
                                Objects.nonNull(parentConfig) ? parentConfig.getAppKey() : unionPayConfig.getAppKey()
                        ), order.getOrderCode());
            }
        } catch (Exception e) {
            log.error("关闭订单失败, order: {}", order, e);
            throw new FebsException("取消订单失败");
        }
        // 删除学生账单订单缓存
        redisService.del(CachePrefixConstant.BILL_ORDER_STUDENT + order.getStudentId().toString()
                + StringConstant.DOT + order.getBillId().toString());
        // 删除账单订单对应缓存
        redisService.del(CachePrefixConstant.BILL_ORDER + orderCode);
        redisService.del(CachePrefixConstant.BILL_ORDER_FLAG + orderCode);
    }

    @Override
    public void refundOrder(String orderCode) {
        EduBillOrder order = eduBillOrderService.getOne(new LambdaQueryWrapper<>(new EduBillOrder()).eq(EduBillOrder::getOrderCode, orderCode));
        // 判断订单能否退款
        Asserts.isTrue(OrderStatusEnum.PAY_SUCCESS.getStatus().equals(order.getOrderStatus())
                && PayStatusEnum.PAY_SUCCESS.getStatus().equals(order.getPayStatus()), "该笔订单无法进行退款");
        // 保存退款信息
        EduBillOrderRefund eduBillOrderRefund = new EduBillOrderRefund();
        eduBillOrderRefund.setOrderCode(orderCode);
        // 生成退款订单号
        String refundOrderCode = "";
        if (PayWay.WEI_XIN.getValue().equals(order.getPayWay())) {
            // 微信
            refundOrderCode = "R" + orderCode;
        } else if (PayWay.UNION.getValue().equals(order.getPayWay())) {
            // 银联
            // 获取银联商户号相关信息
            UnionPayConfig config = commonService.getUnionPayConfigByDept(order.getDeptId());
            refundOrderCode = UnionPayUtil.getOrderNumber(Objects.nonNull(config.getParentConfig())
                    ? config.getParentConfig().getSourceNumber() : config.getSourceNumber());
        }
        eduBillOrderRefund.setRefundCode(refundOrderCode);
        eduBillOrderRefund.setRefundSerialNumber(order.getTransactionNumber().replace("P", "R"));
        eduBillOrderRefundService.save(eduBillOrderRefund);
        // 修改订单状态
        order.setOrderStatus(OrderStatusEnum.USER_REFUNDING.getStatus());
        eduBillOrderService.updateById(order);
        try {
            // 调用退款接口
            PayWay payWay = EnumUtils.valueOf(PayWay.class, order.getPayWay(),
                    PayWay.class.getMethod("getValue"));
            switch (Objects.requireNonNull(payWay)) {
                // 微信
                case WEI_XIN:
                    // 查询学校微信支付配置
                    EduPayWxConfig config = commonService.getPayWxConfigBySchool(order.getDeptId());
                    WxPayUtil.refund(config, eduBillOrderRefund.getOrderCode(),
                            eduBillOrderRefund.getRefundReason(),
                            eduBillOrderRefund.getRefundCode(),
                            NumberUtil.mul(order.getPracticalMoney(), new BigDecimal("100")).longValue(),
                            NumberUtil.mul(order.getPracticalMoney(), new BigDecimal("100")).longValue(),
                            apiUrl + BILL_WX_REFUND_NOTIFY_URL);
                    break;
                case UNION:
                    // 查询学校银联云闪付支付配置
                    UnionPayConfig unionPayConfig = commonService.getUnionPayConfigByDept(order.getDeptId());
                    UnionPayConfig parentConfig = unionPayConfig.getParentConfig();
                    UnionPayUtil.refund(unionPayConfig, UnionRefund.builder()
                                    .orderNo(orderCode)
                                    .refundOrderNo(eduBillOrderRefund.getRefundCode())
                                    .transactionNumber(eduBillOrderRefund.getRefundSerialNumber())
                                    .refundAmount(order.getPracticalMoney())
                                    .build()
                            , unionPayPublicUtil.getAccessToken(
                                    Objects.nonNull(parentConfig) ? parentConfig.getAppId() : unionPayConfig.getAppId(),
                                    Objects.nonNull(parentConfig) ? parentConfig.getAppKey() : unionPayConfig.getAppKey()
                            ));
                    break;
                default:
                    break;
            }
        } catch (Exception e) {
            log.error("退款失败", e);
            throw new FebsException("退款失败");
        }
    }

    @Override
    public String checkExistOrder(Long billId, Long studentId) {
        BillOrderStudentCacheVO existBillOrderStudent = (BillOrderStudentCacheVO) redisService.get(
                CachePrefixConstant.BILL_ORDER_STUDENT + studentId.toString()
                        + StringConstant.DOT + billId.toString());
        if (Objects.nonNull(existBillOrderStudent)) {
            return existBillOrderStudent.getOrderNo();
        }
        return "";
    }
}
