微信呼叫H5支付

心欲無痕CYY發表於2020-10-12

一:參考網站

微信開發文件:https://pay.weixin.qq.com/wiki/doc/api/index.html

二:準備

去商戶平臺獲取商戶賬戶id(appid)、微信支付商戶號、金鑰

微信支付閘道器:https://api.mch.weixin.qq.com/pay/unifiedorder

三:配置檔案yml

在yml檔案中配置微信付款需要的引數

#微信支付配置
wechat:
  #商戶賬號id
  appId: xxxxxxxxxx
  #微信支付商戶號
  mcHid: xxxxxxxx
  #微信支付非同步回撥地址
  notifyUrl: xxxxxxxxxxxxxxxxxxxxxxx
  #微信退款結果通知地址
  notifyRefundUrl: xxxxxxxxxxxxxxxxx
  #金鑰
  wechatPrivateKey: xxxxxxx
  #商品描述
  body: xxxxxxxxx
  #交易型別
  tradeType: MWEB
  openId:
  #微信支付閘道器
  weChatPrepayUrl: https://api.mch.weixin.qq.com/pay/unifiedorder
  #微信退款閘道器
  weChatRefundPrepayUrl: https://api.mch.weixin.qq.com/secapi/pay/refund
  #微信付款查詢訂單閘道器
  weChatQueryPrepayUrl: https://api.mch.weixin.qq.com/pay/orderquery
  #微信退款查詢閘道器
  weChatQueryRefundPrepayUrl: https://api.mch.weixin.qq.com/pay/refundquery
  #微信瀏覽器內調起獲取openid時的appid
  jsapiAppId: xxxxxxxxxxxx
  #JSAPI交易型別
  jsapiTradeType: JSAPI
  #appSecret
  appSecret: xxxxxxxxxxxxxxx

通過實體類可以獲取yml檔案配置的引數值:

package fly.system.bms.entity.input;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @ProjectName: fly-system-bms
 * @Package: fly.system.bms.entity.input
 * @ClassName: WeChatConfig
 * @Author: hfq
 * @Description: 微信支付的配置類
 * @Date: 2020/9/4 14:13
 * @Version: 1.0
 */
@Component
@Data
@ConfigurationProperties(prefix="wechat") //接收application.yml中的weChat下面的屬性
public class WeChatConfig {

    //微信賬戶id
    private String appId;

    //微信支付商戶號
    private String mcHid;

    //非同步接收微信支付結果通知的回撥地址
    private String notifyUrl;

    //金鑰
    private String wechatPrivateKey;

    //商品描述
    private String body;

    //交易型別
    private String tradeType;

    private String openId;

    //退款結果通知url
    private String notifyRefundUrl;

    //微信支付閘道器
    private String weChatPrepayUrl;

    //微信退款閘道器
    private String weChatRefundPrepayUrl;

    //微信付款查詢訂單閘道器
    private String weChatQueryPrepayUrl;

    //微信退款查詢閘道器
    private String weChatQueryRefundPrepayUrl;

    //微信瀏覽器內調起獲取openid時的appid
    private String jsapiAppId;

    //JSAPI交易型別
    private String jsapiTradeType;

    //appSecret
    private String appSecret;
}

四、呼叫第三方介面

private String weCateApiPay(BigDecimal amount) {
        String outTradeNo = OrderCodeFactory.getOrderCode(15L);
        //使用者真實ip:H5支付要求商戶在統一下單介面中上傳使用者真實ip地址“spbill_create_ip”
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String ip = SpbillGreateIp.getIpAddress(request);
        //獲取商品描述:body
        String body = weChatConfig.getBody();
        //交易型別
        String tradeType = weChatConfig.getTradeType();
        String openid = weChatConfig.getOpenId();
        //微信支付分配的公眾賬號ID(企業號corpid即為appid)
        String appid = weChatConfig.getAppId();
        //微信支付分配的商戶號
        String mchId = weChatConfig.getMcHid();
        //非同步接收微信支付結果的回撥地址
        String NnotifyUrl = weChatConfig.getNotifyUrl();
        //金鑰
        String wechatPlatformApiKey = weChatConfig.getWechatPrivateKey();
        //微信支付閘道器
        String weChatPrepayUrl = weChatConfig.getWeChatPrepayUrl();

        //呼叫微信支付的介面
        Map<String, String> map = WechatPayUtils.sendPayment(body, outTradeNo, amount, ip, tradeType,
                openid, NnotifyUrl, null, null, appid, mchId, wechatPlatformApiKey,weChatPrepayUrl);

        log.info("呼叫微信統一下單返回引數 {}",JSON.toJSONString(map));

        //處理業務
        

        //將微信返回的map集合中的地址返回給前臺
        return map.get("mweb_url");
    }

