package com.kym.service.wechat.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.kym.common.config.WxPayConfig;
import com.kym.common.constant.ResponseEnum;
import com.kym.common.exception.BusinessException;
import com.kym.common.utils.CommUtil;
import com.kym.common.utils.LambadaTools;
import com.kym.common.utils.OrderUtils;
import com.kym.entity.common.RedisKeys;
import com.kym.entity.miniapp.Account;
import com.kym.entity.miniapp.*;
import com.kym.service.admin.ActivityService;
import com.kym.common.cache.PlatformCache;
import com.kym.service.miniapp.*;
import com.kym.service.platform.PlatformApiService;
import com.kym.service.wechat.WxPayService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.core.http.HttpHeaders;
import com.wechat.pay.java.core.http.JsonRequestBody;
import com.wechat.pay.java.core.http.okhttp.OkHttpClientAdapter;
import com.wechat.pay.java.core.notification.Notification;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @author skyline
 * @description 微信支付
 * @date 2023-08-10 14:03
 */
@Service
public class WxPayServiceImpl implements WxPayService {

    private static final Logger LOGGER = LoggerFactory.getLogger(WxPayServiceImpl.class);

    public static JsapiService jsapiService;

    public static RefundService refundService;

    public static Config config;

    private OkHttpClientAdapter wxHttpClient;

    private final WxPayConfig conf;

    private final WalletDetailService walletDetailService;

    private final PayLogService payLogService;

    private final AccountService accountService;

    private final ChargeOrderService chargeOrderService;

    private final RefundLogService refundLogService;

    private final PlatformApiService platformApiService;

    private final ActivityService activityService;

    private final UserRechargeRightsService userRechargeRightsService;

    private final StringRedisTemplate stringRedisTemplate;


