1、微信公衆號支付APIJS:html
要完整的實現微信支付功能,須要先後端一塊兒實現,還須要微信商戶平臺的配置。這裏只是涉及服務端的代碼。前端
jar包:pom.xmljava
<!-- ↓↓↓↓↓↓↓↓ 支付相關 ↓↓↓↓↓↓↓↓ --> <!-- http --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency> <!-- xml處理 --> <dependency> <groupId>org.jdom</groupId> <artifactId>jdom</artifactId> <version>1.1.3</version> </dependency> <!-- fast-json包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.39</version> </dependency> <!-- ↑↑↑↑↑↑↑↑ 支付相關 ↑↑↑↑↑↑↑↑ -->
目錄結構:git
MD5Util.javaweb
package webapp.payutil; import java.security.MessageDigest; public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) resultSb.append(byteToHexString(aB)); 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 = 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 ignored) { } return resultString; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; }
PayCommonUtil.java算法
package webapp.payutil; import org.apache.http.Consts; 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.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import javax.net.ssl.SSLContext; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.math.BigDecimal; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.URL; import java.security.KeyStore; import java.util.*; public class PayCommonUtil { //微信參數配置 public static String API_KEY = "kshdf554sdf4s5f2sf121sdfsd1f5sdf"; public static String APPID = "wxd5609f7b5b4dd051"; public static String MCH_ID = "1230989902"; /** * 微信公衆號支付 * @param trade_no 訂單號 * @param totalAmount 支付金額 * @param description 文字內容說明 * @param attach 自定義參數 length=127 * @param openId 微信公衆號openId * @param wxnotify 回調地址 * @param request - * @return - */ public static SortedMap<String, Object> WxPublicPay(String trade_no, BigDecimal totalAmount, String description, String attach, String openId, String wxnotify, HttpServletRequest request) { Map<String, String> map = weixinPrePay(trade_no,totalAmount,description,attach,openId,wxnotify,request); SortedMap<String, Object> finalpackage = new TreeMap<>(); finalpackage.put("appId", PayCommonUtil.APPID); finalpackage.put("timeStamp", System.currentTimeMillis() / 1000); finalpackage.put("nonceStr", getRandomString(32)); finalpackage.put("package", "prepay_id=" + map.get("prepay_id")); finalpackage.put("signType", "MD5"); String sign = PayCommonUtil.createSign(finalpackage); finalpackage.put("paySign", sign); return finalpackage; } /** * - * @param trade_no 訂單號 * @param totalAmount 支付金額 * @param description 文字內容說明 * @param attach 自定義參數 length=127 * @param openid 微信公衆號openId * @param wxnotify 回調地址 * @param request - * @return - */ private static Map<String, String> weixinPrePay(String trade_no, BigDecimal totalAmount, String description, String attach, String openid, String wxnotify, HttpServletRequest request) { SortedMap<String, Object> parameterMap = new TreeMap<>(); parameterMap.put("appid", PayCommonUtil.APPID); parameterMap.put("mch_id", PayCommonUtil.MCH_ID); parameterMap.put("nonce_str", getRandomString(32)); parameterMap.put("body", description); parameterMap.put("attach", attach); parameterMap.put("out_trade_no", trade_no); parameterMap.put("fee_type", "CNY"); BigDecimal total = totalAmount.multiply(new BigDecimal(100)); java.text.DecimalFormat df = new java.text.DecimalFormat("0"); parameterMap.put("total_fee", df.format(total)); parameterMap.put("spbill_create_ip", request.getRemoteAddr()); parameterMap.put("notify_url", wxnotify); parameterMap.put("trade_type", "JSAPI"); //trade_type爲JSAPI是 openid爲必填項 parameterMap.put("openid", openid); String sign = PayCommonUtil.createSign(parameterMap); parameterMap.put("sign", sign); String requestXML = PayCommonUtil.getRequestXml(parameterMap); String result = PayCommonUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", requestXML); System.out.println(result); Map<String, String> map = null; try { map = PayCommonUtil.doXMLParse(result); } catch (JDOMException | IOException e) { e.printStackTrace(); } return map; } //隨機字符串生成 private static String getRandomString(int length) { //length表示生成字符串的長度 String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; Random random = new Random(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } //請求xml組裝 private static String getRequestXml(SortedMap<String, Object> parameters){ StringBuilder sb = new StringBuilder(); sb.append("<xml>"); Set es = parameters.entrySet(); for (Object e : es) { Map.Entry entry = (Map.Entry) e; String key = (String) entry.getKey(); String value = (String) entry.getValue(); if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) { sb.append("<").append(key).append(">").append("<![CDATA[").append(value).append("]]></").append(key).append(">"); } else { sb.append("<").append(key).append(">").append(value).append("</").append(key).append(">"); } } sb.append("</xml>"); return sb.toString(); } //生成簽名 private static String createSign(SortedMap<String, Object> parameters){ StringBuilder sb = new StringBuilder(); Set es = parameters.entrySet(); for (Object e : es) { Map.Entry entry = (Map.Entry) e; String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k).append("=").append(v).append("&"); } } sb.append("key=").append(API_KEY); System.out.println(sb.toString()); return MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); } /** * 驗證回調簽名 * @param map * @return */ public static boolean isTenpaySign(Map<String, String> map) { String charset = "utf-8"; String signFromAPIResponse = map.get("sign"); if (signFromAPIResponse == null || signFromAPIResponse.equals("")) { System.out.println("API返回的數據簽名數據不存在,有可能被第三方篡改!!!"); return false; } System.out.println("服務器回包裏面的簽名是:" + signFromAPIResponse); //過濾空 設置 TreeMap SortedMap<String,String> packageParams = new TreeMap<>(); for (String parameter : map.keySet()) { String parameterValue = map.get(parameter); String v = ""; if (null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } StringBuilder sb = new StringBuilder(); Set es = packageParams.entrySet(); for (Object e : es) { Map.Entry entry = (Map.Entry) e; String k = (String) entry.getKey(); String v = (String) entry.getValue(); if (!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k).append("=").append(v).append("&"); } } sb.append("key=").append(API_KEY); //將API返回的數據根據用簽名算法進行計算新的簽名,用來跟API返回的簽名進行比較 //算出簽名 String tobesign = sb.toString(); String resultSign = MD5Util.MD5Encode(tobesign, "utf-8").toUpperCase(); String tenpaySign = packageParams.get("sign").toUpperCase(); return tenpaySign.equals(resultSign); } //請求方法 private static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { try { URL url = new URL(requestUrl); HttpURLConnection 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 inputStream = conn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuilder buffer = new StringBuilder(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 釋放資源 bufferedReader.close(); inputStreamReader.close(); inputStream.close(); conn.disconnect(); return buffer.toString(); } catch (ConnectException ce) { System.out.println("鏈接超時:{}"+ ce); } catch (Exception e) { System.out.println("https請求異常:{}"+ e); } return null; } //退款的請求方法 public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12"); StringBuilder res = new StringBuilder(""); try (FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12"))) { keyStore.load(instream, "".toCharArray()); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, "1313329201".toCharArray()) .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); try (CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build()) { HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund"); 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) "); StringEntity entity2 = new StringEntity(outputStr, Consts.UTF_8); httpost.setEntity(entity2); System.out.println("executing request" + httpost.getRequestLine()); try (CloseableHttpResponse response = httpclient.execute(httpost)) { HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); String text = ""; res.append(text); while ((text = bufferedReader.readLine()) != null) { res.append(text); System.out.println(text); } } EntityUtils.consume(entity); } } return res.toString(); } //xml解析 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(); for (Object aList : list) { Element e = (Element) aList; String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //關閉流 in.close(); return m; } private static String getChildrenText(List children) { StringBuilder sb = new StringBuilder(); if(!children.isEmpty()) { for (Object aChildren : children) { Element e = (Element) aChildren; String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<").append(name).append(">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</").append(name).append(">"); } } return sb.toString(); } }
WeiXinPayController.javaspring
package webapp.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.jdom.JDOMException; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import webapp.payutil.PayCommonUtil; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.util.Map; import java.util.SortedMap; import java.util.UUID; @Controller @RequestMapping(value = "/api") public class WeiXinPayController { private static String wxnotify = "/api/json/money/wxpay/succ"; /** * @param totalAmount 支付金額 * @param description 描述 * @param openId 微信公衆號openId (能夠前端傳code,而後後臺再經過微信對應接口換取openId) * @param request - * @return - */ @RequestMapping(value = "/weixin/weixinPay/{totalAmount}/{description}/{openId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public SortedMap<String, Object> ToPay(@PathVariable BigDecimal totalAmount, @PathVariable String description, @PathVariable String openId, HttpServletRequest request) { String sym = request.getRequestURL().toString().split("/api/")[0]; // 訂單號 String tradeNo = UUID.randomUUID().toString().replaceAll("-", "").toLowerCase().substring(0,32); // 回調地址 String notifyUrl = sym + wxnotify; // 自定義參數 Long userId = 100L; //對應用戶id本身修改 JSONObject jsAtt = new JSONObject(); jsAtt.put("uid", userId); String attach = jsAtt.toJSONString(); // 返回預支付參數 return PayCommonUtil.WxPublicPay(tradeNo, totalAmount, description, attach, openId, notifyUrl, request); } /** * 支付回調地址 * @param request * @return * @throws IOException */ @RequestMapping(value = "/json/money/wxpay/succ", produces = MediaType.APPLICATION_JSON_VALUE) public String wxpaySucc(HttpServletRequest request) throws IOException { System.out.println("微信支付回調"); InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } String resultxml = new String(outSteam.toByteArray(), "utf-8"); Map<String, String> params = null; try { params = PayCommonUtil.doXMLParse(resultxml); } catch (JDOMException e) { e.printStackTrace(); } outSteam.close(); inStream.close(); if (!PayCommonUtil.isTenpaySign(params)) { // 支付失敗 return "fail"; } else { System.out.println("===============付款成功=============="); // ------------------------------ // 處理業務開始 // ------------------------------ // 此到處理訂單狀態,結合本身的訂單數據完成訂單狀態的更新 // ------------------------------ String total_fee = params.get("total_fee"); double v = Double.valueOf(total_fee) / 100; // 取出用戶id String attach = params.get("attach"); JSONObject jsonObject = JSON.parseObject(attach); Long userId = Long.parseLong(jsonObject.get("uid").toString()); //更新 //updateUserPay(userId, String.valueOf(v)); // 處理業務完畢 // ------------------------------ return "success"; } } }
2、app支付:apache
和公衆號支付同樣,可是支付接口不須要openId,json
PayCommonUtil.java:後端
第83行改成parameterMap.put("trade_type", "APP");
去掉第85行。
參考:http://blog.csdn.net/gbguanbo/article/details/50915333
注意項目中若是有權限配置,微信支付相關接口權限要放行。
一些資料:
地