將呼叫第三方介面返回的地址返回給前端,前端可以通過這個url調起微信支付頁面

注:這個url必須在瀏覽器內開啟,無法直接在微信中開啟支付頁面(微信H5支付的特性)

return_code為FAIL的時候代表未調同第三方支付介面,可以通過return_msg查詢到報錯資訊

result_code為FAIL的時候代表呼叫第三方支付介面出錯,可以通過err_code和err_code_des查詢到出錯原因

WechatPayUtils
package fly.system.bms.utils;

import com.alibaba.fastjson.JSON;
import fly.cloud.common.base.enums.BusinessCodeEnum;
import fly.cloud.common.base.exception.BusinessException;
import fly.system.bms.entity.db.RefundInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.math.BigDecimal;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.util.*;

/**
 * @ClassName WechatPayUtils
 * @Description TODO 微信支付工具類
 * @Author liushan
 * @Date 2020/1/3 13:39
 **/
@Slf4j
public class WechatPayUtils {

    // JSAPI支付
    public static final String WECHAT_JSAPI = "JSAPI";
    // H5支付
    public static final String WECHAT_MWEB = "MWEB";

    //微信退款
    public static final String WECHAT_RETUND = "RETUND";
    //微信付款查詢
    public static final String WECHAT_PAY_QUERY = "PAYQUERY";
    //微信退款查詢
    public static final String WECHAT_RETUND_QUERY = "RETUNDQUEY";

    //微信回撥簽名key
    private static final String FIELD_SIGN = "sign";


    /*
     * 發起支付請求 body 商品描述 out_trade_no 訂單號 total_fee 訂單金額 單位 元 product_id 商品ID
     * appid:微信支付分配的公眾賬號ID(企業號corpid即為此appId)
     * mchid:微信支付分配的商戶號
     * openid:rade_type=JSAPI時(即JSAPI支付),此引數必傳,此引數為微信使用者在商戶對應appid下的唯一標識。
     * notify_url:非同步接收微信支付結果通知的回撥地址,通知url必須為外網可訪問的url,不能攜帶引數
     * sign:通過簽名演算法計算得出的簽名值
     * trade_type:交易型別
     * spbill_create_ip:支援IPV4和IPV6兩種格式的IP地址。使用者的客戶端IP
     */
    public static Map<String, String> sendPayment(String body,
                                                  String out_trade_no,
                                                  BigDecimal total_fee,
                                                  String ip,
                                                  String trade_type,
                                                  String openId,
                                                  String wechatNotifyUrl,
                                                  String wechatAppId,
                                                  String wechatMchId,
                                                  String wechatPlatformAppId,
                                                  String wechatPlatformMchId,
                                                  String wechatPlatformApiKey,
                                                  String weChatPrepayUrl) {
        String xml = wXParamGenerate(body, out_trade_no, total_fee, ip, trade_type, openId,
                wechatNotifyUrl, wechatAppId, wechatMchId, wechatPlatformAppId, wechatPlatformMchId, wechatPlatformApiKey);
        String res = httpsRequest(weChatPrepayUrl, "POST", xml);

        Map<String, String> data = null;
        try {
            data = doXMLParse(res);
        } catch (Exception e) {
        }
        return data;
    }

    // 微信統一下單引數設定
    private static String wXParamGenerate(String description,
                                          String outTradeNo,
                                          BigDecimal total_fee,
                                          String ip,
                                          String trade_type,
                                          String openId,
                                          String wechatNotifyUrl,
                                          String wechatAppId,
                                          String wechatMchId,
                                          String wechatPlatformAppId,
                                          String wechatPlatformMchId,
                                          String wechatPlatformApiKey
    ) {
        int fee = total_fee.multiply(new BigDecimal("100"))
                .setScale(0, BigDecimal.ROUND_HALF_UP)
                .intValue();
        Map<String, String> param = new HashMap<String, String>();
        param.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
        param.put("body", description);
        param.put("out_trade_no", outTradeNo);
        param.put("total_fee", fee + "");
        param.put("sign_type", "MD5");
        param.put("notify_url", wechatNotifyUrl);
        // 交易型別
        param.put("trade_type", trade_type);
        if (WECHAT_JSAPI.equals(trade_type)) {
            param.put("openid", openId);
            param.put("appid", wechatAppId);
            param.put("mch_id", wechatMchId);
            param.put("spbill_create_ip", ip);
        } else {
            param.put("appid", wechatPlatformAppId);
            param.put("mch_id", wechatPlatformMchId);
            if (WECHAT_MWEB.equals(trade_type)) {
                param.put("spbill_create_ip", ip);
            }
        }
        param.put(
                "scene_info",
                "{\"h5_info\":{\"type\":\"Wap\",\"wap_url\":\"http://" + "www.xueguoxue.com" + "\",\"wap_name\":\"您正在購買商品\"}}");
        log.info("驗籤引數集合 {}", JSON.toJSONString(param));
        String sign = getSign(param, wechatPlatformApiKey);
        param.put("sign", sign);
        return getMapToXML(param);
    }



