Java實現公衆號H5 微信支付

最近由於有微信項目使用到了微信支付這功能,我原本以爲跟調用其它js接口一樣,非常簡單,實則不然,因爲微信團隊留下了很多天坑,提供的文檔全是老版的,下載下來無法使用,導致這個支付功能 害我整整調了一天,全是微信團隊留下的坑,有些簽名並不是下載事例所說那樣,接下來我將分享通過自身努力實現的微信支付成果:
第一步:微信支付配置文件 ConfigUtil
/**
* 微信支付配置文件
* @author Mark
*
*/

public class ConfigUtil {
/**
* 服務號相關信息
*/
public final static String APPID = “”;//服務號的appid
public final static String APP_SECRECT = “”;//服務號的appSecrect
public final static String TOKEN = ” “;//服務號的配置token
public final static String MCH_ID = “”;//開通微信支付分配的商戶號
public final static String API_KEY = “”;//商戶API密鑰 自行去商戶平臺設置
public final static String SIGN_TYPE = “MD5”;//簽名加密方式
//微信支付統一接口的回調action
public final static String NOTIFY_URL = “”; //用於告知微信服務器 調用成功

/**
 * 微信基礎接口地址
 */
 //獲取token接口(GET)
 public final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
 //oauth2授權接口(GET)
 public final static String OAUTH2_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
 //刷新access_token接口(GET)
 public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
// 菜單創建接口(POST)
 public final static String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
// 菜單查詢(GET)
 public final static String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
// 菜單刪除(GET)
public final static String MENU_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
/**
 * 微信支付接口地址
 */
//微信支付統一接口(POST)
public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//微信退款接口(POST)
public final static String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//訂單查詢接口(POST)
public final static String CHECK_ORDER_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
//關閉訂單接口(POST)
public final static String CLOSE_ORDER_URL = "https://api.mch.weixin.qq.com/pay/closeorder";
//退款查詢接口(POST)
public final static String CHECK_REFUND_URL = "https://api.mch.weixin.qq.com/pay/refundquery";
//對賬單接口(POST)
public final static String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
//短鏈接轉換接口(POST)
public final static String SHORT_URL = "https://api.mch.weixin.qq.com/tools/shorturl";
//接口調用上報接口(POST)
public final static String REPORT_URL = "https://api.mch.weixin.qq.com/payitil/report";

}
第二步:通用工具類 CommonUtil (用於請求接口)
/**
* 通用工具類
* @author Mark
*/
public class CommonUtil {
private static Logger log = LoggerFactory.getLogger(CommonUtil.class);
/**
* 發送https請求
* @param requestUrl 請求地址
* @param requestMethod 請求方式(GET、POST)
* @param outputStr 提交的數據
* @return 返回微信服務器響應的信息
*/
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
// 創建SSLContext對象,並使用我們指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance(“SSL”, “SunJSSE”);
sslContext.init(null, tm, new java.security.SecureRandom());
// 從上述SSLContext對象中得到SSLSocketFactory對象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(ssf);
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 inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, “utf-8”);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 釋放資源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
log.error(“連接超時:{}”, ce);
} catch (Exception e) {
log.error(“https請求異常:{}”, e);
}
return null;
}

public static String urlEncodeUTF8(String source){
    String result = source;
    try {
        result = java.net.URLEncoder.encode(source,"utf-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return result;
}

}
第三步:MD5處理 MD5Util
/**
* MD5處理
* @author Mark
*
*/
public class MD5Util {

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" };

}
第四步:信任管理器 微信的信任證書 MyX509TrustManager
/**
* 信任管理器
* @author Mark
*/
public class MyX509TrustManager implements X509TrustManager {

// 檢查客戶端證書
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

// 檢查服務器端證書
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}

// 返回受信任的X509證書數組
public X509Certificate[] getAcceptedIssuers() {
    return null;
}

}
第五步:微信H5支付 簽名 隨機碼 時間戳等管理 PayCommonUtil
/**
* 微信H5支付 簽名 隨機碼 時間戳等管理
* @author Mark
*
*/

