WxPayManagerImpl.java 16.8 KB
package com.hotent.lpg.user.manager.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.hotent.base.exception.BaseException;
import com.hotent.base.util.BeanUtils;
import com.hotent.lpg.common.enums.DdZffsEnum;
import com.hotent.lpg.common.enums.DdlyEnum;
import com.hotent.lpg.common.enums.DdztEnum;
import com.hotent.lpg.common.enums.SysEnum;
import com.hotent.lpg.common.model.WCzzfpz;
import com.hotent.lpg.common.model.WDd;
import com.hotent.lpg.user.dao.CzzfpzDao;
import com.hotent.lpg.user.dao.DdDao;
import com.hotent.lpg.user.manager.WxPayManager;
import com.hotent.lpg.user.util.LocalDateTimeUtils;
import com.hotent.lpg.user.util.WxPayConfiguration;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.rmi.ServerError;
import java.time.LocalDateTime;
import java.util.HashMap;

@Service
@Slf4j
public class WxPayManagerImpl implements WxPayManager {


    @Value("${payment.hostUrl}")
    private String hostUrl;

    @Resource
    private DdDao ddDao;

    @Resource
    private CzzfpzDao czzfpzDao;

    /**
     * 发起退款请求
     *
     * @param ddid
     * @return
     */
    @Override
    public String initiateRefundOrder(String ddid) {
        // 1. 查询订单信息
        WDd wDd = ddDao.selectById(ddid);
        if (!DdztEnum.PAID.getCode().equals(wDd.getFDdzt())) {
            throw new RuntimeException("订单状态不正确");
        }
        if (!(wDd.getFDdly().equals(DdlyEnum.xsxd.getCode()) && wDd.getFZffs().equals(DdZffsEnum.WX_PAY.getCode()) && wDd.getFSfzf().equals(SysEnum.Y.getCode()))) {
            throw new RuntimeException("订单状态不正确");
        }
        // 2. 获取支付配置
        WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery().eq(WCzzfpz::getFCzid, wDd.getFCzid()).eq(WCzzfpz::getFZflx, wDd.getFZffs()));
        if (wCzzfpz == null) {
            throw new RuntimeException("支付配置不存在");
        }
        // 3. 初始化微信支付服务
        WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
        try {
            // 4. 调用微信退款接口
            // 这里需要构造退款请求对象,具体参数根据实际情况填写
            // 注意:这里需要根据实际的微信支付SDK版本和退款接口文档来调整
            // 示例代码可能需要根据实际情况修改
            String tkdh = RandomUtil.randomString(32);
            WxPayRefundRequest refundRequest = new WxPayRefundRequest();
            refundRequest.setTransactionId(wDd.getFZfdh());
            refundRequest.setOutTradeNo(wDd.getFDddh()); // 订单号
            refundRequest.setOutRefundNo(RandomUtil.randomString(32)); // 退款单号
            refundRequest.setTotalFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue()); // 订单总金额
            refundRequest.setRefundFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue()); // 退款金额
            refundRequest.setNotifyUrl(hostUrl + "/user/wxPay/handleRefundSuccessCallback");
            // 发起退款请求
            Object payment = wxPayService.refund(refundRequest);
            // 返回结果
            return tkdh;
        } catch (WxPayException e) {
            log.error("退款失败: {}", e.getErrCodeDes(), e);
            throw new RuntimeException("退款失败: " + e.getErrCodeDes());
        }
    }

    /**
     * 处理退款成功回调
     *
     * @param xmlData
     * @return
     */
    @Override
    public String handleRefundSuccessCallback(String xmlData) {
        // 将XML格式的回调数据转换为WxPayOrderNotifyResult对象
        WxPayOrderNotifyResult rs = WxPayOrderNotifyResult.fromXML(xmlData);
        // 获取回调中的返回码
        String returnCode = rs.getReturnCode();
        // 如果返回码不是"SUCCESS",则生成失败响应并返回
        if (!returnCode.equals("SUCCESS")) {
            return generateXmlResponse("FAIL", "退款失败", returnCode);
        }
        // 获取回调中的appid和mchId
        String appid = rs.getAppid();
        String mchId = rs.getMchId();
        // 从数据库中查询对应的应用配置信息
        WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery()
                .eq(WCzzfpz::getFAppid, appid)
                .eq(WCzzfpz::getFMchid, mchId)
                .last("LIMIT 1"));
        // 如果没有找到对应的配置信息,则抛出异常
        if (wCzzfpz == null) {
            throw new BaseException("退款失败,请联系管理员。");
        }
        // 根据配置信息创建WxPayService对象
        WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
        try {
            // 解析退款通知结果
            WxPayRefundNotifyResult notifyResult = wxPayService.parseRefundNotifyResult(xmlData);
            // 获取退款请求信息
            WxPayRefundNotifyResult.ReqInfo reqInfo = notifyResult.getReqInfo();
            // 提取退款相关信息
            String refundId = reqInfo.getRefundId();
            String successTime = reqInfo.getSuccessTime();
            String transactionId = reqInfo.getTransactionId();
            String outTradeNo = reqInfo.getOutTradeNo();
            // 从数据库中查询订单信息
            WDd wDd = ddDao.selectOne(Wrappers.<WDd>lambdaQuery()
                    .eq(WDd::getFZfdh, transactionId)
                    .eq(WDd::getFDddh, outTradeNo));
            // 如果没有查询到订单,则生成失败响应并返回
            if (wDd == null) {
                return generateXmlResponse("FAIL", "未查询到订单", outTradeNo);
            }
            // 如果订单已经退款,则生成失败响应并返回
            if (wDd.getFSftk().equals(SysEnum.Y.getCode())) {
                return generateXmlResponse("FAIL", "订单已退款", outTradeNo);
            }
            // 更新订单状态为已退款,并记录相关信息
            wDd.setFDdzt(DdztEnum.REFUNDED.getCode());
            wDd.setFSftk(SysEnum.Y.getCode());
            wDd.setFTkdh(refundId);
            wDd.setFTksj(LocalDateTimeUtils.parse(successTime));
            // 保存更新后的订单信息
            ddDao.updateById(wDd);
            // 记录日志信息
            log.info("微信退款成功: {}", wDd.getFDddh());
            // 生成成功响应并返回
            return generateXmlResponse("SUCCESS", "成功", outTradeNo);
        } catch (WxPayException e) {
            // 记录微信支付异常信息
            log.error("微信退款回调处理失败: {}", e.getErrCodeDes(), e);
            // 生成失败响应并返回
            return generateXmlResponse("FAIL", e.getErrCodeDes(), "");
        } catch (Exception e) {
            // 记录其他异常信息
            log.error("处理退款回调时发生未知错误: ", e);
            // 生成失败响应并返回
            return generateXmlResponse("FAIL", "处理退款回调时发生未知错误", "");
        }
    }


    /**
     * 处理支付成功回调
     *
     * @param xmlData
     * @return
     */
    @Override
    public String handlePaySuccessCallback(String xmlData) {
        // 从 XML 数据中提取支付单号
        String outTradeNo = extractValueFromXML(xmlData, "out_trade_no");
        //先用订单号找到订单
        WDd wDd = ddDao.selectOne(new LambdaQueryWrapper<WDd>().eq(WDd::getFDddh, outTradeNo));
        //在用 场站信息  拿到场站对应得支付配置
        WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery().eq(WCzzfpz::getFCzid, wDd.getFCzid()).eq(WCzzfpz::getFZflx, wDd.getFZffs()));
        WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
        //下面依旧是走微信sdk,查询订单和验证订单,为了保证数据得安全性,还是重复验证一次
        String dddh = null;
        try {
            WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData);
            // 获取订单编号
            dddh = notifyResult.getOutTradeNo();
            // 获取微信支付单号
            String transactionId = notifyResult.getTransactionId();
            wDd = ddDao.selectOne(new LambdaQueryWrapper<WDd>().eq(WDd::getFDddh, dddh));
            if (wDd == null) {
                log.warn("订单不存在: {}", dddh);
                return generateXmlResponse("FAIL", "订单不存在", dddh);
            }
            // 检查订单状态是否已支付
            if (SysEnum.Y.getCode().equals(wDd.getFDdzt())) {
                log.warn("订单 {} 已经处理过,忽略本次通知", dddh);
                return generateXmlResponse("SUCCESS", "已处理过", dddh);
            }
            BigDecimal payPrice = wDd.getFDdje();
            if (payPrice.multiply(new BigDecimal(100)).intValue() != notifyResult.getTotalFee()) {
                log.warn("付款金额与订单金额不等: {}", dddh);
                return generateXmlResponse("FAIL", "付款金额与订单金额不等", dddh);
            }

            String timeEnd = notifyResult.getTimeEnd();
            LocalDateTime paymentTime = LocalDateTimeUtils.parse(timeEnd);
            wDd.setFFksj(paymentTime);
            wDd.setFSfzf(SysEnum.Y.getCode());
            wDd.setFDdzt(DdztEnum.PAID.getCode());
            wDd.setFZfdh(transactionId);
            ddDao.updateById(wDd);

            log.info("订单支付成功: {}", dddh);
            return generateXmlResponse("SUCCESS", "成功", dddh);
        } catch (WxPayException e) {
            log.error("支付回调处理失败: {}", e.getErrCodeDes(), e);
            return generateXmlResponse("FAIL", e.getErrCodeDes(), dddh);
        }
    }

    /**
     * 生成预支付订单
     *
     * @param wDd
     * @param openId
     * @return
     */
    @Override
    public HashMap<String, Object> generatePrepayOrder(WDd wDd, String openId) {
        try {
            WCzzfpz wCzzfpz = czzfpzDao.selectOne(Wrappers.<WCzzfpz>lambdaQuery().eq(WCzzfpz::getFCzid, wDd.getFCzid()).eq(WCzzfpz::getFZflx, wDd.getFZffs()));
            if (BeanUtils.isEmpty(wCzzfpz) || wCzzfpz == null) {
                throw new RuntimeException("支付配置不存在");
            }
            String tradeType = wDd.getFZflx();
            if (StrUtil.isBlank(tradeType)) {
                throw new RuntimeException("支付方式错误");
            }
            WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest();
            wxPayUnifiedOrderRequest.setAppid(wCzzfpz.getFAppid());
            wxPayUnifiedOrderRequest.setOpenid(null);
            wxPayUnifiedOrderRequest.setBody(wDd.getFDddh());
            wxPayUnifiedOrderRequest.setOutTradeNo(wDd.getFDddh());
            wxPayUnifiedOrderRequest.setTotalFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue());
            wxPayUnifiedOrderRequest.setTradeType(tradeType);
            JSONObject scene_info = new JSONObject();
            scene_info.put("id", "LPG");
            scene_info.put("name", "燃气");
            wxPayUnifiedOrderRequest.setSceneInfo(scene_info.toString());
            wxPayUnifiedOrderRequest.setNotifyUrl(hostUrl + "/user/wxPay/handlePaySuccessCallback");   // 支付回调地址,开放不用登录
            wxPayUnifiedOrderRequest.setSpbillCreateIp("127.0.0.1");
            // trade_type=APP时 移动应用内的支付场景
            if ("APP".equals(wxPayUnifiedOrderRequest.getTradeType())) {
                wxPayUnifiedOrderRequest.setAppid(wCzzfpz.getFAppid());
            }
            // trade_type=NATIVE时  线下消费场景
            if ("NATIVE".equals(wxPayUnifiedOrderRequest.getTradeType())) {
                wxPayUnifiedOrderRequest.setProductId(wxPayUnifiedOrderRequest.getOutTradeNo());
            }
            // 公众号内或者微信内的网页支付
            if ("JSAPI".equals(wxPayUnifiedOrderRequest.getTradeType())) {
                wxPayUnifiedOrderRequest.setOpenid(openId);
            }
            // 手机浏览器中的支付场景
            if ("MWEB".equals(wxPayUnifiedOrderRequest.getTradeType())) {
                wxPayUnifiedOrderRequest.setSceneInfo(null);
            }
            WxPayService wxPayService = WxPayConfiguration.getPayService(wCzzfpz);
            Object payment = wxPayService.createOrder(wxPayUnifiedOrderRequest);
            HashMap<String, Object> resultData = new HashMap<String, Object>();
            resultData.put("payment", payment);
            resultData.put("ddid", wDd.getId());
            resultData.put("dddh", wDd.getFDddh());
            return resultData;
        } catch (WxPayException e) {
            if ("INVALID_REQUEST".equals(e.getErrCode())) {
                throw new RuntimeException("订单号重复,请重新下单");
            }
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 生成微信回调响应
     *
     * @param returnCode
     * @param returnMsg
     * @param outTradeNo
     * @return
     */
    private String generateXmlResponse(String returnCode, String returnMsg, String outTradeNo) {
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder;
        try {
            dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.newDocument();

            Element rootElement = doc.createElement("xml");
            doc.appendChild(rootElement);

            Element returnCodeElement = doc.createElement("return_code");
            returnCodeElement.appendChild(doc.createTextNode(returnCode));
            rootElement.appendChild(returnCodeElement);

            Element returnMsgElement = doc.createElement("return_msg");
            returnMsgElement.appendChild(doc.createTextNode(returnMsg));
            rootElement.appendChild(returnMsgElement);

            Element outTradeNoElement = doc.createElement("out_trade_no");
            outTradeNoElement.appendChild(doc.createTextNode(outTradeNo));
            rootElement.appendChild(outTradeNoElement);

            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(doc);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            transformer.transform(source, result);
            return writer.toString();
        } catch (Exception e) {
            log.error("构建 XML 响应失败", e);
            return "<xml><return_code>FAIL</return_code><return_msg>处理失败</return_msg></xml>";
        }
    }

    /**
     * 从 XML 数据中提取指定参数的值
     *
     * @param xmlData
     * @param paramName
     * @return
     */
    private String extractValueFromXML(String xmlData, String paramName) {
        // 解析 XML 数据以提取指定参数的值
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            InputSource is = new InputSource(new StringReader(xmlData));
            Document doc = dBuilder.parse(is);

            Element element = doc.getDocumentElement();
            Node paramNode = element.getElementsByTagName(paramName).item(0).getFirstChild();
            return paramNode != null ? paramNode.getNodeValue() : null;
        } catch (Exception e) {
            log.error("解析 XML 数据失败: {}", e.getMessage(), e);
            return null;
        }
    }

}