SpringBoot集成微信支付微信退款

微信支付

备注:本次支付接入的是微信支付v2.0版本,其中所有的请求格式均为XML。

如有需求可直接参阅官方文档 https://pay.weixin.qq.com/wiki/doc/api/index.html

官网支付demo地址(v2.0): https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

微信支付V3.0版本是基于JSON格式的API。

个人支付案例git地址[微信支付/支付宝支付/华为支付/苹果支付/小米支付]:https://gitee.com/wazk2008/demo-pay

1.下单支付(跳转微信进行支付)

微信接口名称: 统一下单

微信接口地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

个人支付代码demo

@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class PayController {

    @Autowired
    private WechatService wechatService;

    @PostMapping(value = "/wechat")
    public Result<PayWechatResponse> payWechat (@RequestBody PayWechatRequest payWechatRequest) {
        try {
            log.info("微信支付接口开始执行,请求参数:{}", JSON.toJSONString(payWechatRequest));
            PayWechatResponse payWechatResponse = wechatService.pay(payWechatRequest);
            log.info("微信支付接口执行成功,请求参数:{},响应参数:{}", JSON.toJSONString(payWechatRequest), JSON.toJSON(payWechatResponse));
            return Result.getSuccess(payWechatResponse);
        } catch (ResponseException e) {
            log.error("微信支付接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSONString(payWechatRequest), e.getMessage());
            e.printStackTrace();
            return Result.getFail(e);
        } catch (Exception e) {
            log.error("微信支付接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSONString(payWechatRequest), e.getMessage());
            e.printStackTrace();
            return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
        }
    }

}
@Service
@Slf4j
public class WechatService {

    @Autowired
    private WechatConfig wechatConfig;
    @Autowired
    private Map<String, WechatAppConfig> wechatApps;
    @Autowired
    private HttpXmlRequest httpXmlRequest;

    public PayWechatResponse pay (PayWechatRequest payWechatRequest) throws Exception {
        // 这里可以写各种校验
        // 校验appid是否合法
        String appid = payWechatRequest.getAppid();
        WechatAppConfig wechatApp = wechatApps.get(appid);
        if (Objects.isNull(wechatApp)) {
            throw new ResponseException(ResultCode.WECHAT_PAY_APPID_ERROR);
        }

        // 校验支付方式tradeType是否合法
        String tradeType = payWechatRequest.getTradeType();
        boolean isContainTradeType = wechatApp.getTradeType().contains(tradeType);
        if (!isContainTradeType) {
            throw new ResponseException(ResultCode.WECHAT_PAY_TRADE_TYPE_NAME_ERROR);
        }

        // todo 校验该比订单的支付金额是否合法
        // todo 校验该笔订单的状态是否为待支付

        // 获取请求微信的xml参数
        String xml = getPayValidRequestXml(payWechatRequest);
        String responseXml = httpXmlRequest.postXml(wechatConfig.getUnifiedOrderUrl(), xml);
        Map<String, String> map = WXPayUtil.xmlToMap(responseXml);
        // 返回状态码==SUCCESS && 业务结果==SUCCESS  表示退款申请成功
        if (WechatConstant.SUCCESS.equals(map.get(WechatConstant.RETURN_CODE)) && WechatConstant.SUCCESS.equals(map.get(WechatConstant.RESULT_CODE))) {
            // todo 这里可以保存用户微信支付的唤醒记录
            WechatTradeTypeEnum wechatTradeTypeEnum = WechatTradeTypeEnum.valueOf(payWechatRequest.getTradeType());
            return generatePayWechatResponse(wechatTradeTypeEnum, appid, map);
        }
        throw new ResponseException(ResultCode.WECHAT_PAY_ERROR);
    }

    // 获取请求微信的xml参数
    private String getPayValidRequestXml (PayWechatRequest payWechatRequest) throws Exception {
        // 获取请求微信的xml对应的map
        Map<String, String> payXmlMap = getPayXmlMap(payWechatRequest);

        // 校验生成的签名
        boolean signatureValid = WXPayUtil.isSignatureValid(payXmlMap, wechatApps.get(payWechatRequest.getAppid()).getAppSecret());
        if (!signatureValid) {
            throw new ResponseException(ResultCode.WECHAT_PAY_SIGNATURE_VALID_ERROR);
        }

        //
        return WXPayUtil.mapToXml(payXmlMap);
    }


    private Map<String, String> getPayXmlMap (PayWechatRequest payWechatRequest) {
        Map<String, String> reqData = new HashMap<>();

        String appid = payWechatRequest.getAppid();
        WechatAppConfig wechatApp = wechatApps.get(appid);

        reqData.put(WechatConstant.APPID, appid);
        reqData.put(WechatConstant.MCH_ID, wechatApp.getMchId());
        reqData.put(WechatConstant.NONCE_STR , WXPayUtil.generateNonceStr());
        reqData.put(WechatConstant.BODY , payWechatRequest.getBody());
        reqData.put(WechatConstant.OUT_TRADE_NO , payWechatRequest.getOrderNo());
        reqData.put(WechatConstant.TOTAL_FEE , payWechatRequest.getFee());
        reqData.put(WechatConstant.SPBILL_CREATE_IP , payWechatRequest.getSpbillCreateIp());
        reqData.put(WechatConstant.NOTIFY_URL , wechatConfig.getUnifiedOrderCallbackUrl());
        reqData.put(WechatConstant.TRADE_TYPE , payWechatRequest.getTradeType());
        if (WechatTradeTypeEnum.JSAPI.name().equals(payWechatRequest.getTradeType())) {
            reqData.put(WechatConstant.OPENID , payWechatRequest.getOpenid());
        }
        if (WechatConstant.MD5.equals(wechatApp.getSignType())) {
            reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.MD5);
        }
        else if (WechatConstant.HMACSHA256.equals(wechatApp.getSignType())) {
            reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.HMACSHA256);
        } else {
            throw new ResponseException(ResultCode.WECHAT_CONFIG_SIGN_TYPE_ERROR);
        }
        try {
            reqData.put(WechatConstant.SIGN, WXPayUtil.generateSignature(reqData, wechatApp.getAppSecret(), WechatSignTypeEnum.valueOf(wechatApp.getSignType())));
        } catch (Exception e) {
            log.error("微信支付生成签名错误");
            e.printStackTrace();
            throw new ResponseException(ResultCode.WECHAT_PAY_GENERATE_SIGN_ERROR);
        }
        return reqData;
    }


    private PayWechatResponse generatePayWechatResponse(WechatTradeTypeEnum wechatTradeTypeEnum, String appid, Map<String, String> map) throws Exception {
        PayWechatResponse payWechatResponse = new PayWechatResponse();
        switch (wechatTradeTypeEnum) {
            case APP:
                payWechatResponse.setAppResponse(generatePayWechatAppResponse(appid, map));
                break;
            case JSAPI:
                payWechatResponse.setJsapiResponse(generatePayWechatJsapiResponse(appid, map));
                break;
            case NATIVE:
                break;
            case MWEB:
                break;
            default:
                break;
        }
        return payWechatResponse;
    }

    private PayWechatJsapiResponse generatePayWechatJsapiResponse (String appid, Map<String, String> map) throws Exception{
        // 对于微信返回的参数再签名
        Map<String, String> pkg = new HashMap<>();
        pkg.put("appId", appid);
        pkg.put("timeStamp", String.valueOf(System.currentTimeMillis()/1000));
        pkg.put("nonceStr", WXPayUtil.generateNonceStr());
        pkg.put("package", "prepay_id=" + map.get(WechatConstant.PREPAY_ID));
        pkg.put("signType", wechatApps.get(appid).getSignType());
        String sign = WXPayUtil.generateSignature(pkg, wechatApps.get(appid).getAppSecret(), WechatSignTypeEnum.valueOf(wechatApps.get(appid).getSignType()));

        // 将再签名后的数据响应出去
        PayWechatJsapiResponse payWechatJsapiResponse = new PayWechatJsapiResponse();
        payWechatJsapiResponse.setAppid(appid);
        payWechatJsapiResponse.setTimeStamp(pkg.get("timeStamp"));
        payWechatJsapiResponse.setNonceStr(pkg.get("nonceStr"));
        payWechatJsapiResponse.setPkg(pkg.get("package"));
        payWechatJsapiResponse.setSignType(wechatApps.get(appid).getSignType());
        payWechatJsapiResponse.setPaySign(sign);
        payWechatJsapiResponse.setMchId(wechatApps.get(appid).getMchId());
        payWechatJsapiResponse.setPrepayId(map.get(WechatConstant.PREPAY_ID));
        return payWechatJsapiResponse;
    }

    private PayWechatAppResponse generatePayWechatAppResponse (String appid, Map<String, String> map) throws Exception{
        // 对于微信返回的参数再签名
        Map<String, String> pkg = new HashMap<>();
        pkg.put("appid", appid);
        pkg.put("partnerid", wechatApps.get(appid).getMchId());
        pkg.put("prepayid", map.get(WechatConstant.PREPAY_ID));
        pkg.put("package", "Sign=WXPay");
        pkg.put("noncestr", WXPayUtil.generateNonceStr());
        pkg.put("timestamp", String.valueOf(System.currentTimeMillis()/1000));
        String sign = WXPayUtil.generateSignature(pkg, wechatApps.get(appid).getAppSecret(), WechatSignTypeEnum.valueOf(wechatApps.get(appid).getSignType()));

        // 将再签名后的数据响应出去
        PayWechatAppResponse payWechatAppResponse = new PayWechatAppResponse();
        payWechatAppResponse.setAppid(appid);
        payWechatAppResponse.setPartnerid(wechatApps.get(appid).getMchId());
        payWechatAppResponse.setPrepayid(map.get(WechatConstant.PREPAY_ID));
        payWechatAppResponse.setPkg(pkg.get("package"));
        payWechatAppResponse.setNoncestr(pkg.get("noncestr"));
        payWechatAppResponse.setTimestamp(pkg.get("timestamp"));
        payWechatAppResponse.setSign(sign);
        return payWechatAppResponse;
    }

}