public class PayCommonUtil {

/**
 * 獲取支付隨機碼
 * @return
 */
 public static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }
   /**
    * 獲取微信支付時間戳
    * @return
    */
    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * 獲取預支付ID時  獲取隨機碼
     * @param length
     * @return
     */
    public static String CreateNoncestr(int length) {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < length; i++) {
            Random rd = new Random();
            res += chars.indexOf(rd.nextInt(chars.length() - 1));
        }
        return res;
    }
    /**
     * 獲取預支付ID時  獲取隨機碼
     * @param length
     * @return
     */
    public static String CreateNoncestr() {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < 16; i++) {
            Random rd = new Random();
            res += chars.charAt(rd.nextInt(chars.length() - 1));
        }
        return res;
    }

/**
 * @author Mark
 * @Description:sign簽名
 * @param characterEncoding 編碼格式
 * @param parameters 請求參數
 * @return
 */
public static String createSign(SortedMap<Object,Object> parameters){
    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();
        Object v = entry.getValue();
        if(null != v && !"".equals(v) 
                && !"sign".equals(k) && !"key".equals(k)) {
            sb.append(k + "=" + v + "&");
        }
    }
    sb.append("key=" + ConfigUtil.API_KEY);
    String sign = MD5Util.MD5Encode(sb.toString(),"UTF-8").toUpperCase();
    return sign;
}


/**
 * @author Mark
 * @Description:將請求參數轉換爲xml格式的string
 * @param parameters  請求參數
 * @return
 */
public static String getRequestXml(SortedMap<Object,Object> parameters){
    StringBuffer sb = new StringBuffer();
    sb.append("<xml>");
    Set es = parameters.entrySet();
    Iterator it = es.iterator();
    while(it.hasNext()) {
        Map.Entry entry = (Map.Entry)it.next();
        String k = (String)entry.getKey();
        String v = (String)entry.getValue();
        if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
            sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
        }else {
            sb.append("<"+k+">"+v+"</"+k+">");
        }
    }
    sb.append("</xml>");
    return sb.toString();
}
/**
 * @author Mark
 * @Description:返回給微信的參數
 * @param return_code 返回編碼
 * @param return_msg  返回信息
 * @return
 */
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>";
}

}
第六步:調用微信H5支付統一下單接口 得到預支付ID WxPayUtil
/**
* 調用微信H5支付統一下單接口 得到預支付ID
* @author Mark
*
*/

