package shop.alien.store.strategy.merchantPayment.impl;

import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import shop.alien.entity.result.R;
import shop.alien.entity.store.MerchantPaymentOrder;
import shop.alien.entity.store.RefundRecord;
import shop.alien.entity.store.StoreInfo;
import shop.alien.entity.store.UserReservation;
import shop.alien.entity.store.UserReservationOrder;
import shop.alien.mapper.StoreInfoMapper;
import shop.alien.store.service.MerchantPaymentOrderService;
import shop.alien.store.service.RefundRecordAsyncService;
import shop.alien.store.service.RefundRecordService;
import shop.alien.store.service.ReservationOrderPaymentTimeoutService;
import shop.alien.store.service.UserReservationOrderService;
import shop.alien.store.service.UserReservationService;
import shop.alien.store.strategy.merchantPayment.MerchantPaymentStrategy;
import shop.alien.store.strategy.payment.impl.WeChatPartnerPaymentStrategyImpl;
import shop.alien.store.strategy.payment.impl.WeChatPaymentStrategyImpl;
import shop.alien.store.util.WXPayUtility;
import shop.alien.util.common.UniqueRandomNumGenerator;
import shop.alien.util.common.constant.PaymentEnum;
import shop.alien.util.system.OSUtil;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 商户微信支付策略（预订订单）：业务与支付宝侧一致；支付接口走微信服务商模式（特约商户），
 * 与 {@link WeChatPartnerPaymentStrategyImpl} 一致使用 sp_mchid + sub_mchid、服务商证书；
 * 不查 {@code StorePaymentConfig}；预下单请求体不设 sub_appid（与 Partner 的 APP 预下单一致）。
 *
 * @author system
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MerchantWechatPaymentStrategyImpl implements MerchantPaymentStrategy {

    private static final String POSTMETHOD = "POST";
    private static final String GETMETHOD = "GET";
    private static final String REDIS_PREPAY_KEY_PREFIX = "merchant:wechat:prepay:order:";
    private static final long REDIS_PREPAY_EXPIRE_SECONDS = 15 * 60;
    /** 防抖 key 前缀：同一订单在有效期内仅允许一次创建预支付 */
    private static final String REDIS_PREPAY_DEBOUNCE_KEY_PREFIX = "merchant:wechat:prepay:debounce:order:";
    /** 防抖有效期（秒），期内重复调用返回请勿重复提交 */
    private static final long REDIS_PREPAY_DEBOUNCE_SECONDS = 5;
    /** 支付状态：退款中 */
    private static final int PAY_STATUS_REFUNDING = 4;
    /** 退款记录状态：退款失败 */
    private static final String REFUND_STATUS_FAIL = "FAIL";

    @Value("${payment.wechatPartnerPay.host:https://api.mch.weixin.qq.com}")
    private String wechatPayApiHost;
    @Value("${payment.wechatPartnerPay.prePayPath:/v3/pay/partner/transactions/app}")
    private String prePayPath;
    @Value("${payment.wechatPartnerPay.searchOrderByOutTradeNoPath:/v3/pay/partner/transactions/out-trade-no/{out_trade_no}}")
    private String searchOrderByOutTradeNoPath;
    @Value("${payment.wechatPartnerPay.refundPath:/v3/refund/domestic/refunds}")
    private String refundPath;

    @Value("${payment.wechatPartnerPay.business.spAppId}")
    private String spAppId;
    @Value("${payment.wechatPartnerPay.business.spMchId}")
    private String spMchId;

    @Value("${payment.wechatPartnerPay.business.win.privateKeyPath}")
    private String privateWinKeyPath;
    @Value("${payment.wechatPartnerPay.business.linux.privateKeyPath}")
    private String privateLinuxKeyPath;
    @Value("${payment.wechatPartnerPay.business.win.wechatPayPublicKeyFilePath}")
    private String wechatWinPayPublicKeyFilePath;
    @Value("${payment.wechatPartnerPay.business.linux.wechatPayPublicKeyFilePath}")
    private String wechatLinuxPayPublicKeyFilePath;
    @Value("${payment.wechatPartnerPay.business.merchantSerialNumber}")
    private String merchantSerialNumber;
    @Value("${payment.wechatPartnerPay.business.wechatPayPublicKeyId}")
    private String wechatPayPublicKeyId;

    /** 服务商下单回调 URL：优先 partner 配置（与 {@link WeChatPartnerPaymentStrategyImpl} 一致），否则回退直连配置 */
    @Value("${payment.wechatPartnerPay.business.prePayNotifyUrl:}")
    private String partnerPrePayNotifyUrl;
    @Value("${payment.wechatPay.business.prePayNotifyUrl:}")
    private String directPrePayNotifyUrl;
    @Value("${payment.wechatPartnerPay.business.refundNotifyUrl:}")
    private String partnerRefundNotifyUrl;
    @Value("${payment.wechatPay.business.refundNotifyUrl:}")
    private String directRefundNotifyUrl;

    private PrivateKey partnerPrivateKey;
    private PublicKey partnerWechatPayPublicKey;

    private final StoreInfoMapper storeInfoMapper;
    private final UserReservationOrderService userReservationOrderService;
    private final MerchantPaymentOrderService merchantPaymentOrderService;
    private final RefundRecordAsyncService refundRecordAsyncService;
    private final RefundRecordService refundRecordService;
    private final ReservationOrderPaymentTimeoutService reservationOrderPaymentTimeoutService;
    private final UserReservationService userReservationService;
    private final StringRedisTemplate stringRedisTemplate;

    @PostConstruct
    public void loadPartnerCertificates() {
        String privateKeyPath;
        String wechatPayPublicKeyFilePath;
        if ("windows".equals(OSUtil.getOsName())) {
            privateKeyPath = privateWinKeyPath;
            wechatPayPublicKeyFilePath = wechatWinPayPublicKeyFilePath;
        } else {
            privateKeyPath = privateLinuxKeyPath;
            wechatPayPublicKeyFilePath = wechatLinuxPayPublicKeyFilePath;
        }
        log.info("[MerchantWechat] 加载服务商商户证书私钥与平台公钥，os={}, keyPath={}", OSUtil.getOsName(), privateKeyPath);
        this.partnerPrivateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
        this.partnerWechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
    }

    @Override
    public R<Map<String, Object>> createPrePay(Integer storeId, Integer orderId, String amountYuan, String subject, Integer userId, String smid) {
        if (storeId == null || orderId == null) {
            return R.fail("门店ID和订单ID不能为空");
        }
        if (StringUtils.isBlank(amountYuan) || new BigDecimal(amountYuan).compareTo(BigDecimal.ZERO) <= 0) {
            return R.fail("支付金额必须大于0");
        }
        if (StringUtils.isBlank(subject)) {
            return R.fail("订单描述不能为空");
        }
        String subMchid = resolveSubMchidFromStore(storeId);
        if (StringUtils.isBlank(subMchid)) {
            return R.fail("门店未配置微信特约商户号 wechat_sub_mchid");
        }
        if (StringUtils.isBlank(spMchId) || StringUtils.isBlank(spAppId)) {
            return R.fail("服务商微信支付未配置（payment.wechatPartnerPay.business.spMchId / spAppId）");
        }
        if (partnerPrivateKey == null || partnerWechatPayPublicKey == null) {
            return R.fail("服务商微信证书未加载，请检查 payment.wechatPartnerPay 私钥与平台公钥路径");
        }
        UserReservationOrder order = userReservationOrderService.getById(orderId);
        if (order == null) {
            return R.fail("预订订单不存在");
        }
        if (!order.getStoreId().equals(storeId)) {
            return R.fail("订单与门店不匹配");
        }
        if (order.getPaymentStatus() != null && order.getPaymentStatus() == 1) {
            return R.fail("订单已支付");
        }

        String redisKey = REDIS_PREPAY_KEY_PREFIX + orderId;
        String cached = stringRedisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isNotBlank(cached)) {
            try {
                @SuppressWarnings("unchecked")
                Map<String, Object> data = JSON.parseObject(cached, Map.class);
                if (data != null && !data.isEmpty()) {
                    log.info("商户预订订单微信预支付命中缓存，orderId={}", orderId);
                    return R.data(data);
                }
            } catch (Exception e) {
                log.warn("解析微信预支付缓存失败，将重新发起，orderId={}", orderId, e);
            }
        }

        // 防抖：同一订单在有效期内只允许一次创建预支付，防止重复调用
        String debounceKey = REDIS_PREPAY_DEBOUNCE_KEY_PREFIX + orderId;
        Boolean debounceSet = stringRedisTemplate.opsForValue().setIfAbsent(debounceKey, "1", REDIS_PREPAY_DEBOUNCE_SECONDS, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(debounceSet)) {
            log.warn("商户微信预支付防抖拦截，orderId={}", orderId);
            return R.fail("请勿重复提交，请稍后再试");
        }

        int deleted = merchantPaymentOrderService.logicDeleteByOrderIdAndPayType(orderId, PaymentEnum.WECHAT_PAY.getType());
        if (deleted > 0) {
            log.info("未命中缓存，已逻辑删除该订单下微信支付单 {} 条，orderId={}", deleted, orderId);
        }

        String outTradeNo = UniqueRandomNumGenerator.generateUniqueCode(19);
        BigDecimal amount = new BigDecimal(amountYuan);

        MerchantPaymentOrder paymentOrder = new MerchantPaymentOrder();
        paymentOrder.setPaymentNo(merchantPaymentOrderService.generatePaymentNo());
        paymentOrder.setOrderType("reservation_order");
        paymentOrder.setOrderId(order.getId());
        paymentOrder.setOrderSn(order.getOrderSn());
        paymentOrder.setStoreId(storeId);
        paymentOrder.setPayType(PaymentEnum.WECHAT_PAY.getType());
        paymentOrder.setOutTradeNo(outTradeNo);
        paymentOrder.setPayAmount(amount);
        paymentOrder.setPayStatus(0);
        paymentOrder.setPayerUserId(userId);
        paymentOrder.setSubject(subject);
        paymentOrder.setCreatedTime(new Date());
        paymentOrder.setUpdatedTime(new Date());
        merchantPaymentOrderService.save(paymentOrder);

        try {
            WeChatPartnerPaymentStrategyImpl.PartnerAppPrepayRequest request = new WeChatPartnerPaymentStrategyImpl.PartnerAppPrepayRequest();
            request.spAppid = spAppId;
            request.spMchid = spMchId;
            request.subMchid = subMchid;
            request.description = subject;
            request.outTradeNo = outTradeNo;
            request.notifyUrl = resolveNotifyUrl(partnerPrePayNotifyUrl, directPrePayNotifyUrl);
            request.amount = new WeChatPaymentStrategyImpl.CommonAmountInfo();
            request.amount.total = amount.multiply(new BigDecimal(100)).longValue();
            request.amount.currency = "CNY";

            WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse response = partnerPrePayOrderRun(request);
            if (response == null || StringUtils.isBlank(response.prepayId)) {
                return R.fail("微信预支付失败");
            }

            long timestamp = System.currentTimeMillis() / 1000;
            String nonce = WXPayUtility.createNonce(32);
            // APP 调起支付验签串（与微信文档一致）：appId\ntimeStamp\nnonceStr\nprepayId\n
            // 须与传给 OpenSDK 的 appId、timeStamp、nonceStr、prepayId 完全一致；此处与直连 {@link WeChatPaymentStrategyImpl} 一致
            String message = String.format("%s\n%s\n%s\n%s\n", spAppId, timestamp, nonce, response.prepayId);
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(partnerPrivateKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            String signStr = Base64.getEncoder().encodeToString(sign.sign());

            Map<String, Object> data = new HashMap<>();
            data.put("outTradeNo", outTradeNo);
            data.put("orderSn", order.getOrderSn());
            data.put("orderId", order.getId());
            data.put("paymentNo", paymentOrder.getPaymentNo());
            data.put("prepayId", response.prepayId);
            data.put("appId", spAppId);
            data.put("spAppId", spAppId);
            data.put("spMchId", spMchId);
            data.put("subMchId", subMchid);
            data.put("mchId", subMchid);
            data.put("sign", signStr);
            data.put("timestamp", String.valueOf(timestamp));
            data.put("nonce", nonce);
            data.put("orderStr", "");

            String cacheJson = JSON.toJSONString(data);
            stringRedisTemplate.opsForValue().set(redisKey, cacheJson != null ? cacheJson : "{}", REDIS_PREPAY_EXPIRE_SECONDS, TimeUnit.SECONDS);
            log.info("商户预订订单微信预支付成功并写入缓存，storeId={}, orderSn={}, outTradeNo={}", storeId, order.getOrderSn(), outTradeNo);
            return R.data(data);
        } catch (WXPayUtility.ApiException e) {
            log.error("商户预订订单微信预支付异常，storeId={}, orderId={}", storeId, orderId, e);
            return R.fail("预支付失败：" + e.getMessage());
        } catch (Exception e) {
            log.error("商户微信预支付异常，storeId={}", storeId, e);
            return R.fail("预支付失败：" + e.getMessage());
        }
    }

    @Override
    public R<Object> queryPayStatus(Integer storeId, String outTradeNo) {
        log.info("queryPayStatus 开始 storeId={} outTradeNo={}", storeId, outTradeNo);
        if (storeId == null || StringUtils.isBlank(outTradeNo)) {
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=参数为空", storeId, outTradeNo);
            return R.fail("门店ID和商户订单号不能为空");
        }
        String subMchid = resolveSubMchidFromStore(storeId);
        if (StringUtils.isBlank(subMchid)) {
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=未配置特约商户号", storeId, outTradeNo);
            return R.fail("门店未配置微信特约商户号 wechat_sub_mchid");
        }
        if (partnerPrivateKey == null || partnerWechatPayPublicKey == null) {
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=服务商证书未加载", storeId, outTradeNo);
            return R.fail("服务商微信证书未加载");
        }
        MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
        if (paymentOrder == null) {
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=支付单不存在", storeId, outTradeNo);
            return R.fail("支付单不存在");
        }
        UserReservationOrder order = userReservationOrderService.getById(paymentOrder.getOrderId());
        if (order == null) {
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=预订订单不存在 orderId={}", storeId, outTradeNo, paymentOrder.getOrderId());
            return R.fail("预订订单不存在");
        }
        try {
            WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse response = partnerSearchOrderRun(outTradeNo, subMchid);
            if (response == null) {
                log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=微信返回为空", storeId, outTradeNo);
                return R.fail("查询失败");
            }
            log.info("queryPayStatus 微信查单结果 storeId={} outTradeNo={} tradeState={} tradeStateDesc={}", storeId, outTradeNo, response.tradeState, response.tradeStateDesc);
            if ("SUCCESS".equals(response.tradeState)) {
                Date now = new Date();
                paymentOrder.setPayStatus(1);
                paymentOrder.setTradeNo(response.transactionId);
                paymentOrder.setPayTime(now);
                paymentOrder.setUpdatedTime(now);
                merchantPaymentOrderService.updateById(paymentOrder);

                order.setPaymentStatus(1);
                order.setPayTime(now);
                order.setOutTradeNo(outTradeNo);
                order.setPaymentMethod("wechatPay");
                order.setOrderStatus(1);
                if (StringUtils.isBlank(order.getVerificationCode())) {
                    order.setVerificationCode("YS" + UniqueRandomNumGenerator.generateUniqueCode(10));
                }
                order.setUpdatedTime(now);
                userReservationOrderService.updateById(order);
                if (order.getReservationId() != null) {
                    UserReservation reservation = userReservationService.getById(order.getReservationId());
                    if (reservation != null) {
                        reservation.setStatus(1);
                        userReservationService.updateById(reservation);
                    }
                }
                reservationOrderPaymentTimeoutService.cancelReservationOrderPaymentTimeout(order.getOrderSn());
                log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=success tradeState=SUCCESS orderId={}", storeId, outTradeNo, order.getId());
                return R.success("支付成功");
            }
            if ("CLOSED".equals(response.tradeState)) {
                log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=交易已关闭", storeId, outTradeNo);
                return R.fail("交易已关闭");
            }
            if ("NOTPAY".equals(response.tradeState) || "USERPAYING".equals(response.tradeState)) {
                log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=等待用户付款 tradeState={}", storeId, outTradeNo, response.tradeState);
                return R.fail("等待用户付款");
            }
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=其他状态 tradeState={} tradeStateDesc={}", storeId, outTradeNo, response.tradeState, response.tradeStateDesc);
            return R.fail("订单状态：" + (response.tradeStateDesc != null ? response.tradeStateDesc : response.tradeState));
        } catch (WXPayUtility.ApiException e) {
            log.error("queryPayStatus 异常 storeId={} outTradeNo={} ApiException statusCode={} body={}", storeId, outTradeNo, e.getStatusCode(), e.getBody(), e);
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=ApiException", storeId, outTradeNo);
            return R.fail("查询异常：" + e.getMessage());
        } catch (Exception e) {
            log.error("queryPayStatus 异常 storeId={} outTradeNo={}", storeId, outTradeNo, e);
            log.info("queryPayStatus 结束 storeId={} outTradeNo={} result=fail reason=Exception", storeId, outTradeNo);
            return R.fail("查询失败：" + e.getMessage());
        }
    }

    @Override
    public R<String> refund(Integer storeId, String outTradeNo, String refundAmount, String refundReason, Integer refundType) {
        if (storeId == null || StringUtils.isBlank(outTradeNo)) {
            return R.fail("门店ID和商户订单号不能为空");
        }
        if (StringUtils.isBlank(refundAmount) || new BigDecimal(refundAmount).compareTo(BigDecimal.ZERO) <= 0) {
            return R.fail("退款金额必须大于0");
        }
        String subMchid = resolveSubMchidFromStore(storeId);
        if (StringUtils.isBlank(subMchid)) {
            return R.fail("门店未配置微信特约商户号 wechat_sub_mchid");
        }
        if (partnerPrivateKey == null || partnerWechatPayPublicKey == null) {
            return R.fail("服务商微信证书未加载");
        }
        MerchantPaymentOrder paymentOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
        if (paymentOrder == null) {
            return R.fail("支付单不存在");
        }
        UserReservationOrder order = userReservationOrderService.getById(paymentOrder.getOrderId());
        if (order == null) {
            return R.fail("预订订单不存在");
        }
        if (order.getPaymentStatus() == null || order.getPaymentStatus() != 1) {
            return R.fail("订单未支付或已退款，无法退款");
        }
        try {
            WeChatPartnerPaymentStrategyImpl.PartnerRefundCreateRequest request = new WeChatPartnerPaymentStrategyImpl.PartnerRefundCreateRequest();
            request.subMchid = subMchid;
            request.outTradeNo = outTradeNo;
            request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
            request.reason = StringUtils.isNotBlank(refundReason) ? refundReason : "用户申请退款";
            request.notifyUrl = resolveNotifyUrl(partnerRefundNotifyUrl, directRefundNotifyUrl);
            request.amount = new WeChatPaymentStrategyImpl.AmountReq();
            request.amount.refund = new BigDecimal(refundAmount).multiply(new BigDecimal(100)).longValue();
            // 微信 V3 amount.total 单位为分，depositAmount 为元，需 *100 转分
            request.amount.total = order.getDepositAmount().multiply(new BigDecimal(100)).longValue();
            request.amount.currency = "CNY";

            WeChatPaymentStrategyImpl.Refund response = null;
            WXPayUtility.ApiException lastApiException = null;
            int maxAttempts = 3;
            for (int attempt = 1; attempt <= maxAttempts; attempt++) {
                try {
                    response = partnerRefundRun(request);
                    break;
                } catch (WXPayUtility.ApiException e) {
                    lastApiException = e;
                    if (e.getStatusCode() == 429 && attempt < maxAttempts) {
                        long delayMs = 2000L * attempt;
                        log.warn("微信退款 429 限频，{}ms 后重试，第 {}/{} 次，outTradeNo={}", delayMs, attempt, maxAttempts, outTradeNo);
                        try {
                            Thread.sleep(delayMs);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                            markRefundFailed(order, paymentOrder, "操作过于频繁，请稍后再试", refundReason, storeId, outTradeNo);
                            return R.fail("操作过于频繁，请稍后再试");
                        }
                    } else {
                        if (e.getStatusCode() == 429) {
                            break;
                        }
                        throw e;
                    }
                }
            }
            if (response == null) {
                String msg = lastApiException != null && lastApiException.getStatusCode() == 429
                        ? (StringUtils.isNotBlank(lastApiException.getErrorMessage()) ? lastApiException.getErrorMessage() : "操作过于频繁，请稍后再试")
                        : "退款失败";
                markRefundFailed(order, paymentOrder, msg, refundReason, storeId, outTradeNo);
                return R.fail(msg);
            }
            String status = response.status != null ? response.status.name() : "";
            if (!"SUCCESS".equals(status) && !"PROCESSING".equals(status)) {
                String failMsg = "退款失败：" + status;
                markRefundFailed(order, paymentOrder, failMsg, refundReason, storeId, outTradeNo);
                return R.fail(failMsg);
            }

            Date now = new Date();
            BigDecimal refundAmountDecimal = new BigDecimal(refundAmount);
            paymentOrder.setPayStatus(3);
            paymentOrder.setUpdatedTime(now);

            order.setOrderStatus(7);
            order.setPaymentStatus(2);
            order.setRefundAmount(refundAmountDecimal);
            order.setRefundTime(now);
            order.setRefundReason(refundReason);
            order.setUpdatedTime(now);
            order.setRefundType(refundType);
            userReservationOrderService.updateById(order);

            //修改用户预约信息表状态
            UserReservation reservation = userReservationService.getById(order.getReservationId());
            if (reservation != null) {
                reservation.setStatus(3);
                userReservationService.updateById(reservation);
            }

            RefundRecord record = new RefundRecord();
            record.setPayType(PaymentEnum.WECHAT_PAY.getType());
            record.setOutTradeNo(outTradeNo);
            record.setTransactionId(response.transactionId);
            record.setOutRefundNo(response.outRefundNo != null ? response.outRefundNo : request.outRefundNo);
            record.setRefundId(response.refundId);
            record.setRefundStatus("SUCCESS");
            record.setRefundAmount(refundAmountDecimal.multiply(new BigDecimal(100)).longValue());
            record.setRefundReason(refundReason);
            record.setOrderId(String.valueOf(order.getId()));
            record.setStoreId(storeId);
            record.setUserId(order.getUserId());
            record.setCreatedTime(now);
            record.setCreatedUserId(order.getUserId());
            record.setUpdatedUserId(order.getUserId());
            record.setDeleteFlag(0);
            refundRecordAsyncService.completeRefundAsync(paymentOrder, record);

            log.info("商户预订订单微信退款成功，outTradeNo={}", outTradeNo);
            return R.data("退款成功");
        } catch (WXPayUtility.ApiException e) {
            log.error("商户预订订单微信退款异常，outTradeNo={}", outTradeNo, e);
            String errMsg = e.getStatusCode() == 429
                    ? (StringUtils.isNotBlank(e.getErrorMessage()) ? e.getErrorMessage() : "操作过于频繁，请稍后再试")
                    : "退款失败：" + e.getMessage();
            MerchantPaymentOrder payOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
            UserReservationOrder resOrder = payOrder != null ? userReservationOrderService.getById(payOrder.getOrderId()) : null;
            if (resOrder != null && payOrder != null) {
                markRefundFailed(resOrder, payOrder, errMsg, refundReason, storeId, outTradeNo);
            }
            return R.fail(errMsg);
        } catch (Exception e) {
            log.error("商户微信退款异常，storeId={}", storeId, e);
            MerchantPaymentOrder payOrder = merchantPaymentOrderService.getByOutTradeNo(outTradeNo);
            UserReservationOrder resOrder = payOrder != null ? userReservationOrderService.getById(payOrder.getOrderId()) : null;
            if (resOrder != null && payOrder != null) {
                markRefundFailed(resOrder, payOrder, "退款失败：" + e.getMessage(), refundReason, storeId, outTradeNo);
            }
            return R.fail("退款失败：" + e.getMessage());
        }
    }

    /** 退款失败时：订单与支付单置为退款中，并写入退款失败记录 */
    private void markRefundFailed(UserReservationOrder order, MerchantPaymentOrder paymentOrder,
                                  String errorMsg, String refundReason, Integer storeId, String outTradeNo) {
        try {
            Date now = new Date();
            order.setPaymentStatus(PAY_STATUS_REFUNDING);
            order.setUpdatedTime(now);
            userReservationOrderService.updateById(order);
            paymentOrder.setPayStatus(PAY_STATUS_REFUNDING);
            paymentOrder.setUpdatedTime(now);
            merchantPaymentOrderService.updateById(paymentOrder);
            RefundRecord record = new RefundRecord();
            record.setOutTradeNo(outTradeNo);
            record.setPayType(PaymentEnum.WECHAT_PAY.getType());
            record.setRefundStatus(REFUND_STATUS_FAIL);
            record.setOrderId(String.valueOf(order.getId()));
            record.setStoreId(storeId);
            record.setUserId(order.getUserId());
            record.setRefundReason(StringUtils.isNotBlank(refundReason) ? refundReason : "退款失败");
            record.setErrorMsg(errorMsg != null ? errorMsg : "退款失败");
            record.setDeleteFlag(0);
            refundRecordService.save(record);
        } catch (Exception ex) {
            log.error("标记退款失败状态异常，outTradeNo={}", outTradeNo, ex);
        }
    }

    @Override
    public String getType() {
        return PaymentEnum.WECHAT_PAY.getType();
    }

    private WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse partnerPrePayOrderRun(
            WeChatPartnerPaymentStrategyImpl.PartnerAppPrepayRequest request) throws IOException {
        String uri = prePayPath;
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(spMchId, merchantSerialNumber, partnerPrivateKey, POSTMETHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(reqBody, jsonMediaType);
        reqBuilder.method(POSTMETHOD, body);
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(wechatPayPublicKeyId, partnerWechatPayPublicKey, httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse.class);
            }
            throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
        }
    }

    private WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse partnerSearchOrderRun(String outTradeNo, String subMchid) throws IOException {
        String uri = searchOrderByOutTradeNoPath.replace("{out_trade_no}", WXPayUtility.urlEncode(outTradeNo));
        Map<String, Object> args = new HashMap<>();
        args.put("sp_mchid", spMchId);
        args.put("sub_mchid", subMchid);
        String queryString = WXPayUtility.urlEncode(args);
        if (!queryString.isEmpty()) {
            uri = uri + "?" + queryString;
        }
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(spMchId, merchantSerialNumber, partnerPrivateKey, GETMETHOD, uri, null));
        reqBuilder.method(GETMETHOD, null);
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .build();
        try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(wechatPayPublicKeyId, partnerWechatPayPublicKey, httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse.class);
            }
            throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
        }
    }

    private WeChatPaymentStrategyImpl.Refund partnerRefundRun(WeChatPartnerPaymentStrategyImpl.PartnerRefundCreateRequest request) throws IOException {
        String uri = refundPath;
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(spMchId, merchantSerialNumber, partnerPrivateKey, POSTMETHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(reqBody, jsonMediaType);
        reqBuilder.method(POSTMETHOD, body);
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(wechatPayPublicKeyId, partnerWechatPayPublicKey, httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.Refund.class);
            }
            throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
        }
    }

    private static String resolveNotifyUrl(String primary, String fallback) {
        if (StringUtils.isNotBlank(primary)) {
            return primary.trim();
        }
        if (StringUtils.isNotBlank(fallback)) {
            return fallback.trim();
        }
        return "";
    }

    private String resolveSubMchidFromStore(Integer storeId) {
        if (storeId == null) {
            log.warn("[MerchantWechat] storeId 为空，无法解析 wechat_sub_mchid");
            return null;
        }
        StoreInfo info = storeInfoMapper.selectById(storeId);
        if (info == null) {
            log.warn("[MerchantWechat] 门店不存在 storeId={}", storeId);
            return null;
        }
        if (StringUtils.isBlank(info.getWechatSubMchid())) {
            log.warn("[MerchantWechat] 门店未配置 wechat_sub_mchid storeId={}", storeId);
            return null;
        }
        return info.getWechatSubMchid().trim();
    }
}
