微信支付对接记录,和遇到的坑,总结!并附带微信支付工具类,只需要替换几个参数就可以直接使用!

第一个坑,签名获取,微信是两次签名加密,要按照微信官方文档一步步查看参数是否正确.

第二个坑,md5加密,我用到网上的md5加密方法,当小程序调起微信支付的时候一直报签名失败,最后发现是签名的md5加密的问题.

第三个坑,两次签名加密如果用到时间戳和随机字符串,一定要保持一致,用同一个,否则也会报出签名异常.

第四个坑,微信支付不能重复提交不一样的订单,比如:用户待付款,商家修改价格,然后提交就会出现订单重复问题.解决方案:就是业务订单号和支付订单号分开处理,每次支付都生成支付单号就也可以解决.

 

下面是微信支付的工具类,只需要替换一下

小程序ID
商户号
密钥key

 

package com.gqsh.other.pay.wechat;

/**
 * 小程序微信支付工具类
 *
 * @author wsh
 */

import com.gqsh.other.entity.WxPayParams;
import com.gqsh.other.entity.WxQueryResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springblade.core.tool.utils.StringUtil;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * Function: 小程序微信支付工具类
 *
 * @author att
 * @version 1.0
 * @since JDK1.8
 */
@Slf4j
public class WxPayUtil {

  /**
   * 小程序ID
   */
  private final static String APPID = " ";
  /**
   * 商户号
   */
  private final static String MCH_ID = " ";
  /**
   * 密钥
   */
  private final static String KEY = " ";
  /**
   * 餐见小程序(随便写的,没有实际意义,微信必传参数)
   */
  private final static String BODY = "cjxcx";
  /**
   * 交易类型
   */
  private final static String TRADE_TYPE_JSAPI = "JSAPI";
  /**
   * 微信统一下单路径
   */
  private final static String WX_TYXD_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  /**
   * 微信查询支付结果路径
   */
  private final static String WX_ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
  /**
   * 签名类型
   */
  private final static String SIGN_TYPE_MD5 = "MD5";

  public final static String SUCCESS = "SUCCESS";

  private final static String FAIL = "FAIL";

  private final static String OK = "OK";


  /**
   * 获取当前机器的ip
   *
   * @return String
   */
  public static String getLocalIp() {
    InetAddress ia = null;
    String localip = null;
    try {
      ia = ia.getLocalHost();
      localip = ia.getHostAddress();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return localip;

  }

  /**
   * Map转换为 Xml
   *
   * @param map
   * @return Xml
   * @throws Exception
   */
  public static String mapToXml(SortedMap<String, String> map) throws Exception {
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    //防止XXE攻击
    documentBuilderFactory.setXIncludeAware(false);
    documentBuilderFactory.setExpandEntityReferences(false);
    DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
    org.w3c.dom.Document document = documentBuilder.newDocument();
    org.w3c.dom.Element root = document.createElement("xml");
    document.appendChild(root);
    for (String key : map.keySet()) {
      String value = map.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();
    try {
      writer.close();
    } catch (Exception ex) {
    }
    return output;
  }


  /**
   * 创建签名Sign
   *
   * @param key
   * @param parameters
   * @return
   */
  public static String createSign(SortedMap<String, String> parameters, String key) {
    StringBuffer sb = new StringBuffer();
    Set es = parameters.entrySet();
    Iterator<?> it = es.iterator();
    while (it.hasNext()) {
      Map.Entry entry = (Map.Entry) it.next();
      String k = (String) entry.getKey();
      if (entry.getValue() != null || !"".equals(entry.getValue())) {
        String v = String.valueOf(entry.getValue());
        if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
          sb.append(k + "=" + v + "&");
        }
      }
    }
    sb.append("key=" + key);
    String sign = DigestUtils.md5Hex(getContentBytes(sb.toString(), "UTF-8")).toUpperCase();
    return sign;
  }


  /**
   * XML转换为Map
   *
   * @param strXML
   * @return Map
   * @throws Exception
   */
  public static Map<String, Object> getMapFromXML(String strXML) throws Exception {
    try {
      Map<String, Object> data = new HashMap<String, Object>();
      DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
      //防止XXE攻击
      documentBuilderFactory.setXIncludeAware(false);
      documentBuilderFactory.setExpandEntityReferences(false);
      DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
      InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
      org.w3c.dom.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) {
        ex.printStackTrace();
      }
      return data;
    } catch (Exception ex) {
      throw ex;
    }
  }