    public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService,
                            PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
                            RefundLogService refundLogService,
                            PlatformApiService platformApiService, ActivityService activityService, UserRechargeRightsService userRechargeRightsService,
                            StringRedisTemplate stringRedisTemplate) {
        this.conf = conf;
        this.walletDetailService = walletDetailService;
        this.payLogService = payLogService;
        this.accountService = accountService;
        this.chargeOrderService = chargeOrderService;
        this.refundLogService = refundLogService;
        this.platformApiService = platformApiService;
        this.activityService = activityService;
        this.userRechargeRightsService = userRechargeRightsService;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 商户订单号查询订单
     */
    public static Transaction queryOrderByOutTradeNo() {

        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        // 调用接口
        return jsapiService.queryOrderByOutTradeNo(request);
    }

    /**
     * 初始化
     *
     * @throws IOException
     */
    @PostConstruct
    void init() throws IOException {
        String privateKey;
        File file = new File(conf.getKeyPath());
        if (file.exists()) {
            privateKey = IoUtil.read(new FileInputStream(file), StandardCharsets.UTF_8);
        } else {
            ClassPathResource resource = new ClassPathResource(conf.getKeyPath());
            privateKey = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
        }
        config = new RSAAutoCertificateConfig.Builder()
                .merchantId(conf.getMchid()) // 商户号
                .privateKey(privateKey)//商户API私钥路径
                .merchantSerialNumber(conf.getMchsn()) // 商户证书序列号
                .apiV3Key(conf.getV3key()) // 商户APIV3密钥
                .build();
        jsapiService = new JsapiService.Builder().config(config).build();
        refundService = new RefundService.Builder().config(config).build();

        wxHttpClient = (OkHttpClientAdapter) new DefaultHttpClientBuilder().newInstance().config(config).build();

        // 初始化微信电子发票开发配置，主要是回调地址
        // devConfig();
    }

    /**
     * 回调签名验证失败排查指引 （body中字段顺序变化造成验签失败是个大坑）
     * https://developers.weixin.qq.com/community/pay/article/doc/0004a879f60928d89340b8b9f64c13
     * https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/issues/152
     *
     * @param request
     * @return
     */
    @SneakyThrows
    Object[] handleWxNotify(HttpServletRequest request) {
        var no = RandomUtil.randomInt(1000, 9999);
        var signature = request.getHeader("Wechatpay-Signature");
        var serial = request.getHeader("Wechatpay-Serial");
        var nonce = request.getHeader("Wechatpay-Nonce");
        var timestamp = request.getHeader("Wechatpay-Timestamp");
        var signatureType = request.getHeader("Wechatpay-Signature-Type");

        // 应对探测流量
        if (signature.contains("SIGNTEST")) {
            throw new BusinessException("接收到签名探测流量");
        }

        LOGGER.info("微信支付回调{}：\n Request参数：\n signature:{},serial:{},nonce:{},timestamp:{},signatureType:{}", no, signature, serial, nonce, timestamp, signatureType);

        ServletInputStream inputStream = request.getInputStream();
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        for (int lenght; (lenght = inputStream.read(buffer)) != -1; ) {
            result.write(buffer, 0, lenght);
        }
        var body = result.toString();

        LOGGER.info("微信支付回调{}：\nBody数据:\n{}", no, result);

        // 构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce) // 随机数
                .signature(signature)
                .timestamp(timestamp)
                .body(body)
                .build();
        LOGGER.info("微信支付回调{}：构造 RequestParam完毕", no);

        // 如果已经初始化了 RSAAutoCertificateConfig，可直接使用
        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser((NotificationConfig) config);
        return new Object[]{requestParam, parser, no};
    }

    /**
     * JSAPI支付下单
     */
    @Override
    public PrepayWithRequestPaymentResponse wxPay(JSONObject params) {
        if (!params.containsKey("amount") || params.getInteger("amount") == null || !(params.get("amount") instanceof Integer)) {
            throw new BusinessException(ResponseEnum.WX_PAY_AMOUNT_ERROR);
        }
        var rechargeAmount = params.getInteger("amount");
        var openid = StpUtil.getSession().getString("openid");
        var userId = StpUtil.getLoginIdAsLong();
        // 生成订单号
        String outTradeNo = OrderUtils.getOrderNo();
        // 创建钱包流水
        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_充值)
                .setStatus(WalletDetail.STATUS_待确认)
                .setUserId(userId)
                .setAmount(rechargeAmount)
                .setOrderNo(outTradeNo);
        walletDetailService.save(walletDetail);

        // request.setXxx(val)设置所需参数，具体参数可见Request定义
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(rechargeAmount); // 传入金额单位为分
        request.setAmount(amount);
        request.setAppid(conf.getAppid());
        request.setMchid(conf.getMchid());
        request.setDescription("K7充电生活充值");
        request.setNotifyUrl(conf.getNotifyUrl());
        request.setOutTradeNo(outTradeNo);
        Payer payer = new Payer();
        payer.setOpenid(openid);
        request.setPayer(payer);
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
        // response包含了调起支付所需的所有参数，可直接用于前端调起支付
        return service.prepayWithRequestPayment(request);
    }

    /**
     * 微信支付订单号查询订单
     */
    public Transaction queryOrderById(String transactionId) {
        QueryOrderByIdRequest request = new QueryOrderByIdRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        request.setMchid(conf.getMchid());
        request.setTransactionId(transactionId);
        // 调用接口
        return jsapiService.queryOrderById(request);
    }

    /**
     * 关闭订单
     *
     * @param outTradeNo
     */
    public void closeOrder(String outTradeNo) {
        CloseOrderRequest request = new CloseOrderRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        request.setMchid(conf.getMchid());
        request.setOutTradeNo(outTradeNo);
        // 调用接口
        jsapiService.closeOrder(request);
    }

    /**
     * 微信支付结果通知
     *
     * @param request
     * @return
     */
    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    @DS("db-miniapp")
    public ResponseEntity<Object> wxNotify(HttpServletRequest request) {

        try {
            var notifyRes = handleWxNotify(request);
            // 以支付通知回调为例，验签、解密并转换成 Transaction
            Transaction transaction = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], Transaction.class);
            LOGGER.info("微信支付回调{}：验签解密完毕，数据：\n{}", notifyRes[2], transaction);
            // 判断是否已经接收处理过通知
            if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
                return ResponseEntity.status(HttpStatus.OK).build();
            }

            DateTime dt = DateUtil.parse(transaction.getSuccessTime());
            LocalDateTime successTime = LocalDateTimeUtil.of(dt);

            // 钱包流水
            var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo(), WalletDetail.TYPE_充值);
            if (walletDetail != null) {
                walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
                walletDetail.setCurrency(transaction.getAmount().getCurrency());
                walletDetail.setAmount(transaction.getAmount().getTotal());
                walletDetail.setTransactionTime(successTime);
                walletDetailService.updateById(walletDetail);

                // 更新余额
                accountService.lambdaUpdate().setSql("balance = (balance+%d)".formatted(transaction.getAmount().getTotal()))
                        .eq(Account::getUserId, walletDetail.getUserId()).update();

                // 异步处理充值服务费打折权益活动相关逻辑
                activityService.handleRechargeActivity(walletDetail.getUserId(), transaction.getAmount().getTotal());

                // 支付记录
                var payLog = new PayLog();
                payLog.setUserId(walletDetail.getUserId());
                payLog.setOpenid(transaction.getPayer().getOpenid());
                payLog.setBankType(transaction.getBankType());
                payLog.setMchId(transaction.getMchid());
                payLog.setOutTradeNo(transaction.getOutTradeNo());
                payLog.setTransactionId(transaction.getTransactionId());
                payLog.setSuccessTime(successTime);
                payLog.setTradeType(transaction.getTradeType().name());
                payLog.setTradeState(transaction.getTradeState().name());
                payLog.setAttach(transaction.getAttach());
                payLog.setTotal(transaction.getAmount().getTotal());
                payLog.setCurrency(transaction.getAmount().getCurrency());
                payLog.setPayerTotal(transaction.getAmount().getPayerTotal());
                payLog.setPayerCurrency(transaction.getAmount().getPayerCurrency());
                payLogService.save(payLog);
                LOGGER.info("微信支付回调{}：业务处理结束", notifyRes[2]);

                // 如果存在进行中的订单，则向en+更新用户余额（朗新不支持此操作）
                var chargingOrder = chargeOrderService.getChargingOrderByUserId(walletDetail.getUserId());
                if (chargingOrder != null && "EN_PLUS".equals(PlatformCache.INSTANCE.getPlatformNameByConnectorId(chargingOrder.getConnectorId()))) {
                    var account = accountService.getAccountByUserId(walletDetail.getUserId());
                    // TODO: 2023-11-30 快充这里考虑过充的金额要提高
                    var res = platformApiService.updateBalanceByQueryEquipChargeStatus(PlatformCache.INSTANCE.getPlatformNameByConnectorId(chargingOrder.getConnectorId()), chargingOrder.getStartChargeSeq(), account.getBalance() - 50);
                    LOGGER.info("用户：{}充电过程中充值，已更新EN_PLUS充电金额,en+返回数据：{}", account.getUserId(), res);
                }
                return ResponseEntity.status(HttpStatus.OK).build();

            } else {
                LOGGER.error("微信支付通知处理异常，资金流水为空，回调信息：{}", transaction);
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "资金流水为空"));
            }
        } catch (Exception e) {
            if (e instanceof ValidationException) {
                // 签名验证失败，返回 401 UNAUTHORIZED 状态码
                LOGGER.error("微信支付通知验签失败", e);
            }
            if (e instanceof BusinessException) {
                LOGGER.error("业务异常", e);
            }
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("code", HttpStatus.UNAUTHORIZED, "message", "验签失败"));
        }
    }

    /**
     * 申请退款
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyWxRefund(String reason) {
        var userId = StpUtil.getLoginIdAsLong();

        // 防重复提交：Redis分布式锁，同一用户10秒内只能提交一次退款申请
        String lockKey = RedisKeys.REFUND_LOCK + userId;
        Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(locked)) {
            throw new BusinessException("退款申请正在处理中，请勿重复提交");
        }
        try {
            // 防重复提交：检查是否已有处理中的退款申请
            var existingRefund = refundLogService.lambdaQuery()
                    .eq(RefundLog::getUserId, userId)
                    .in(RefundLog::getStatus, RefundLog.STATUS_退款已申请, RefundLog.STATUS_退款处理中)
                    .one();
            if (existingRefund != null) {
                throw new BusinessException("您已有退款申请正在处理中，请勿重复提交");
            }

            var chargeOrder = chargeOrderService.getChargingOrderByUserId(userId);
            if (chargeOrder != null) {
                throw new BusinessException("存在未完结的订单，请等待所有订单完结之后重试");
            }

            var account = accountService.getAccountByUserId(userId);
            if (account.getBalance() <= 0) {
                throw new BusinessException("账户余额不足，无需退款");
            }
            // 校验余额大于优惠金额
            if (account.getBalance() <= account.getDiscountAmount()) {
                throw new BusinessException("不可退金额高于钱包余额，不支持退款");
            }

        // 将余额转移至冻结余额
        accountService.lambdaUpdate().setSql(" frozen_amount = (frozen_amount + balance) ,balance = 0").eq(Account::getUserId, userId).update();

        // 退款时，充值权益失效（权益余额转入冻结余额，权益状态设置为失效）
        userRechargeRightsService.lambdaUpdate()
                .setSql("frozen_balance = (rights_balance + frozen_balance) , rights_balance = 0")
                .set(UserRechargeRights::getStatus, UserRechargeRights.STATUS_无效)
                .eq(UserRechargeRights::getUserId, userId)
                .eq(UserRechargeRights::getStatus, UserRechargeRights.STATUS_有效)
                .update();

        // 余额减去优惠金额作为退款金额,优惠金额数据每次退款完成后重新计算
        AtomicInteger refundAmount = new AtomicInteger(account.getBalance() - account.getDiscountAmount());
        var originalRefundAmount = BigDecimal.valueOf(refundAmount.get());
        // 充值记录
        var payLogs = payLogService.lambdaQuery().eq(PayLog::getUserId, userId).eq(PayLog::getTradeState, PayLog.STATUS_充值成功).orderByDesc(PayLog::getSuccessTime).list();
        // 历史退款金额
        var refundsAmount = refundLogService.lambdaQuery().eq(RefundLog::getUserId, userId).list().stream().mapToInt(RefundLog::getRefund).sum();
        // 充值金额-历史退款金额不足以支付余额退款
        if (!CommUtil.isEmptyOrNull(payLogs)) {
            var total = payLogs.stream().mapToInt(PayLog::getTotal).sum();
            if ((total - refundsAmount) < account.getBalance()) {
                LOGGER.error("用户：{},历史充值金额：{}，不足以支付余额退款金额：{}", userId, total - refundsAmount, account.getBalance());
                throw new BusinessException("充值金额不足以支付余额退款");
            }
        }

        // 最后一次的充值金额可以覆盖退款金额
        if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() >= refundAmount.get()) {
            // 退款日志
            var refundLog = new RefundLog().setUserId(payLogs.get(0).getUserId()).setOutTradeNo(payLogs.get(0).getOutTradeNo())
                    .setTotal(payLogs.get(0).getTotal())
                    .setRefund(refundAmount.get())
                    .setDiscountAmount(account.getDiscountAmount())
                    .setOutRefundNo(OrderUtils.getOrderNo());
            refundLogService.save(refundLog);
        } else if (!CommUtil.isEmptyOrNull(payLogs) && payLogs.get(0).getTotal() < refundAmount.get()) {
            // 最后一次的充值金额不能覆盖退款金额，拆分成多笔退款
            int amount = 0;
            // 用来退款的充值记录
            var refundPayLogs = new ArrayList<PayLog>();
            for (PayLog payLog : payLogs) {
                amount += payLog.getTotal();
                refundPayLogs.add(payLog);
                if (amount >= refundAmount.get()) {
                    break;
                }
            }
            // 需要退款的记录数量
            var size = refundPayLogs.size();

            var refundLogList = new ArrayList<RefundLog>(size);
            var newRefundLogList = new ArrayList<RefundLog>(size);

            refundPayLogs.forEach(LambadaTools.forEachWithIndex((item, index) -> {
                // 如果不是最后一笔，金额全退，最后一笔退剩余的金额
                if (index < size - 1) {
                    refundLogList.add(new RefundLog().setUserId(item.getUserId()).setOutTradeNo(item.getOutTradeNo())
                            .setTotal(item.getTotal()).setRefund(item.getTotal()).setOutRefundNo(OrderUtils.getOrderNo()));
                    refundAmount.addAndGet(-item.getTotal());
                } else {
                    refundLogList.add(new RefundLog().setUserId(item.getUserId()).setOutTradeNo(item.getOutTradeNo())
                            .setTotal(item.getTotal()).setRefund(refundAmount.get()).setOutRefundNo(OrderUtils.getOrderNo()));
                }
            }));

            // 不可退金额按退款金额比例放入每笔退款中
            refundLogList.forEach(LambadaTools.forEachWithIndex((refundLog, index) -> {
                int discountAmount;
                if (index < size - 1) {
                    discountAmount = BigDecimal.valueOf(account.getDiscountAmount()).multiply((BigDecimal.valueOf(refundLog.getRefund()).divide(originalRefundAmount, 2, RoundingMode.HALF_UP))).intValue();
                } else {
                    // 前面存在精度误差，最后一个元素用总数相减最精确
                    discountAmount = account.getDiscountAmount() - newRefundLogList.stream().mapToInt(RefundLog::getDiscountAmount).sum();
                }
                refundLog.setDiscountAmount(discountAmount);
                refundLog.setReason(CommUtil.isEmptyOrNull(reason) ? reason : JSONObject.parseObject(reason).getString("reason"));
                newRefundLogList.add(index, refundLog);
            }));

            refundLogService.saveBatch(newRefundLogList);
        } else {
            // 退款异常
            LOGGER.error("退款异常：userId：{}，退款金额：{}，payLogs：{}", userId, originalRefundAmount, payLogs);
            throw new BusinessException("退款异常");
        }
        } finally {
            stringRedisTemplate.delete(lockKey);
        }
    }


    /**
     * 手动确认退款（针对超过365天的订单）
     *
     * @param refundLogId 退款记录ID
     */
    @DS("db-miniapp")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void manualConfirmRefund(long refundLogId) {
        // 通过退款申请id获取退款申请记录
        var refundLog = refundLogService.getById(refundLogId);
        if (refundLog == null) {
            throw new BusinessException("退款记录不存在");
        }

        // 检查退款状态，只能处理已申请状态的退款
        if (!RefundLog.STATUS_退款已申请.equals(refundLog.getStatus())) {
            throw new BusinessException("退款状态不正确，只能处理已申请状态的退款");
        }

        // 验证是否超过退款时限（365天）
        var payLog = payLogService.lambdaQuery()
                .eq(PayLog::getOutTradeNo, refundLog.getOutTradeNo())
                .eq(PayLog::getTradeState, PayLog.STATUS_充值成功)
                .one();
        
        if (payLog == null) {
            throw new BusinessException("找不到对应的支付记录");
        }

        // 检查是否超过365天退款时限
        LocalDateTime paySuccessTime = payLog.getSuccessTime();
        boolean isOverdue = paySuccessTime.plusDays(365).isBefore(LocalDateTime.now());
        
        if (!isOverdue) {
            throw new BusinessException("该订单未超过365天退款时限，应使用正常退款流程");
        }

        // 钱包流水
        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_提现)
                .setStatus(WalletDetail.STATUS_待确认)
                .setUserId(refundLog.getUserId())
                .setAmount(refundLog.getRefund())
                .setOrderNo(refundLog.getOutRefundNo());
        walletDetailService.save(walletDetail);

        // 执行与微信退款成功后相同的业务逻辑
        processRefundSuccess(refundLog, walletDetail);
        
        // 记录操作人员信息
        refundLogService.lambdaUpdate()
                .set(RefundLog::getAdminUserId, StpUtil.getLoginIdAsLong())
                .set(RefundLog::getAdminUsername, StpUtil.getSession().getString("username"))
                .eq(RefundLog::getId, refundLogId)
                .update();
        
        LOGGER.info("手动确认退款完成，退款记录ID：{}，操作人员：{}", refundLogId, StpUtil.getSession().getString("username"));
    }

    /**
     * 挂起退款申请（针对超过365天的订单）
     *
     * @param refundLogId 退款记录ID
     */
    @DS("db-miniapp")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void suspendRefund(long refundLogId) {
        var refundLog = refundLogService.getById(refundLogId);
        if (refundLog == null) {
            throw new BusinessException("退款记录不存在");
        }

        if (!RefundLog.STATUS_退款已申请.equals(refundLog.getStatus())) {
            throw new BusinessException("退款状态不正确，只能挂起已申请状态的退款");
        }

        var payLog = payLogService.lambdaQuery()
                .eq(PayLog::getOutTradeNo, refundLog.getOutTradeNo())
                .eq(PayLog::getTradeState, PayLog.STATUS_充值成功)
                .one();

        if (payLog == null) {
            throw new BusinessException("找不到对应的支付记录");
        }

        LocalDateTime paySuccessTime = payLog.getSuccessTime();
        boolean isOverdue = paySuccessTime.plusDays(365).isBefore(LocalDateTime.now());

        if (!isOverdue) {
            throw new BusinessException("该订单未超过365天退款时限，无需挂起");
        }

        refundLogService.lambdaUpdate()
                .set(RefundLog::getStatus, RefundLog.STATUS_挂起)
                .set(RefundLog::getAdminUserId, StpUtil.getLoginIdAsLong())
                .set(RefundLog::getAdminUsername, StpUtil.getSession().getString("username"))
                .eq(RefundLog::getId, refundLogId)
                .update();

        LOGGER.info("挂起退款申请完成，退款记录ID：{}，操作人员：{}", refundLogId, StpUtil.getSession().getString("username"));
    }

    /**
     * 取消挂起退款申请
     *
     * @param refundLogId 退款记录ID
     */
    @DS("db-miniapp")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelSuspendRefund(long refundLogId) {
        var refundLog = refundLogService.getById(refundLogId);
        if (refundLog == null) {
            throw new BusinessException("退款记录不存在");
        }

        if (!RefundLog.STATUS_挂起.equals(refundLog.getStatus())) {
            throw new BusinessException("退款状态不正确，只能取消挂起状态的退款");
        }

        refundLogService.lambdaUpdate()
                .set(RefundLog::getStatus, RefundLog.STATUS_退款已申请)
                .set(RefundLog::getAdminUserId, StpUtil.getLoginIdAsLong())
                .set(RefundLog::getAdminUsername, StpUtil.getSession().getString("username"))
                .eq(RefundLog::getId, refundLogId)
                .update();

        LOGGER.info("取消挂起退款申请完成，退款记录ID：{}，操作人员：{}", refundLogId, StpUtil.getSession().getString("username"));
    }

    /**
     * 处理退款成功的公共逻辑
     *
     * @param refundLog 退款记录
     * @param walletDetail 钱包流水
     */
    private void processRefundSuccess(RefundLog refundLog, WalletDetail walletDetail) {
        // 更新退款记录状态为退款成功
        LocalDateTime successTime = LocalDateTime.now();
        refundLogService.lambdaUpdate()
                .set(RefundLog::getStatus, RefundLog.STATUS_退款成功)
                .set(RefundLog::getSuccessTime, successTime)
                .set(RefundLog::getUserReceivedAccount, "线下退款")
                .eq(RefundLog::getId, refundLog.getId())
                .update();

        // 冻结金额扣减此次（退款金额+优惠金额）,优惠金额字段减去申请退款时的优惠金额
        accountService.lambdaUpdate().setSql("frozen_amount = (frozen_amount - (%d + %d)) , discount_amount = (discount_amount - %d)"
                        .formatted(refundLog.getRefund(), refundLog.getDiscountAmount(), refundLog.getDiscountAmount()))
                .eq(Account::getUserId, refundLog.getUserId()).update();

        // 更新资金流水
        walletDetailService.lambdaUpdate()
                .set(WalletDetail::getStatus, WalletDetail.STATUS_已确认)
                .set(WalletDetail::getTransactionTime, successTime)
                .set(WalletDetail::getAmount, refundLog.getRefund())
                .eq(WalletDetail::getId, walletDetail.getId()).update();
    }

    /**
     * 处理退款
     *
     * @return
     */
    @DS("db-miniapp")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void wxRefund(long refundLogId) {
        // 通过退款申请id获取退款申请记录
        var refundLog = refundLogService.getById(refundLogId);

        // 钱包流水
        var walletDetail = new WalletDetail()
                .setType(WalletDetail.TYPE_提现)
                .setStatus(WalletDetail.STATUS_待确认)
                .setUserId(refundLog.getUserId())
                .setAmount(refundLog.getRefund())
                .setOrderNo(refundLog.getOutRefundNo());
        walletDetailService.save(walletDetail);

        CreateRequest request = new CreateRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        // 原始第三方订单号
        request.setOutTradeNo(refundLog.getOutTradeNo());
        // 退款订单号
        request.setOutRefundNo(refundLog.getOutRefundNo());
        // 退款原因
        request.setReason("用户申请退款");
        // 回调通知地址
        request.setNotifyUrl(conf.getRefundNotifyUrl());
        var amount = new AmountReq();
        // 退款金额
        amount.setRefund((long) refundLog.getRefund());
        amount.setTotal((long) refundLog.getTotal());
        amount.setCurrency(refundLog.getCurrency());
        request.setAmount(amount);
        var refund = refundService.create(request);

        refundLog.setChannel(refund.getChannel().name());
        refundLog.setFundsAccount(refund.getFundsAccount().name());
        refundLog.setCurrency(refund.getAmount().getCurrency());
        refundLog.setAdminUserId(StpUtil.getLoginIdAsLong());
        refundLog.setAdminUsername(StpUtil.getSession().getString("username"));
        refundLog.setStatus(RefundLog.STATUS_退款处理中);
        refundLogService.updateById(refundLog);

    }

    /**
     * 查询单笔退款（通过商户退款单号）
     *
     * @return
     */
    @Override
    public Refund queryByOutRefundNo(String outRefundNo) {
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
        request.setOutRefundNo(outRefundNo);
        // 调用接口
        return refundService.queryByOutRefundNo(request);
    }


    /**
     * 微信退款结果通知
     *
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<Object> wxRefundNotify(HttpServletRequest request) {
        var notifyRes = handleWxNotify(request);

        try {
            // 以支付通知回调为例，验签、解密并转换成 RefundNotification
            RefundNotification refundNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], RefundNotification.class);
            LOGGER.info("微信退款回调{}：验签解密完毕，数据：\n{}", notifyRes[2], refundNotification);

            //退款日志在申请时插入，接收通知时更新
            var refundLog = refundLogService.lambdaQuery().eq(RefundLog::getOutRefundNo, refundNotification.getOutRefundNo()).one();
            // 防止重复处理消息
            if (RefundLog.STATUS_退款成功.equals(refundLog.getStatus())) {
                return ResponseEntity.status(HttpStatus.OK).build();
            }

            DateTime dt = DateUtil.parse(refundNotification.getSuccessTime());
            LocalDateTime successTime = LocalDateTimeUtil.of(dt);

            refundLogService.lambdaUpdate()
                    .set(RefundLog::getRefundId, refundNotification.getRefundId())
                    .set(RefundLog::getTransactionId, refundNotification.getTransactionId())
                    .set(RefundLog::getUserReceivedAccount, refundNotification.getUserReceivedAccount())
                    .set(RefundLog::getSuccessTime, successTime)
                    .set(RefundLog::getStatus, refundNotification.getRefundStatus().name())
                    .set(RefundLog::getTotal, refundNotification.getAmount().getTotal().intValue())
                    .set(RefundLog::getRefund, refundNotification.getAmount().getRefund().intValue())
                    .eq(RefundLog::getId, refundLog.getId()).update();

            if (RefundLog.STATUS_退款成功.equals(refundNotification.getRefundStatus().name())) {
                // 冻结金额扣减此次（退款金额+优惠金额）,优惠金额字段减去申请退款时的优惠金额
                accountService.lambdaUpdate().setSql("frozen_amount = (frozen_amount - (%d + %d)) , discount_amount = (discount_amount - %d)"
                                .formatted(refundNotification.getAmount().getRefund().intValue(), refundLog.getDiscountAmount(), refundLog.getDiscountAmount()))
                        .eq(Account::getUserId, refundLog.getUserId()).update();

                // 更新资金流水
                var walletDetail = walletDetailService.getWalletDetailByOrderNo(refundNotification.getOutRefundNo(), WalletDetail.TYPE_提现);
                walletDetailService.lambdaUpdate()
                        .set(WalletDetail::getStatus, WalletDetail.STATUS_已确认)
                        .set(WalletDetail::getTransactionId, refundNotification.getTransactionId())
                        .set(WalletDetail::getTransactionTime, successTime)
                        .set(WalletDetail::getAmount, refundNotification.getAmount().getRefund().intValue())
                        .set(WalletDetail::getTransactionTime, successTime)
                        .eq(WalletDetail::getId, walletDetail.getId()).update();
                LOGGER.info("微信退款回调{}：业务处理结束", notifyRes[2]);

                return ResponseEntity.status(HttpStatus.OK).build();
            } else {
                // 退款失败
                LOGGER.error("微信退款失败，用户id:{}，退款状态：{} ，退款结果通知详情：{}", refundLog.getUserId(), refundNotification.getRefundStatus().name(), refundNotification);
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", HttpStatus.INTERNAL_SERVER_ERROR, "message", "退款处理异常"));
            }
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信退款通知验签失败", e);
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("code", HttpStatus.UNAUTHORIZED, "message", "验签失败"));
        }
    }

}