2.微信支付回调

@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class PayController {

    @Autowired
    private WechatService wechatService;
  
    @PostMapping(value = "/wechatCallback")
    public Result<Object> payWechatCallback (@RequestBody PayWechatCallbackRequest payWechatCallbackRequest) {
        try {
            log.info("微信支付回调接口开始执行,请求参数:{}", JSON.toJSON(payWechatCallbackRequest));
            wechatService.payCallback(payWechatCallbackRequest);
            log.info("微信支付回调接口执行成功,请求参数:{}", JSON.toJSON(payWechatCallbackRequest));
            return Result.getSuccess();
        } catch (ResponseException e) {
            log.error("微信支付回调接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSON(payWechatCallbackRequest), e.getMessage());
            e.printStackTrace();
            return Result.getFail(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("微信支付回调接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSON(payWechatCallbackRequest), e.getMessage());
            e.printStackTrace();
            return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
        }
    }

}
@Service
@Slf4j
public class WechatService {

    @Autowired
    private WechatConfig wechatConfig;
    @Autowired
    private Map<String, WechatAppConfig> wechatApps;
  
    public void payCallback (PayWechatCallbackRequest payWechatCallbackRequest) throws Exception {
        String xml = payWechatCallbackRequest.getXml();
        Map<String, String> map = getValidResponseMap(xml);

        // todo 校验订单是否存在
        String outTradeNo = map.get(WechatConstant.OUT_TRADE_NO);

        // todo 校验订单是否已支付
      
        // todo 校验支付金额
        Integer totalFee = Integer.valueOf(map.get(WechatConstant.TOTAL_FEE));

        // todo 修改支付和订单的状态
        // 微信的支付交易流水号
        String transactionId = map.get(WechatConstant.TRANSACTION_ID);
        // 用户支付时间
        String payTime = map.get(WechatConstant.TIME_END);

    }

    private Map<String, String> getValidResponseMap (String xml) throws Exception {
        Map<String, String> map = WXPayUtil.xmlToMap(xml);
        String returnCode = map.get(WechatConstant.RETURN_CODE);
        if (!StringUtils.isEmpty(returnCode) && WechatConstant.SUCCESS.equals(returnCode)) {
            String appid = map.get(WechatConstant.APPID);
            WechatAppConfig wechatAppConfig = wechatApps.get(appid);
            if (!Objects.isNull(wechatAppConfig)) {
                log.error("微信支付回调返回的appid错误");
                throw new ResponseException(ResultCode.WECHAT_PAY_CALLBACK_APPID_ERROR);
            }
            boolean isSignatureValid = WXPayUtil.isSignatureValid(map, wechatAppConfig.getAppSecret(), WechatSignTypeEnum.valueOf(wechatAppConfig.getSignType()));
            if (isSignatureValid) {
                return map;
            } else {
                throw new ResponseException(ResultCode.WECHAT_PAY_CALLBACK_SIGNATURE_VALID_ERROR);
            }
        } else {
            log.error("微信支付回调返回的return_code字段错误");
            throw new ResponseException(ResultCode.WECHAT_PAY_CALLBACK_RETURN_CODE_ERROR);
        }
    }
  
}

3.微信支付退款

@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class RefundController {

    @Autowired
    private RefundService refundService;

    @PostMapping(value = "/orderRefund")
    public Result<Object> orderRefund (@RequestBody OrderRefundRequest orderRefundRequest) {
        try {
            log.info("订单退款接口开始执行,请求参数:{}", JSON.toJSONString(orderRefundRequest));
            refundService.orderRefund(orderRefundRequest);
            log.info("订单退款接口执行完成,请求参数:{}", JSON.toJSONString(orderRefundRequest));
            return Result.getSuccess();
        } catch (ResponseException e) {
            log.error("订单退款接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSONString(orderRefundRequest), e.getMessage());
            e.printStackTrace();
            return Result.getFail(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("订单退款接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSONString(orderRefundRequest), e.getMessage());
            e.printStackTrace();
            return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
        }
    }

}
@Service
@Slf4j
public class RefundService {

    @Autowired
    private WechatService wechatService;

    // 订单退款
    public void orderRefund (OrderRefundRequest orderRefundRequest) {
        String orderNo = orderRefundRequest.getOrderNo();
        Integer curRefundFee = orderRefundRequest.getFee();

        // 获取该订单的支付详情
        OrderPayInfo payInfo = getOrderPayInfo(orderNo);
        // 获取该订单的支付金额
        int orderPaidFee = getOrderPaidFee(orderNo);
        // 获取该订单的已退金额
        int orderRefundedFee = getOrderRefundedFee(orderNo);

        if (curRefundFee+orderRefundedFee > orderPaidFee) {
            // 订单可退余额不足
            log.info("订单可退余额不足,退款请求参数:{}", JSON.toJSONString(orderRefundRequest));
            throw new ResponseException(ResultCode.REFUND_FEE_NOT_ENOUGH_ERROR);
        }

        // 获取该订单的支付方式
        PayTypeEnum payTypeEnum = getOrderPayTypeEnum(orderNo);
        switch (payTypeEnum) {
            case WECHAT_PAY:
                wechatService.refund(payInfo, curRefundFee);
                break;
            case ALI_PAY:
                break;
            case HUAWEI_PAY:
                break;
            case APPLE_PAY:
                break;
            case MI_PAY:
                break;
            default:
                break;
        }

    }

    // 获取订单的支付详情
    private OrderPayInfo getOrderPayInfo (String orderNo) {
        return new OrderPayInfo();
    }
    // 获取订单的支付金额
    private int getOrderPaidFee (String orderNo) {
        return 0;
    }

    // 获取订单的已退金额
    private int getOrderRefundedFee (String orderNo) {
        return 0;
    }

    // 获取订单的支付方式
    private PayTypeEnum getOrderPayTypeEnum (String orderNo) {
        return PayTypeEnum.WECHAT_PAY;
    }

}
@Service
@Slf4j
public class WechatService {

    @Autowired
    private WechatConfig wechatConfig;
    @Autowired
    private Map<String, WechatAppConfig> wechatApps;
    @Autowired
    private HttpXmlRequest httpXmlRequest;

    // 微信退款
    public void refund (OrderPayInfo payInfo, Integer refundFee) {
        Map<String, String> refundXmlMap = getRefundXmlMap(payInfo, refundFee);
        String xml;
        try {
            xml = WXPayUtil.mapToXml(refundXmlMap);
        } catch (Exception e) {
            log.error("微信退款生成退款xml数据异常");
            e.printStackTrace();
            throw new ResponseException(ResultCode.WECHAT_REFUND_GENERATE_XML_ERROR);
        }

        String appid = "";
        WechatAppConfig wechatApp = wechatApps.get(appid);
        String mchId = wechatApp.getMchId();
        String certPath = wechatApp.getCert();
        try {
            Map<String, String> map = httpXmlRequest.postXmlWithCert(certPath, mchId, wechatConfig.getRefundOrderUrl(), xml);
            if (WechatConstant.SUCCESS.equals(map.get(WechatConstant.RETURN_CODE)) && WechatConstant.SUCCESS.equals(map.get(WechatConstant.RESULT_CODE))) {
                // todo 退款申请成功,记录退款数据到db
            }
        } catch (Exception e) {
            log.error("微信退款请求微信接口失败");
            e.printStackTrace();
            throw new ResponseException(ResultCode.WECHAT_REFUND_ERROR);
        }

    }

    private Map<String, String> getRefundXmlMap (OrderPayInfo payInfo, Integer curRefundFee) {
        // 从订单支付详payInfo中获取appid
        String appid = "";
        WechatAppConfig wechatApp = wechatApps.get(appid);
        String signType = wechatApp.getSignType();
        String appSecret = wechatApp.getAppSecret();

        // 从订单支付详payInfo中获取支付时微信的transaction_id
        String payTransactionId = "";

        // 生成退款时,请求微信的 out_refund_no
        String outRefundNo = "";

        // 订单的支付金额,单位为分
        String totalFee = "100";

        Map<String, String> reqData = new HashMap<>();
        reqData.put(WechatConstant.APPID, appid);
        reqData.put(WechatConstant.MCH_ID, wechatApp.getMchId());
        reqData.put(WechatConstant.NONCE_STR, WXPayUtil.generateNonceStr());
        reqData.put(WechatConstant.TRANSACTION_ID, payTransactionId);
        reqData.put(WechatConstant.OUT_REFUND_NO, outRefundNo);
        reqData.put(WechatConstant.TOTAL_FEE, totalFee);
        reqData.put(WechatConstant.REFUND_FEE, String.valueOf(curRefundFee));
        if (WechatConstant.MD5.equals(signType)) {
            reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.MD5);
        }
        else if (WechatConstant.HMACSHA256.equals(signType)) {
            reqData.put(WechatConstant.SIGN_TYPE, WechatConstant.HMACSHA256);
        }
        try {
            reqData.put(WechatConstant.SIGN, WXPayUtil.generateSignature(reqData, appSecret, WechatSignTypeEnum.valueOf(signType)));
        } catch (Exception e) {
            log.warn(e.getMessage());
            e.printStackTrace();
        }
        return reqData;
    }


}

微信支付官网demo工具类

package com.wazk.pay.utils;

import com.wazk.pay.constant.WechatConstant;
import com.wazk.pay.constant.WechatSignTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.math.BigDecimal;
import java.security.*;
import java.util.*;

@Slf4j
public class WXPayUtil {

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map<String, String> data) throws TransformerException, ParserConfigurationException {
        Document document = newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key : data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("获取微信支付的请求参数XML:{}", output);
        return output;
    }


    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
        return generateSignedXml(data, key, WechatSignTypeEnum.MD5);
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map<String, String> data, String key, WechatSignTypeEnum signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put(WechatConstant.SIGN, sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key    API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WechatConstant.SIGN)) {
            return false;
        }
        String sign = data.get(WechatConstant.SIGN);
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, WechatSignTypeEnum.MD5);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, WechatSignTypeEnum signType) throws Exception {
        if (!data.containsKey(WechatConstant.SIGN)) {
            return false;
        }
        String sign = data.get(WechatConstant.SIGN);
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WechatSignTypeEnum.MD5);
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, WechatSignTypeEnum signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals(WechatConstant.SIGN)) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        if (WechatSignTypeEnum.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (WechatSignTypeEnum.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 日志
     *
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳,单位秒
     *
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     *
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }


    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }


    /**
     * 获取请求文体
     *
     * @param request
     * @return
     * @throws IOException
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常!");
        } finally {
            reader.close();
        }
        return sb.toString();
    }

    /**
     * @param return_code:
     * @param return_msg:
     * @Description: 通过xml给微信返回数据
     * @Author: wazk2008
     * @Date: 2021/10/15 4:29 下午
     * @return: java.lang.String
     **/
    public static String setXml(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code + "]]>" +
                "</return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }

    /**
     * @Description: 初始化证书获取 SSLConnectionSocketFactory
     * @Author: wazk2008
     * @Date: 2021/10/27 6:48 下午
     * @param cert:
     * @param mchId:
     * @return: org.apache.http.conn.ssl.SSLConnectionSocketFactory
     **/
    public static SSLConnectionSocketFactory initCert(String cert, String mchId) throws Exception {
        // 加载本地的证书进行https加密传输
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        InputStream inputStream = new FileInputStream(cert);
        try {
            // 加载证书密码,默认为商户ID
            keyStore.load(inputStream, mchId.toCharArray());
        } finally {
            inputStream.close();
        }
        SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();

        SSLConnectionSocketFactory sslcsf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1.2"}, null, new DefaultHostnameVerifier());

        return sslcsf;
    }

    /**
     * @Description: 解析xml的输入流为map
     * @Author: wazk2008
     * @Date: 2021/10/28 2:11 下午
     * @param inputStream:
     * @return: java.util.Map<java.lang.String,java.lang.String>
     **/
    public static Map<String, String> parseXml(InputStream inputStream) throws IOException {
        SortedMap<String, String> map = new TreeMap<>();
        try {
            // 获取request输入流
            SAXReader reader = new SAXReader();
            org.dom4j.Document document = reader.read(inputStream);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素所有节点
            List<Element> elementList = root.elements();
            // 遍历所有子节点
            for (Element element : elementList) {
                map.put(element.getName(), element.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("*****微信工具类:解析xml异常*****");
        } finally {
            // 释放资源
            inputStream.close();
        }
        return map;
    }

    private static Cipher cipher = null;

    /**
     * @Description: 初始化解码器
     * @Author: wazk2008
     * @Date: 2021/10/28 5:01 下午
     * @param key:
     * @return: void
     **/
    private static synchronized void init(String key){
        if (cipher != null) {
            return;
        }
        String md5Key = DigestUtils.md5DigestAsHex(key.getBytes());
        SecretKeySpec secretKeySpec = new SecretKeySpec(md5Key.getBytes(), "AES");
        Security.addProvider(new BouncyCastleProvider());
        try {
            cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
    }

    /**
     * @Description: 微信退款回调时,解析reqInfo加密数据
     * @Author: wazk2008
     * @Date: 2021/10/28 4:36 下午
     * @param reqInfo:
     * @return: java.lang.String
     **/
    public static String parseReqInfo (String reqInfo, String key) throws IllegalBlockSizeException, BadPaddingException {
        Base64.Decoder decoder = Base64.getDecoder();
        byte[] base64ByteArr = decoder.decode(reqInfo);

        init(key);
        String result = new String(cipher.doFinal(base64ByteArr));

        return result;
    }

}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

难过的风景

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值