開發(fā)者平臺(tái)地址:https://open.weixin.QQ.com/



APP 支付能力開通后,微信會(huì)給你一個(gè)商戶號(hào),用戶和密碼等信息。需要驗(yàn)證商戶信息,還需要設(shè)置一個(gè)加密的密鑰字段,這里就不一一細(xì)說了。
微信APP支付接口,是很好調(diào)試的,(不像微信公眾平臺(tái),需要80端口),可以直接在本地就可以進(jìn)行調(diào)試。 具體業(yè)務(wù)就不細(xì)說,直接看代碼就懂了。
package com.qx.client.common.pay.weichart.config;import java.util.PRoperties;import com.tom.util.properties.PropertiesUtil;import com.tom.util.system.RSystemConfig;public class WeiChartConfig { /** * 預(yù)支付請(qǐng)求地址 */ public static final String PrepayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 查詢訂單地址 */ public static final String OrderUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; /** * 關(guān)閉訂單地址 */ public static final String CloSEOrderUrl = "https://api.mch.weixin.qq.com/pay/closeorder"; /** * 申請(qǐng)退款地址 */ public static final String RefundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund"; /** * 查詢退款地址 */ public static final String RefundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery"; /** * 下載賬單地址 */ public static final String DownloadBillUrl = "https://api.mch.weixin.qq.com/pay/downloadbill"; /** * 商戶APPID */ public static final String AppId = "wxabcdefghjjsdfsd"; /** * 商戶賬戶 獲取支付能力后,從郵件中得到 */ public static final String MchId = "13000000000001"; /** * 商戶秘鑰 32位,在微信商戶平臺(tái)中設(shè)置 */ public static final String AppSercret = "qx12345645778679789"; /** * 服務(wù)器異步通知頁面路徑 */ public static String notify_url = getProperties().getProperty("notify_url"); /** * 頁面跳轉(zhuǎn)同步通知頁面路徑 */ public static String return_url = getProperties().getProperty("return_url"); /** * 退款通知地址 */ public static String refund_notify_url = getProperties().getProperty("refund_notify_url"); /** * 退款需要證書文件,證書文件的地址 */ public static String refund_file_path = getProperties().getProperty("refund_file_path"); /** * 商品名稱 */ public static String subject = getProperties().getProperty("subject"); /** * 商品描述 */ public static String body = getProperties().getProperty("body"); private static Properties properties; public static synchronized Properties getProperties(){ if(properties == null){ String path = System.getenv(RSystemConfig.KEY_WEB_HOME_CONF) + "/weichart.properties"; properties = PropertiesUtil.getInstance().getProperties(path); } return properties; }}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596972 http /https請(qǐng)求的工具類
其中有需要證書的,也有不需要證書的。 證書是在需要退款接口的時(shí)候需要使用,直接把證書放在服務(wù)器上,然后傳路徑
package com.qx.client.common.pay.weichart.util.httpClient;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.MalformedURLException;import java.net.URL;import java.net.URLConnection;import java.security.KeyStore;import java.util.Map;import javax.net.ssl.SSLContext;import org.apache.commons.httpclient.HttpClient;import org.apache.commons.httpclient.methods.PostMethod;import org.apache.commons.httpclient.methods.StringRequestEntity;import org.apache.commons.httpclient.params.HttpMethodParams;import org.apache.http.HttpEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.conn.ssl.SSLContexts;import org.apache.http.entity.StringEntity;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.util.EntityUtils;public class HttpClientUtil{ public static String post(String url, Map<String, String> headMap, Map<String, String> params){ try{ HttpClient httpclient = new HttpClient(); httpclient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8"); PostMethod httpPost = new PostMethod(url); if(null != headMap){ for(String key : headMap.keySet()){ httpPost.setRequestHeader(key, headMap.get(key)); } } if(null != params){ for(String pkey : params.keySet()){ httpPost.addParameter(pkey, params.get(pkey)); } } httpclient.executeMethod(httpPost); BufferedReader reader = new BufferedReader(new InputStreamReader(httpPost.getResponseBodyAsStream())); StringBuffer stringBuffer = new StringBuffer(); String str = ""; while((str = reader.readLine()) != null){ stringBuffer.append(str); } reader.close(); return stringBuffer.toString(); }catch(Exception e){ e.printStackTrace(); } return null; } public static String postHttplient(String url, String xmlInfo){ try{ HttpClient httpclient = new HttpClient(); httpclient.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8"); PostMethod httpPost = new PostMethod(url); httpPost.setRequestEntity(new StringRequestEntity(xmlInfo)); httpclient.executeMethod(httpPost); BufferedReader reader = new BufferedReader(new InputStreamReader(httpPost.getResponseBodyAsStream())); StringBuffer stringBuffer = new StringBuffer(); String str = ""; while((str = reader.readLine()) != null){ stringBuffer.append(str); } reader.close(); return stringBuffer.toString(); }catch(Exception e){ e.printStackTrace(); } return null; } /** * 需要加密執(zhí)行的 * @param url * @param xmlInfo * @return * @throws Exception */ public static String postHttplientNeedSSL(String url, String xmlInfo, String cretPath, String mrchId) throws Exception{ //選擇初始化密鑰文件格式 KeyStore keyStore = KeyStore.getInstance("PKCS12"); //得到密鑰文件流 FileInputStream instream = new FileInputStream(new File(cretPath)); try{ //用商戶的ID 來解讀文件 keyStore.load(instream, mrchId.toCharArray()); }finally{ instream.close(); } //用商戶的ID 來加載 SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mrchId.toCharArray()).build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); //用最新的httpclient 加載密鑰 CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); StringBuffer ret = new StringBuffer(); try{ HttpPost httpPost = new HttpPost(url); httpPost.setEntity(new StringEntity(xmlInfo)); CloseableHttpResponse response = httpclient.execute(httpPost); try{ HttpEntity entity = response.getEntity(); if(entity != null){ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); String text; while((text = bufferedReader.readLine()) != null){ ret.append(text); } } EntityUtils.consume(entity); }finally{ response.close(); } }finally{ httpclient.close(); } return ret.toString(); } public static String postHtpps(String urlStr, String xmlInfo){ try{ URL url = new URL(urlStr); URLConnection con = url.openConnection(); con.setDoOutput(true); con.setRequestProperty("Pragma:", "no-cache"); con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Content-Type", "text/xml;charset=utf-8"); OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream(), "utf-8"); out.write(xmlInfo); out.flush(); out.close(); BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream())); StringBuffer lines = new StringBuffer(); String line = ""; for(line = br.readLine(); line != null; line = br.readLine()){ lines.append(line); } return lines.toString(); }catch(MalformedURLException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } return null; }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711724 調(diào)用API接口
其中包含 XML生成,和解析XML,請(qǐng)求參數(shù)字典排序,拼接密鑰,md5加密
package com.qx.client.common.pay.weichart.util;import java.net.InetAddress;import java.net.UnknownHostException;import java.util.Arrays;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.Random;import java.util.Set;import org.mobj.com.encoding.RMd5;import org.mobj.com.xml.FXmlDocument;import org.mobj.com.xml.FXmlNode;import org.mobj.com.xml.FXmlNodes;import com.qx.client.common.pay.weichart.config.WeiChartConfig;import com.qx.client.common.pay.weichart.util.httpClient.HttpClientUtil;public class WeiChartUtil{ /** * 返回狀態(tài)碼 */ public static final String ReturnCode = "return_code"; /** * 返回信息 */ public static final String ReturnMsg = "return_msg"; /** * 業(yè)務(wù)結(jié)果 */ public static final String ResultCode = "result_code"; /** * 預(yù)支付交易會(huì)話標(biāo)識(shí) */ public static final String PrepayId = "prepay_id"; /** * 得到微信預(yù)付單的返回ID * @param orderId 商戶自己的訂單號(hào) * @param totalFee 總金額 (分) * @return */ public static Map<String, String> getPreyId(String orderId, String totalFee){ Map<String, String> reqMap = new HashMap<String, String>(); reqMap.put("appid", WeiChartConfig.AppId); reqMap.put("mch_id", WeiChartConfig.MchId); reqMap.put("nonce_str", getRandomString()); reqMap.put("body", WeiChartConfig.body); //reqMap.put("detail", WeiChartConfig.subject); //非必填 //reqMap.put("attach", "附加數(shù)據(jù)"); //非必填 reqMap.put("out_trade_no", orderId); //商戶系統(tǒng)內(nèi)部的訂單號(hào), reqMap.put("total_fee", totalFee); //訂單總金額,單位為分 reqMap.put("spbill_create_ip", getHostIp()); //用戶端實(shí)際ip // reqMap.put("time_start", "172.16.40.18"); //交易起始時(shí)間 非必填 // reqMap.put("time_expire", "172.16.40.18"); //交易結(jié)束時(shí)間 非必填 // reqMap.put("goods_tag", "172.16.40.18"); //商品標(biāo)記 非必填 reqMap.put("notify_url", WeiChartConfig.notify_url); //通知地址 reqMap.put("trade_type", "APP"); //交易類型 //reqMap.put("limit_pay", "no_credit"); //指定支付方式,no_credit 指定不能使用信用卡支 非必填 reqMap.put("sign", getSign(reqMap)); String reqStr = creatXml(reqMap); String retStr = HttpClientUtil.postHttplient(WeiChartConfig.PrepayUrl, reqStr); return getInfoByXml(retStr); } /** * 關(guān)閉訂單 * @param orderId 商戶自己的訂單號(hào) * @return */ public static Map<String, String> closeOrder(String orderId){ Map<String, String> reqMap = new HashMap<String, String>(); reqMap.put("appid", WeiChartConfig.AppId); reqMap.put("mch_id", WeiChartConfig.MchId); reqMap.put("nonce_str", getRandomString()); reqMap.put("out_trade_no", orderId); //商戶系統(tǒng)內(nèi)部的訂單號(hào), reqMap.put("sign", getSign(reqMap)); String reqStr = creatXml(reqMap); String retStr = HttpClientUtil.postHttplient(WeiChartConfig.CloseOrderUrl, reqStr); return getInfoByXml(retStr); } /** * 查詢訂單 * @param orderId 商戶自己的訂單號(hào) * @return */ public static String getOrder(String orderId){ Map<String, String> reqMap = new HashMap<String, String>(); reqMap.put("appid", WeiChartConfig.AppId); reqMap.put("mch_id", WeiChartConfig.MchId); reqMap.put("nonce_str", getRandomString()); reqMap.put("out_trade_no", orderId); //商戶系統(tǒng)內(nèi)部的訂單號(hào), reqMap.put("sign", getSign(reqMap)); String reqStr = creatXml(reqMap); String retStr = HttpClientUtil.postHttplient(WeiChartConfig.OrderUrl, reqStr); return retStr; } /** * 退款 * @param orderId 商戶訂單號(hào) * @param refundId 退款單號(hào) * @param totralFee 總金額(分) * @param refundFee 退款金額(分) * @param opUserId 操作員ID * @return */ public static Map<String, String> refundWei(String orderId,String refundId,String totralFee,String refundFee,String opUserId){ Map<String, String> reqMap = new HashMap<String, String>(); reqMap.put("appid", WeiChartConfig.AppId); reqMap.put("mch_id", WeiChartConfig.MchId); reqMap.put("nonce_str", getRandomString()); reqMap.put("out_trade_no", orderId); //商戶系統(tǒng)內(nèi)部的訂單號(hào), reqMap.put("out_refund_no", refundId); //商戶退款單號(hào) reqMap.put("total_fee", totralFee); //總金額 reqMap.put("refund_fee", refundFee); //退款金額 reqMap.put("op_user_id", opUserId); //操作員 reqMap.put("sign", getSign(reqMap)); String reqStr = creatXml(reqMap); String retStr = ""; try{ retStr = HttpClientUtil.postHttplientNeedSSL(WeiChartConfig.RefundUrl, reqStr, WeiChartConfig.refund_file_path, WeiChartConfig.MchId); }catch(Exception e){ e.printStackTrace(); return null; } return getInfoByXml(retStr); } /** * 退款查詢 * @param refundId 退款單號(hào) * @return */ public static Map<String, String> getRefundWeiInfo(String refundId){ Map<String, String> reqMap = new HashMap<String, String>(); reqMap.put("appid", WeiChartConfig.AppId); reqMap.put("mch_id", WeiChartConfig.MchId); reqMap.put("nonce_str", getRandomString()); reqMap.put("out_refund_no", refundId); //商戶退款單號(hào) reqMap.put("sign", getSign(reqMap)); String reqStr = creatXml(reqMap); String retStr = HttpClientUtil.postHttplient(WeiChartConfig.RefundQueryUrl, reqStr); return getInfoByXml(retStr); } /** * 傳入map 生成頭為XML的xml字符串,例:<xml><key>123</key></xml> * @param reqMap * @return */ public static String creatXml(Map<String, String> reqMap){ Set<String> set = reqMap.keySet(); FXmlNode rootXml = new FXmlNode(); rootXml.setName("xml"); for(String key : set){ rootXml.createNode(key, reqMap.get(key)); } return rootXml.xml().toString(); } /** * 得到加密值 * @param map * @return */ public static String getSign(Map<String, String> map){ String[] keys = map.keySet().toArray(new String[0]); Arrays.sort(keys); StringBuffer reqStr = new StringBuffer(); for(String key : keys){ String v = map.get(key); if(v != null && !v.equals("")){ reqStr.append(key).append("=").append(v).append("&"); } } reqStr.append("key").append("=").append(WeiChartConfig.AppSercret); //MD5加密 return RMd5.encode(reqStr.toString()).toUpperCase(); } /** * 得到10 位的時(shí)間戳 * 如果在JAVA上轉(zhuǎn)換為時(shí)間要在后面補(bǔ)上三個(gè)0 * @return */ public static String getTenTimes(){ String t = new Date().getTime()+""; t = t.substring(0, t.length()-3); return t; } /** * 得到隨機(jī)字符串 * @param length * @return */ public static String getRandomString(){ int length = 32; String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for(int i = 0; i < length; ++i){ int number = random.nextInt(62);//[0,62) sb.append(str.charAt(number)); } return sb.toString(); } /** * 得到本地機(jī)器的IP * @return */ private static String getHostIp(){ String ip = ""; try{ ip = InetAddress.getLocalHost().getHostAddress(); }catch(UnknownHostException e){ e.printStackTrace(); } return ip; } /** * 將XML轉(zhuǎn)換為Map 驗(yàn)證加密算法 然后返回 * @param xml * @return */ public static Map<String, String> getInfoByXml(String xml){ try{ FXmlDocument xdoc = new FXmlDocument(); FXmlNode nodeRoot = xdoc.formatStringToXml(xml); FXmlNodes allNodes = nodeRoot.allNodes(); Map<String, String> map = new HashMap<String, String>(); for(FXmlNode fXmlNode : allNodes){ map.put(fXmlNode.name(), fXmlNode.text()); } //對(duì)返回結(jié)果做校驗(yàn).去除sign 字段再去加密 String retSign = map.get("sign"); map.remove("sign"); String rightSing = getSign(map); if(rightSing.equals(retSign)){ return map; } }catch(Exception e){ return null; } return null; } /** * 將金額轉(zhuǎn)換成分 * @param fee 元格式的 * @return 分 */ public static String changeToFen(Double fee){ String priceStr = ""; if(fee != null){ int p = (int)(fee * 100); //價(jià)格變?yōu)榉? priceStr = Integer.toString(p); } return priceStr; }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282在微信支付的調(diào)試過程中,發(fā)現(xiàn)了一個(gè)困擾了很長(zhǎng)時(shí)間的BUG,或者說是一個(gè)問題。 就是微信請(qǐng)求預(yù)支付的時(shí)候如果傳了中文,就是body中給的是中文,就會(huì)報(bào)body不是UTF-8格式。如果強(qiáng)行對(duì)字段進(jìn)行編碼,又會(huì)報(bào) 加密錯(cuò)誤。
但是這不是最主要的讓人困擾的地方,最讓我煩惱的是,我用本地的JDK調(diào)試的時(shí)候,它是OK 的。 但是用TOMCAT 部署的時(shí)候 卻一直都不行
在網(wǎng)上有很多的說法,有的說,對(duì)body 進(jìn)行編碼轉(zhuǎn)換UTF-8,有的說對(duì)整個(gè)請(qǐng)求的XML,進(jìn)行編碼。
還有的說編碼格式用統(tǒng)一的。iso900…等等,巴拉巴拉的。。 反正我都不行。
最后在大神的幫助下,慢慢梳理,對(duì)發(fā)送請(qǐng)求的post方法上面想辦法。 然后就是下面的 這句關(guān)鍵
public static String postHtpps(String urlStr, String xmlInfo){ try{ URL url = new URL(urlStr); URLConnection con = url.openConnection(); con.setDoOutput(true); con.setRequestProperty("Pragma:", "no-cache"); con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Content-Type", "text/xml;charset=utf-8"); //在輸入流里面進(jìn)行轉(zhuǎn)碼,是最重要的 OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream(), "utf-8"); out.write(xmlInfo); out.flush(); out.close(); BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream())); StringBuffer lines = new StringBuffer(); String line = ""; for(line = br.readLine(); line != null; line = br.readLine()){ lines.append(line); } return lines.toString(); }catch(MalformedURLException e){ e.printStackTrace(); }catch(IOException e){ e.printStackTrace(); } return null; }}
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注