    private static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        return sb.toString();
    }

    /**
     * @return java.lang.String
     * @Author liushan
     * @Description Map轉xml資料
     * @Date 2018/11/29 16:20
     * @Param [param]
     **/
    public static String getMapToXML(Map<String, String> param) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        for (Map.Entry<String, String> entry : param.entrySet()) {
            sb.append("<" + entry.getKey() + ">");
            sb.append(entry.getValue());
            sb.append("</" + entry.getKey() + ">");
        }
        sb.append("</xml>");
        return sb.toString();
    }



    /**
     * @return java.lang.String
     * @Author liushan
     * @Description 微信回撥返回引數
     * @Date 2018/11/29 16:18
     * @Param [return_code, return_msg]
     **/
    public static String setXML(HashMap<String,String> return_data) {
        String returnCode = return_data.get("return_code");
        String returnMsg = return_data.get("return_msg");
        return "<xml><return_code><![CDATA[" + returnCode
                + "]]></return_code><return_msg><![CDATA[" + returnMsg
                + "]]></return_msg></xml>";
    }

    /**
     * @return java.lang.String
     * @Author liushan
     * @Description 獲取返回給微信支付的簽名
     * @Date 2020年1月7日 17:24:37
     * @Param [weChatResult]
     **/
    public static String getPaySign(Map<String, String> weChatResult, String tradeType, String wechatPlatformApiKey) {
        String sign = "";
        Map<String, String> param = new HashMap<>();
        if (tradeType.equals(WECHAT_JSAPI)) {
            param.put("appId", weChatResult.get("appid"));
            param.put("nonceStr", weChatResult.get("nonce_str"));
            param.put("timeStamp", weChatResult.get("timestamp"));
            param.put("signType", weChatResult.get("sign_type"));
            param.put("package", "prepay_id=" + weChatResult.get("prepay_id"));
        } else {
            param.put("appid", weChatResult.get("appid"));
            param.put("partnerid", weChatResult.get("mch_id"));
            param.put("prepayid", weChatResult.get("prepay_id"));
            param.put("noncestr", weChatResult.get("nonce_str"));
            param.put("timestamp", weChatResult.get("timestamp"));
            param.put("package", weChatResult.get("packageInfo"));
        }
        sign = getSign(param, wechatPlatformApiKey);
        return sign;
    }

    /**
     * 微信退款服務方法
     * @return
     */
      public static Map<String, String> sendRefundPayment(RefundInfo refundInfo) {
        log.info("微信原路返回入參 {}", JSON.toJSONString(refundInfo));
        String refundFee = String.valueOf(refundInfo.getRefundFee().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue());
        String totalFee = String.valueOf(refundInfo.getTotalFee().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue());

        String xml = wXParam(refundInfo.getAppid(), refundInfo.getMchId(), refundInfo.getNotifyUrl(), refundFee, totalFee, refundInfo.getPayNumber(),
                refundInfo.getOutRefundNo(), refundInfo.getWechatPlatformApiKey(),WECHAT_RETUND);

        //微信退款傳送訊息
        //載入證照
        String res = initCert(refundInfo.getWeChatRefundPrepayUrl(), xml, refundInfo.getMchId());

        Map<String, String> data = null;
        try {
            data = doXMLParse(res);
        } catch (Exception e) {
        }
        return data;
    }

    /**
     * 微信支付查詢結果
     */
    public static Map<String, String> queryWeChatPay(String appid, String mchId, String serialNumber, String wechatPlatformApiKey,String weChatQueryPrepayUrl) {

        String xml = wXParam(appid, mchId,"","","","",serialNumber,wechatPlatformApiKey,WECHAT_PAY_QUERY);

        String res = httpsRequest(weChatQueryPrepayUrl, "POST", xml);

        Map<String, String> data = null;
        try {
            data = doXMLParse(res);
        } catch (Exception e) {
        }
        return data;
    }

    /**
     * 微信退款查詢結果
     */
    public static Map<String, String> queryRetundWeChatPay(String appid, String mchId, String serialNumber, String wechatPlatformApiKey,String weChatQueryRefundPrepayUrl) {
        String xml = wXParam(appid, mchId, "", "", "","" ,serialNumber,wechatPlatformApiKey,WECHAT_RETUND_QUERY);

        String res = httpsRequest(weChatQueryRefundPrepayUrl, "POST", xml);

        Map<String, String> data = null;
        try {
            data = doXMLParse(res);
        } catch (Exception e) {
        }
        return data;
    }

    // 微信退款/查詢引數設定
    private static String wXParam(String appid, String mchId, String refundNotifyUrl, String refundFee,String totalFee,
                                  String payNumber, String outTradeNo, String wechatPlatformApiKey,String type) {

        Map<String,String> param = new HashMap<String,String>();
        //微信退款
        if (WECHAT_RETUND.equals(type)){
            param.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
            param.put("appid",appid);
            param.put("mch_id",mchId);
            param.put("notify_url",refundNotifyUrl);
            param.put("refund_fee",refundFee);
            param.put("total_fee",totalFee);
            param.put("transaction_id",payNumber);
            param.put("out_refund_no",outTradeNo);
        }//微信付款查詢
        else if (WECHAT_PAY_QUERY.equals(type)){
            param.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
            param.put("appid",appid);
            param.put("mch_id",mchId);
            param.put("out_trade_no",outTradeNo);
//            param.put("transaction_id","4200000689202009029472000195");
        }//微信退款查詢
        else if (WECHAT_RETUND_QUERY.equals(type)){
            param.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
            param.put("appid",appid);
            param.put("mch_id",mchId);
            param.put("out_refund_no",outTradeNo);
        }
        String sign = getSign(param, wechatPlatformApiKey);

        param.put("sign", sign);

        return getMapToXML(param);
    }

    // 發起微信支付請求
    private static String httpsRequest(String requestUrl, String requestMethod,
                                       String outputStr) {
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        HttpURLConnection conn = null;
        try {
            URL url = new URL(requestUrl);
            conn = (HttpURLConnection) url.openConnection();

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 設定請求方式(GET/POST)
            conn.setRequestMethod(requestMethod);
            conn.setRequestProperty("content-type",
                    "application/x-www-form-urlencoded");
            // 當outputStr不為null時向輸出流寫資料
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意編碼格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 從輸入流讀取返回內容
            inputStream = conn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            return buffer.toString();
        } catch (ConnectException ce) {
            System.out.println("連線超時:{}" + ce);
        } catch (Exception e) {
            System.out.println("https請求異常:{}" + e);
        }finally {
            // 釋放資源
            try{
                if (Objects.nonNull(bufferedReader)){
                    bufferedReader.close();
                }
                if (Objects.nonNull(inputStreamReader)){
                    inputStreamReader.close();
                }
                if (Objects.nonNull(inputStream)){
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (Objects.nonNull(conn)){
                conn.disconnect();
            }
            inputStream = null;
        }
        return null;
    }

    // 組裝引數
    public static Map<String, String> doXMLParse(String strxml)
            throws Exception {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }

        Map<String, String> m = new HashMap<String, String>();
        InputStream in = null;
        try{
            in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
            SAXBuilder builder = new SAXBuilder();
            Document doc = builder.build(in);
            //指向根節點
            Element root = doc.getRootElement();
            List list = root.getChildren();
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String k = e.getName();
                String v = "";
                List children = e.getChildren();
                if (children.isEmpty()) {
                    v = e.getTextNormalize();
                } else {
                    v = getChildrenText(children);
                }

                m.put(k, v);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (Objects.nonNull(in)){
                // 關閉流
                in.close();
            }
        }
        return m;
    }

    /**
     * @Author liushan
     * @Description //TODO 獲取
     * @Date 2020/1/3 15:26
     * @Param [param, wechatPlatformApiKey]
     * @return java.lang.String
     **/
    public static String getSign(Map<String, String> param, String wechatPlatformApiKey) {
        log.info("param {}",JSON.toJSONString(param));
        String paramUrl = formatUrlMap(param, true, false);
        log.info("StringA {}",paramUrl);
        if (!StringUtils.isNotBlank(paramUrl)){
            return null;
        }
        String stringSignTemp = MD5Utils.md5(java.net.URLDecoder.decode(paramUrl) + "&key=" + wechatPlatformApiKey)
                .toUpperCase();
        return stringSignTemp;
    }

    /**
     * 方法用途: 對所有傳入引數按照欄位名的 ASCII 碼從小到大排序(字典序),並且生成url引數串<br>
     * 實現步驟: <br>
     *
     * @param paraMap    要排序的Map物件
     * @param urlEncode  是否需要URLENCODE
     * @param keyToLower 是否需要將Key轉換為全小寫 true:key轉化成小寫,false:不轉化
     * @return
     */
    private static String formatUrlMap(Map<String, String> paraMap,
                                       boolean urlEncode, boolean keyToLower) {
        if (paraMap == null) {
            return "";
        }
        String buff = "";
        Map<String, String> tmpMap = paraMap;
        try {
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(
                    tmpMap.entrySet());
            // 對所有傳入引數按照欄位名的 ASCII 碼從小到大排序(字典序)
            Collections.sort(infoIds,
                    new Comparator<Map.Entry<String, String>>() {
                        @Override
                        public int compare(Map.Entry<String, String> o1,
                                           Map.Entry<String, String> o2) {
                            return (o1.getKey()).toString().compareTo(
                                    o2.getKey());
                        }
                    });
            // 構造URL 鍵值對的格式
            StringBuilder buf = new StringBuilder();
            for (Map.Entry<String, String> item : infoIds) {
                String key = item.getKey();
                String val = item.getValue();
                if (urlEncode) {
                    val = URLEncoder.encode(val, "utf-8");
                }
                if (keyToLower) {
                    buf.append(key.toLowerCase() + "=" + val);
                } else {
                    buf.append(key + "=" + val);
                }
                buf.append("&");
            }
            buff = buf.toString();
            if (buff.equals("") == false) {
                buff = buff.substring(0, buff.length() - 1);
            }
        } catch (Exception e) {
            return null;
        }
        return buff;
    }



    //載入證照
    private static String initCert(String url,String xml,String mchId){
        RestTemplate restTemplate = null;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");//eg. PKCS12
            ClassPathResource cp = new ClassPathResource("apiclient_cert.p12");
            keyStore.load(cp.getInputStream(), mchId.toCharArray());

            SSLContext sslcontext = SSLContextBuilder.create()
                    .loadKeyMaterial(keyStore, mchId.toCharArray())
                    .build();

//            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE);
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
//            HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
//            restTemplate = new RestTemplate(factory);
//            //將轉換器的編碼換成utf-8
//            restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("utf-8")));


            try {
                HttpPost httpost = new HttpPost(url); // 設定響應頭資訊
                httpost.addHeader("Connection", "keep-alive");
                httpost.addHeader("Accept", "*/*");
                httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
                httpost.addHeader("Host", "api.mch.weixin.qq.com");
                httpost.addHeader("X-Requested-With", "XMLHttpRequest");
                httpost.addHeader("Cache-Control", "max-age=0");
                httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
                httpost.setEntity(new StringEntity(xml, "UTF-8"));
                CloseableHttpResponse response = httpClient.execute(httpost);
                try {
                    HttpEntity entity = response.getEntity();

                    String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                    EntityUtils.consume(entity);
                    return jsonStr;
                } finally {
                    response.close();
                }
            }finally {
                httpClient.close();
            }

        } catch (Exception e) {
            throw new BusinessException(BusinessCodeEnum.DATA_IS_NULL.getCode(), "微信退款載入證照失敗,請聯絡管理員!");
        }
    }
}
MD5Utils
package fly.system.bms.utils;