  /**
   * 生成随机数
   *
   * @return
   */
  public static String makeUUID(int len) {
    return UUID.randomUUID().toString().replaceAll("-", "").substring(0, len);
  }

  /**
   * 获取当前的Timestamp
   *
   * @return
   */
  public static String getCurrentTimeStamp() {
    return Long.toString(System.currentTimeMillis() / 1000);
  }


  /**
   * Xml字符串转换为Map
   *
   * @param xmlStr
   * @return
   */
  public static Map<String, String> xmlStrToMap(String xmlStr) {
    Map<String, String> map = new HashMap<String, String>();
    Document doc;
    try {
      doc = DocumentHelper.parseText(xmlStr);
      Element root = doc.getRootElement();
      List children = root.elements();
      if (children != null && children.size() > 0) {
        for (int i = 0; i < children.size(); i++) {
          Element child = (Element) children.get(i);
          map.put(child.getName(), child.getTextTrim());
        }
      }
    } catch (DocumentException e) {
      e.printStackTrace();
    }
    return map;
  }

  public static byte[] getContentBytes(String content, String charset) {
    if (charset == null || "".equals(charset)) {
      return content.getBytes();
    }
    try {
      return content.getBytes(charset);
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
    }
  }

  /**
   * 微信支付统一下单接口
   *
   * @param orderCode (订单支付单号)商户系统内部订单号,要求32个字符内,只能是数字、大小写字母
   * @param totalFee  订单总金额,单位为分
   * @param localIp   终端ip地址(非必填)
   * @param openId    用户openId
   * @param notifyUrl 微信回调地址(非必填)回调地址不能包含参数,具体看微信文档
   * @return
   * @throws Exception
   */
  public static WxPayParams wxPay(String orderCode, String totalFee, String localIp, String openId,
      String notifyUrl)
      throws Exception {
    WxPayParams wxPayParams = new WxPayParams();
    //随机字符串
    String nonce_str = WxPayUtil.makeUUID(32);
    //封装参数
    SortedMap<String, String> paramMap = new TreeMap<String, String>();
    paramMap.put("appid", APPID);
    paramMap.put("mch_id", MCH_ID);
    paramMap.put("nonce_str", nonce_str);
    paramMap.put("body", BODY);
    paramMap.put("out_trade_no", orderCode);
    paramMap.put("total_fee", totalFee);
    if (StringUtil.isBlank(localIp)) {
      paramMap.put("spbill_create_ip", WxPayUtil.getLocalIp());
    } else {
      paramMap.put("spbill_create_ip", localIp);
    }
    if (StringUtil.isNotBlank(notifyUrl)) {
      paramMap.put("notify_url", notifyUrl);
    } else {
      paramMap.put("notify_url", "http://www.weixin.qq.com/wxpay/pay.php");
    }
    paramMap.put("trade_type", TRADE_TYPE_JSAPI);
    paramMap.put("openid", openId);
    paramMap.put("sign", createSign(paramMap, KEY));
    //转换为xml
    String s = mapToXml(paramMap);
    log.info("微信统一支付参数:" + s);
    String post = cn.hutool.http.HttpUtil.post(WX_TYXD_URL, s);
    Map<String, String> map = xmlStrToMap(post);
    log.info("微信统一支付返回结果:" + map);
    //封装二次签名参数
    SortedMap<String, String> resultMap = new TreeMap<String, String>();
    if (SUCCESS.equals(map.get("return_code")) && OK.equals(map.get("return_msg"))) {
      if (FAIL.equals(map.get("result_code"))) {
        throw new Exception("FAIL:" + map.get("err_code_des"));
      }
      resultMap.put("appId", APPID);
      resultMap.put("timeStamp", WxPayUtil.getCurrentTimeStamp());
      resultMap.put("nonceStr", nonce_str);
      resultMap.put("package", "prepay_id=" + map.get("prepay_id"));
      resultMap.put("signType", SIGN_TYPE_MD5);
      resultMap.put("paySign", createSign(resultMap, KEY));

      //统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=
      wxPayParams.setWxPackage(resultMap.get("package"));
      //随机字符串,长度为32个字符以下
      wxPayParams.setNonceStr(resultMap.get("nonceStr"));
      //微信支付的paySign(第二次加密结果)
      wxPayParams.setPaySign(resultMap.get("paySign"));
      //签名加密方式
      wxPayParams.setSignType(resultMap.get("signType"));
      //时间戳
      wxPayParams.setTimeStamp(resultMap.get("timeStamp"));
    } else {
      throw new Exception("FAIL:" + map.get("return_msg"));
    }
    return wxPayParams;
  }

