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 cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson2.JSONObject;
import com.kym.common.config.WxFapiaoConfig;
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.miniapp.Account;
import com.kym.entity.miniapp.*;
import com.kym.entity.wechat.*;
import com.kym.service.admin.ActivityService;
import com.kym.service.admin.InvoiceDetailService;
import com.kym.service.enplus.EnPlusService;
import com.kym.service.miniapp.*;
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.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.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 final WxPayConfig conf;

    private final WxFapiaoConfig fapiaoConfig;

    private final WalletDetailService walletDetailService;

    private final PayLogService payLogService;

    private final AccountService accountService;

    private final ChargeOrderService chargeOrderService;

    private final RefundLogService refundLogService;

    private final InvoiceService invoiceService;

    private final InvoiceTitleService invoiceTitleService;

    private final EnPlusService enPlusService;

    private final ActivityService activityService;

    private final UserRechargeRightsService userRechargeRightsService;

    private final InvoiceDetailService invoiceDetailService;


    /**
     * 微信支付专用，支持自动签名验签解密等
     */
    private OkHttpClientAdapter wxHttpClient;


    public WxPayServiceImpl(WxPayConfig conf, WxFapiaoConfig fapiaoConfig, WalletDetailService walletDetailService,
                            PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService,
                            RefundLogService refundLogService, InvoiceService invoiceService, InvoiceTitleService invoiceTitleService,
                            EnPlusService enPlusService, ActivityService activityService, UserRechargeRightsService userRechargeRightsService, InvoiceDetailService invoiceDetailService) {
        this.conf = conf;
        this.fapiaoConfig = fapiaoConfig;
        this.walletDetailService = walletDetailService;
        this.payLogService = payLogService;
        this.accountService = accountService;
        this.chargeOrderService = chargeOrderService;
        this.refundLogService = refundLogService;
        this.invoiceService = invoiceService;
        this.invoiceTitleService = invoiceTitleService;
        this.enPlusService = enPlusService;
        this.activityService = activityService;
        this.userRechargeRightsService = userRechargeRightsService;
        this.invoiceDetailService = invoiceDetailService;
    }

    /**
     * 商户订单号查询订单
     */
    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();

        // 初始化微信电子发票开发配置，主要是回调地址 todo 区块链电子发票开通后放开
        // 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("超级进化车生活充值");
        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)
    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) {

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

                walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
                walletDetail.setCurrency(transaction.getAmount().getCurrency());
                walletDetail.setAmount(transaction.getAmount().getTotal());
                walletDetail.setBeforeBalance(account.getBalance());
                walletDetail.setAfterBalance(account.getBalance() + walletDetail.getAmount());
                walletDetail.setTransactionTime(successTime);
                walletDetailService.updateById(walletDetail);


                // 异步处理充值服务费打折权益活动相关逻辑
                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) {
                    // TODO: 2023-11-30 快充这里考虑过充的金额要提高
                    var res = enPlusService.updateBalanceByQueryEquipChargeStatus(chargingOrder.getStartChargeSeq(), account.getBalance() - 50);
                    LOGGER.info("用户：{}充电过程中充值，已更新en+充电金额,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();
        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("退款异常");
        }
    }


    /**
     * 处理退款
     *
     * @return
     */
    @Override
    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())) {
                // 冻结金额扣减此次（退款金额+优惠金额）,优惠金额字段减去申请退款时的优惠金额
                var account = accountService.getAccountByUserId(refundLog.getUserId());
                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::getBeforeBalance, account.getBalance())
                        .set(WalletDetail::getAfterBalance, account.getBalance() - 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:{}，退款状态：{} \n 退款结果通知详情：{}", 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", "验签失败"));
        }
    }

    //================================================================发票=====================================================================

    /**
     * 微信电子发票配置开发选项，例如回调地址、全部账单展示开发票入口开关
     */
    @Override
    public Object devConfig() {
        // 创建卡券模版
        cardTemplate();
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        headers.addHeader("Content-Type", "application/json");
        var params = JSONObject.of("callback_url", "https://www.kuaiyuman.cn/api/invoice/notify", "show_fapiao_cell", false);
        var requestBody = new JsonRequestBody.Builder().body(params.toJSONString()).build();
        var res = wxHttpClient.patch(headers, fapiaoConfig.getDevConfig(), requestBody, JSONObject.class);
        LOGGER.info("微信电子发票开发设置：{}", res.toString());
        return res.getServiceResponse();
    }

    /**
     * 创建电子发票卡券模版
     */
    @Override
    public void cardTemplate() {
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        headers.addHeader("Content-Type", "application/json");
        var params = JSONObject.of(
                "card_appid", "wx93b0ef1be901bd19", // 公众号appid，不是小程序appid
                "card_template_information", JSONObject.of(
                        "logo_url", "https://kym-static.oss-cn-shenzhen.aliyuncs.com/logo/K7.png"
                )
        );
        var requestBody = new JsonRequestBody.Builder().body(params.toJSONString()).build();
        var res = wxHttpClient.post(headers, fapiaoConfig.getCardTemplate(), requestBody, JSONObject.class);
        LOGGER.info("创建电子发票卡券模版:{}", res);
    }

    /**
     * 查询商户配置的开发选项
     */
    @Override
    public Object getDevConfig() {
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        headers.addHeader("Content-Type", "application/json");
        var res = wxHttpClient.get(headers, fapiaoConfig.getDevConfig(), JSONObject.class);
        LOGGER.info("查询微信电子发票开发设置：{}", res.toString());
        return res;
    }

    /**
     * 获取用户抬头信息填写URL
     *
     * @param invoice
     * @return
     */
    @Override
    public TitleUrl titleUrl(Invoice invoice) {
        var headers = new HttpHeaders();
        var params = Map.of(
                "fapiao_apply_id", invoice.getApplyId(),
                "appid", conf.getAppid(),
                "openid", invoice.getOpenid(),
                "total_amount", invoice.getInvoiceAmount(),
                "source", "MINIPROGRAM"
        );
        headers.addHeader("Accept", "application/json");
        var res = wxHttpClient.get(headers, fapiaoConfig.getTitleUrl().concat("?").concat(URLUtil.buildQuery(params, StandardCharsets.UTF_8)), TitleUrl.class);
        return res.getServiceResponse();
    }


    /**
     * 处理发票抬头填写完成通知
     * event_type为FAPIAO.USER_APPLIED
     *
     * @param notifyRes
     */
    @Override
    public void titleWriteNotice(Object[] notifyRes) {
        try {
            TitleWriteNotification titleWriteNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], TitleWriteNotification.class);

            // 查询用户填写的抬头信息
            var applyId = titleWriteNotification.getFapiaoApplyId();
            LOGGER.info("回调成功，applyId:{}", applyId);
            var buyerInformation = userTitle(applyId);

            // 更新invoice表数据
            var invoice = invoiceService.lambdaQuery().eq(Invoice::getApplyId, applyId).one();
            invoice.setInvoiceType(buyerInformation.getType())
                    .setInvoiceTitle(buyerInformation.getName())
                    .setTaxId(buyerInformation.getTaxpayer_id())
                    .setAddress(buyerInformation.getAddress())
                    .setTelephone(buyerInformation.getTelephone())
                    .setBankName(buyerInformation.getBank_name())
                    .setBankAccount(buyerInformation.getBank_account())
                    .setEmail(buyerInformation.getEmail())
                    .setPhone(buyerInformation.getPhone());
            invoiceService.updateById(invoice);

            // 将发票抬头写入用户抬头表
            var invoiceTitle = new InvoiceTitle()
                    .setUserId(invoice.getUserId())
                    .setInvoiceType(buyerInformation.getType())
                    .setInvoiceTitle(buyerInformation.getName())
                    .setTaxId(buyerInformation.getTaxpayer_id())
                    .setTelephone(buyerInformation.getTelephone())
                    .setAddress(buyerInformation.getAddress())
                    .setBankName(buyerInformation.getBank_name())
                    .setBankAccount(buyerInformation.getBank_account())
                    .setEmail(buyerInformation.getEmail())
                    .setPhone(buyerInformation.getPhone());
            boolean exist = invoiceTitleService.lambdaQuery()
                    .eq(InvoiceTitle::getUserId, invoiceTitle.getUserId())
                    .eq(InvoiceTitle::getTaxId, invoiceTitle.getTaxId())
                    .exists();
            if (!exist) {
                invoiceTitleService.save(invoiceTitle);
            }
            // 运营后台财务操作开具发票
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信处理发票抬头填写完成通知验签失败", e);
            throw new BusinessException("验签失败");
        }
    }

    /**
     * 获取用户填写的抬头信息
     *
     * @param applyId
     * @return
     */
    @Override
    public FaPiao.BuyerInformation userTitle(String applyId) {
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        var res = wxHttpClient.get(headers, fapiaoConfig.getUserTitle().formatted(applyId), FaPiao.BuyerInformation.class);
        LOGGER.info("用户发票申请applyId:{}，抬头信息：{}", applyId, res);
        return res.getServiceResponse();
    }

    /**
     * 获取商户开票基础信息
     */
    @Override
    public InvoiceBaseInfo baseInformation() {
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        var res = wxHttpClient.get(headers, fapiaoConfig.getBaseInformation(), InvoiceBaseInfo.class);
        return res.getServiceResponse();
    }


    /**
     * 获取商户可开具的商品和服务税收分类编码对照表
     */
    private TaxCodes getTaxCodes() {
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        var url = fapiaoConfig.getTaxCodes().concat("?offset=%d&limit=%d").formatted(0, 5);
        var res = wxHttpClient.get(headers, url, TaxCodes.class);
        return res.getServiceResponse();
    }


    /**
     * 开具电子发票
     */
    @Override
    public void fapiaoApplication(String invoiceId) {
        var invoice = invoiceService.lambdaQuery().eq(Invoice::getId, invoiceId).one();
        // 开票状态校验
        if (!invoice.getStatus().equals(Invoice.STATUS_待开票)) {
            throw new BusinessException("发票状态为开票中或已开票");
        }
        // 记录开票人
        invoiceService.lambdaUpdate().set(Invoice::getBiller, StpUtil.getSession().getString("username")).eq(Invoice::getId, invoice.getId()).update();

        // 订单金额+ ，服务费优惠金额- 服务费优惠金额单独一个item，金额为负数
        var itemList = new ArrayList<FaPiao.FaPiaoInfomation.IssueItem>();
        // 电费 税务商品编码：1100101020000000000
        var elecMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getElecMoney).sum();
        // 必须为正数
        if (elecMoney > 0) {
            var elecMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotal_amount(elecMoney).setTax_code("1100101020000000000");
            itemList.add(elecMoneyItem);
        }
        // 服务费 税务商品编码：3040100000000000000
        var serviceMoney = invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getServiceMoney).sum();
        // 必须为正数
        if (serviceMoney > 0) {
            var serviceMoneyItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotal_amount(serviceMoney).setTax_code("3040100000000000000");
            itemList.add(serviceMoneyItem);
        }

        // 服务费优惠 税务商品编码：3040100000000000000  注意：折扣行必须是被折扣行的下一行
        var serviceMoneyDiscount = -invoice.getOrderDetails().stream().mapToInt(InvoiceOrderDetail::getServiceMoneyDiscount).sum();
        // 必须为负数
        if (serviceMoneyDiscount < 0) {
            var serviceMoneyDiscountItem = new FaPiao.FaPiaoInfomation.IssueItem().setTotal_amount(serviceMoneyDiscount).setTax_code("3040100000000000000").setDiscount(true);
            itemList.add(serviceMoneyDiscountItem);
        }

        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        headers.addHeader("Content-Type", "application/json");
        headers.addHeader("Wechatpay-Serial", conf.getMchsn());

        // 客户开票信息
        var buyerInformation = new FaPiao.BuyerInformation()
                .setType(invoice.getInvoiceType())
                .setName(invoice.getInvoiceTitle())
                .setTaxpayer_id(invoice.getTaxId())
                .setTelephone(invoice.getTelephone())
                .setAddress(invoice.getAddress())
                .setBank_name(invoice.getBankName())
                .setBank_account(invoice.getBankAccount());

        // 发票信息
        var fapiaoInformation = new FaPiao.FaPiaoInfomation();
        // fapiao_id为invoice.id
        fapiaoInformation.setFapiao_id(String.valueOf(invoice.getId()))
                .setTotal_amount(invoice.getInvoiceAmount())
                .setItems(itemList);

        // 请求参数
        var fapiao = new FaPiao()
                .setScene("WITHOUT_WECHATPAY")
                .setFapiao_apply_id(invoice.getApplyId())
                .setBuyer_information(buyerInformation)
                .setFapiao_information(List.of(fapiaoInformation));

        var requestBody = new JsonRequestBody.Builder().body(JSONObject.toJSONString(fapiao)).build();
        wxHttpClient.post(headers, fapiaoConfig.getFapiaoApplications(), requestBody, null);
        // 修改发票状态为开票中
        invoiceService.lambdaUpdate().set(Invoice::getStatus, Invoice.STATUS_开票中).eq(Invoice::getId, invoiceId).update();
    }

    /**
     * 微信开票结果通知
     * event_type为FAPIAO.ISSUED
     *
     * @param notifyRes
     * @return
     */
    @Override
    @Transactional
    public void wxInvoiceNotify(Object[] notifyRes) {
        try {
            InvoiceNotification invoiceNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], InvoiceNotification.class);
            LOGGER.info("微信开具发票结果通知回调{}：验签解密完毕，数据：\n{}", notifyRes[2], invoiceNotification);
            // 业务逻辑
            if (invoiceNotification.getFapiaoInformation().get(0).getFapiaoStatus().equals(InvoiceNotification.FapiaoStatus.ISSUED.name())) {
                // 更新发票状态
                invoiceService.lambdaUpdate().set(Invoice::getStatus, Invoice.STATUS_已开票).eq(Invoice::getApplyId, invoiceNotification.getFapiaoApplyId()).update();
                var invoice = invoiceService.lambdaQuery().eq(Invoice::getApplyId, invoiceNotification.getFapiaoApplyId()).one();
                // 更新订单开票状态为已开票
                var chargeOrderSeqs = invoice.getOrderDetails().stream().map(InvoiceOrderDetail::getStartChargeSeq).toList();
                if (!CommUtil.isEmptyOrNull(chargeOrderSeqs)) {
                    chargeOrderService.lambdaUpdate().set(ChargeOrder::getInvoiceStatus, ChargeOrder.INVOICE_STATUS_已开票).in(ChargeOrder::getStartChargeSeq, chargeOrderSeqs).update();
                }
                /*
                 * 销项发票excel数据中需包含：
                 * 1.开票日期
                 * 2.购买方名称及税号
                 * 3.开票明细、金额、税额
                 * 4.开票优惠明细、金额、税额
                 * 5.发票状态是否作废或者红冲
                 */
                var fapiaoApplications = queryFapiao(invoice.getApplyId());
                invoiceDetailService.updateInvoiceDetail(invoice.getApplyId(), fapiaoApplications);
            } else {
                LOGGER.error("微信开具发票失败：{}", invoiceNotification);
            }
        } catch (ValidationException e) {
            // 签名验证失败，返回 401 UNAUTHORIZED 状态码
            LOGGER.error("微信开具发票结果通知验签失败", e);
            throw new BusinessException("验签失败");
        }
    }


    /**
     * 接收电子发票相关回调通知
     *
     * @param request
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invoiceNotify(HttpServletRequest request) {
        var notifyRes = handleWxNotify(request);
        var requestParam = (RequestParam) notifyRes[0];
        Notification notification = JSONObject.parseObject(requestParam.getBody(), Notification.class);
        switch (notification.getEventType()) {
            // 发票抬头填写完成
            case "FAPIAO.USER_APPLIED" -> titleWriteNotice(notifyRes);
            // 发票开具结果
            case "FAPIAO.ISSUED" -> wxInvoiceNotify(notifyRes);
        }
    }


    /**
     * 下载发票，下载链接30s有效
     *
     * @param invoiceId
     * @return
     */
    @Override
    public Map<String, String> downloadInvoice(String invoiceId) {
        var invoice = invoiceService.getById(invoiceId);
        var download = getFapiaoDownloadInfo(invoice.getApplyId());
        var downloadInfo = download.getFapiao_download_info_list().get(0);
        // 查询电子发票
        var fapiaoApplications = queryFapiao(invoice.getApplyId());

        var params = "&mchid=%s&openid=%s&invoice_code=%s&invoice_no=%s&fapiao_id=%s";
        // openid 通过查询电子发票接口获取的card_openid
        var fapiao = fapiaoApplications.getFapiao_information().get(0);
        var downloadUrl = downloadInfo.getDownload_url().concat(params.formatted(conf.getMchid(),
                fapiao.getCard_information().getCard_openid(), fapiao.getBlue_fapiao().getFapiao_code(),
                fapiao.getBlue_fapiao().getFapiao_number(), invoice.getId()));
        return Map.of("downloadUrl", downloadUrl);
    }

    /**
     * 获取发票下载信息
     *
     * @param applyId
     * @return
     */
    @Override
    public FapiaoDownload getFapiaoDownloadInfo(String applyId) {
        var downloadUrl = fapiaoConfig.getFapiaoFiles().formatted(applyId);
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        var res = wxHttpClient.get(headers, downloadUrl, FapiaoDownload.class);
        return res.getServiceResponse();
    }

    /**
     * 查询电子发票
     *
     * @param applyId
     */
    @Override
    public FapiaoApplications queryFapiao(String applyId) {
        var headers = new HttpHeaders();
        headers.addHeader("Accept", "application/json");
        headers.addHeader("Content-Type", "application/json");
        var res = wxHttpClient.get(headers, fapiaoConfig.getQueryFapiao().formatted(applyId), FapiaoApplications.class);
        return res.getServiceResponse();
    }

}