import java.security.MessageDigest;

public class MD5Utils {

	private static String byteArrayToHexString(byte b[]) {
		StringBuffer resultSb = new StringBuffer();
		for (int i = 0; i < b.length; i++)
			resultSb.append(byteToHexString(b[i]));

		return resultSb.toString();
	}

	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0)
			n += 256;
		int d1 = n / 16;
		int d2 = n % 16;
		return hexDigits[d1] + hexDigits[d2];
	}

	public static String MD5Encode(String origin, String charsetname) {
		String resultString = null;
		try {
			resultString = new String(origin);
			MessageDigest md = MessageDigest.getInstance("MD5");
			if (charsetname == null || "".equals(charsetname))
				resultString = byteArrayToHexString(md.digest(resultString
						.getBytes()));
			else
				resultString = byteArrayToHexString(md.digest(resultString
						.getBytes(charsetname)));
		} catch (Exception exception) {
		}
		return resultString;
	}

	private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
			"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
	
	// MD5加密
	public final static String md5(String s) {
		char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
				'A', 'B', 'C', 'D', 'E', 'F' };
		try {
			byte[] btInput = s.getBytes();
			// 獲得MD5摘要演算法的 MessageDigest 物件
			MessageDigest mdInst = MessageDigest.getInstance("MD5");
			// 使用指定的位元組更新摘要
			mdInst.update(btInput);
			// 獲得密文
			byte[] md = mdInst.digest();
			// 把密文轉換成十六進位制的字串形式
			int j = md.length;
			char str[] = new char[j * 2];
			int k = 0;
			for (int i = 0; i < j; i++) {
				byte byte0 = md[i];
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

}

五、微信支付非同步回撥

@Api(tags = {"【微信支付API】"})
@RestController
@RequestMapping("/wechat")
@Slf4j
public class WeCateApiController {
 /**
     * @return java.lang.String
     * @Author hfq
     * @Description
     * @Date 2020/1/3 15:49
     * @Param [request, response]
     **/
    @PostMapping("/callback")
    public String payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        InputStream inStream = request.getInputStream();
        StringBuffer sffer = new StringBuffer();
        BufferedReader buffInt = null;
        HashMap<String,String> return_data = null;
        try{
            String str = new String();
            return_data = new HashMap<String,String>();
            buffInt = new BufferedReader(new InputStreamReader(inStream, "UTF-8"));
            while ((str = buffInt.readLine()) != null) {
                sffer.append(str);
            }
            Map<String, String> results = WechatPayUtils.doXMLParse(sffer.toString());
            log.info("微信支付回撥引數【" + results + "】");


            //微信支付單號
            String transactionId = results.get("transaction_id");
            //微信訂單號
            String outTradeNo = results.get("out_trade_no");
            /**
              *判斷該筆訂單是否已經處理(去三方流水錶中查詢當前訂單的狀態) 
              */
          

            if(!"SUCCESS".equals(results.get("return_code").toString())) {
                return_data.put("return_code", "FAIL");
                return_data.put("return_msg", "return_code不正確");
            }

            if (!"SUCCESS".equals(results.get("result_code").toString())) {
                return_data.put("return_code", "FAIL");
                return_data.put("return_msg", "result_code不正確");
            }

            //判斷金額是否正確
            long total_amount = new BigDecimal(results.get("total_fee")).longValue();
            if ( total_amount != tripartitePay.getAmount().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).longValue()){
                return_data.put("return_code", "FAIL");
                return_data.put("return_msg", "返回金額不正確");
                throw new BusinessException(BusinessCodeEnum.RETURN_ERROR.getCode(), "金額total_amount錯誤");
            }

            /**
             * 第一步:首先通過resultCode判斷付款成功還是失敗
             * 第二步:如果付款失敗:
             *          1、修改三方流水錶
             */
            PayCallBackParam payCallBackParam = PayCallBackParam.builder()
                                                                .outTradeNo(outTradeNo)
                                                                .payNumber(transactionId)                                                         .timeEnd(results.get("time_end"))                                                                .totalFee(results.get("total_fee"))                                                                .returnMsg(results.get("return_msg"))                                                                .resultCode(results.get("result_code"))                                                                .errCode(results.get("err_code"))                                                                .errCodeDesc(results.get("err_code_des"))                                                                .payRetundType(PayRefundTypeEnum.PAY_MODE.getCode().toString())                                                                .transactionId(tripartitePay.getTransactionId())                                                               .tripartitePayInfoId(tripartitePay.getId())                                                               .transactionCode(tripartitePay.getTransactionCode())
                                                                .result(sffer.toString())
                                                                .build();

            //處理業務

            return_data.put("return_code","SUCCESS");
            return_data.put("return_msg","OK");
            return WechatPayUtils.setXML(return_data);

        }catch(BusinessException e){
            if (Objects.nonNull(return_data)){
                return_data.put("return_code", "FAIL");
                return_data.put("return_msg", "執行時異常");
            }
            throw new BusinessException(BusinessCodeEnum.SYSTEM_ERROR.getCode(),e.getMessage());
        }finally {
            if(Objects.nonNull(buffInt)){
                buffInt.close();
            }
            if(Objects.nonNull(inStream)){
                inStream.close();
            }
        }
    }

}

可以通過查詢result_code的值判斷是否支付成功

相關文章