public class WxPayUtil {

@SuppressWarnings("unchecked")
public static String unifiedorder(String body,String out_trade_no,String openid) {
    SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
    parameters.put("appid", ConfigUtil.APPID);

    parameters.put("mch_id", ConfigUtil.MCH_ID);
    parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());
    parameters.put("body", body);
    parameters.put("out_trade_no", out_trade_no);
    parameters.put("total_fee", "1");
    parameters.put("spbill_create_ip","113.57.246.11");
    parameters.put("notify_url", ConfigUtil.NOTIFY_URL);
    parameters.put("trade_type", "JSAPI");
    parameters.put("openid", openid);
    String sign = PayCommonUtil.createSign(parameters);
    parameters.put("sign", sign);
    String requestXML = PayCommonUtil.getRequestXml(parameters);
    System.out.println(requestXML.toString());
    String result =CommonUtil.httpsRequest(ConfigUtil.UNIFIED_ORDER_URL, "POST", requestXML);
    System.out.println(result.toString());
    Map<String, String> map=new HashMap<String, String>();
    try {
        map = XMLUtil.doXMLParse(result);
    } catch (JDOMException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }//解析微信返回的信息,以Map形式存儲便於取值
    return map.get("prepay_id").toString();
}

}
第七步:xml管理 XMLUtil
/**
* xml管理
* @author Mark
*
*/
public class XMLUtil {
/**
* 解析xml,返回第一級元素鍵值對。如果第一級元素有子節點,則此節點的值是子節點的xml數據。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst(“encoding=\”.*\”“, “encoding=\”UTF-8\”“);

    if(null == strxml || "".equals(strxml)) {
        return null;
    }

    Map m = new HashMap();

    InputStream 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 = XMLUtil.getChildrenText(children);
        }

        m.put(k, v);
    }

    //關閉流
    in.close();

    return m;
}

/**
 * 獲取子結點的xml
 * @param children
 * @return String
 */
public 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(XMLUtil.getChildrenText(list));
            }
            sb.append(value);
            sb.append("</" + name + ">");
        }
    }

    return sb.toString();
}

}
第八步:接下來是Controller中調用,方式如下:
@ApiOperation(value = “微信支付調用”, httpMethod = “POST”)
@RequestMapping(“/couponsConfirm”)
public String couponsConfirm(Model m,@RequestParam(“openid”)String openid,@RequestParam(“orderNo”)String orderNo) {
//openid可通過微信高級接口oath2.0網頁授權接口獲取到用戶信息,此接口本文中就不提供了,如果有需要,請留言。
m.addAttribute(“openid”, openid);
//orderNo是你的商品訂單號,自行生成的隨機訂單號,但是要保證隨機性,不能有重複訂單號。
m.addAttribute(“orderNo”, orderNo);

    String timeStamp=PayCommonUtil.create_timestamp();
    String nonceStr=PayCommonUtil.create_nonce_str();
    m.addAttribute("appid", ConfigUtil.APPID);
    m.addAttribute("timestamp", timeStamp);
    m.addAttribute("nonceStr", nonceStr);
    m.addAttribute("openid",openid);

    String prepayId=WxPayUtil.unifiedorder("外賣下單",orderNo, openid);

// String userAgent = request.getHeader(“user-agent”);
// char agent = userAgent.charAt(userAgent.indexOf(“MicroMessenger”)+15);
// m.addAttribute(“agent”, new String(new char[]{agent}));//微信版本號,用於前面提到的判斷用戶手機微信的版本是否是5.0以上版本。

    SortedMap<Object,Object> signParams = new TreeMap<Object,Object>();  
    signParams.put("appId", ConfigUtil.APPID);  
    signParams.put("nonceStr",nonceStr);  
    signParams.put("package", "prepay_id="+prepayId);  
    signParams.put("timeStamp", timeStamp);  
    signParams.put("signType", "MD5");  

    // 生成支付簽名,要採用URLENCODER的原始值進行SHA1算法!  
    String sign= PayCommonUtil.createSign(signParams);


    m.addAttribute("paySign", sign);

    m.addAttribute("packageValue", "prepay_id="+prepayId);

    return "跳轉到你的支付頁面";
}

最後一步:頁面調用
WeixinJSBridge.invoke(‘getBrandWCPayRequest’,{
“appId”:(“#appid”).val(),  
                            “timeStamp”:
(“#timestamp”).val(),
“nonceStr”:(“#nonceStr”).val(),   
                            “package”:
(“#packageValue”).val(),
“signType”:”MD5”,
“paySign”:$(“#paySign”).val()
},function(res){
WeixinJSBridge.log(res.err_msg);
if(res.err_msg == “get_brand_wcpay_request:ok”){
alert(“支付成功”);
}else if(res.err_msg == “get_brand_wcpay_request:cancel”){
alert(“取消支付!”)

    }else{  
         alert("支付失敗!")
    }  

到此,所有需要的代碼都已提供,各位在借鑑的時候,記得也要做些內容修改,ConfigUtil中的一些參數,需要各位自行提供。其它的都可以直接使用,微信支付整體並不難,難點就在於微信簽名,很多人調試微信支付,都被這個微信簽名給難倒了,這也是微信團隊的留下的一天坑。

**最後一句:如果各位在借鑑中,發現缺失什麼,可以私信找我要,也可以留言。如果覺得我寫的好,就來犒勞我一下吧這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章