  /**
   * 微信支付结果查询
   *
   * @param outTradeNo    商户订单号
   * @param transactionId 微信订单号
   */
  public static WxQueryResult orderQuery(String outTradeNo, String transactionId) throws Exception {
    if (StringUtil.isBlank(outTradeNo) && StringUtil.isBlank(transactionId)) {
      throw new Exception("FAIL:参数不能为空");
    }
    WxQueryResult wxQueryResult = null;
    //封装参数
    SortedMap<String, String> paramMap = new TreeMap<String, String>();
    if (StringUtil.isNotBlank(transactionId)) {
      paramMap.put("transaction_id", transactionId);
    } else {
      paramMap.put("out_trade_no", outTradeNo);
    }
    paramMap.put("appid", APPID);
    paramMap.put("mch_id", MCH_ID);
    paramMap.put("nonce_str", WxPayUtil.makeUUID(32));
    paramMap.put("sign", createSign(paramMap, KEY));
    log.info("微信支付结果查询参数:" + paramMap);
    String s = mapToXml(paramMap);
    String post = cn.hutool.http.HttpUtil.post(WX_ORDERQUERY_URL, s);
    Map<String, String> map = WxPayUtil.xmlStrToMap(post);
    log.info("微信支付结果查询结果:" + map);
    if (SUCCESS.equals(map.get("return_code")) && OK.equals(map.get("return_msg"))) {
      if (FAIL.equals(map.get("result_code"))) {
        throw new Exception("FAIL:" + map.get("err_code_des"));
      }
      wxQueryResult = new WxQueryResult();
      wxQueryResult.setTransactionId(map.get("transaction_id"));
      wxQueryResult.setNonceStr(map.get("nonce_str"));
      wxQueryResult.setTradeState(map.get("trade_state"));
      wxQueryResult.setBankType(map.get("bank_type"));
      wxQueryResult.setOpenId(map.get("openid"));
      wxQueryResult.setSign(map.get("sign"));
      wxQueryResult.setMchId(map.get("mch_id"));
      wxQueryResult.setCashFee(map.get("cash_fee"));
      wxQueryResult.setOutTradeNo(map.get("out_trade_no"));
      wxQueryResult.setAppId(map.get("appid"));
      wxQueryResult.setTotalFee(map.get("total_fee"));
      wxQueryResult.setTradeStateDesc(map.get("trade_state_desc"));
      wxQueryResult.setTradeType(map.get("trade_type"));
      wxQueryResult.setResultCode(map.get("result_code"));
      wxQueryResult.setTimeEnd(map.get("time_end"));
      wxQueryResult.setIsSubscribe(map.get("is_subscribe"));
      wxQueryResult.setResultCode(map.get("SUCCESS"));
    } else {
      throw new Exception("FAIL:" + map.get("return_msg"));
    }
    return wxQueryResult;
  }

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值