您好,登錄后才能下訂單哦!
本篇內容主要講解“Java后端如何對接微信支付”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java后端如何對接微信支付”吧!
首先我們要明確目標,我們點擊微信支付官網,我們主要聚焦于這三種支付方式,其中JSPAI與APP主要與uniapp開發微信小程序與APP對接,而NATIVE主要與網頁端掃碼支付對接
建議導入這個jar,里面一些提供map和xml互轉以及生成簽名的函數,使用非常方便。
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
ps:下面代碼涉及到的WXPayUtil下面的函數都是這個工具包里來的,所以不要再問這個函數代碼在哪
商戶號與支付結果通知回調地址
回調url配置途徑:
微信商戶平臺(pay.weixin.qq.com)-->產品中心-->開發配置-->支付配置
既然涉及到維信小程序和app,那么它們的app_id是必須的
統一下單、查單、退款的API地址,去官方文檔找即可,我這里直接寫好了
/** * 統一下單接口 */ public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 查詢訂單接口 */ public static final String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"; /** * 訂單退款接口 */ public static final String ORDER_REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
API密鑰
用于簽名算法
獲取途徑
微信商戶平臺(pay.weixin.qq.com)-->賬戶中心-->賬戶設置-->API安全-->設置API密鑰
這種支付的使用場景,我們這里是對接uniapp開發的微信小程序
我們仔細閱讀文檔并提取信息,其實訪問接口的很多參數都不是必填的,我們盡量去關注必填參數
歸納如下:
appid微信小程序app_idmch_id商戶號nonce_str隨機字符串,可以調用WXPayUtil下generateNonceStr方法生成sign
可以調用WXPayUtil下generateSignature方法生成,這個方法需要用到準備工作中的商戶API密鑰(APP_KEY)來加密。
appid | 微信小程序app_id |
mch_id | 商戶號 |
nonce_str | 隨機字符串,可以調用WXPayUtil下generateNonceStr方法生成 |
sign | 可以調用WXPayUtil下generateSignature方法生成,這個方法需要用到準備工作中的商戶API密鑰(APP_KEY)來加密。 |
body | 商品描述,建議軟件名字+產品操作,例如天天愛消除-游戲充值 |
out_trade_no | 我們自己生成的訂單號,保證同一商號下唯一即可 |
total_fee | 金額,注意單位是分 |
spbill_create_ip | 用戶客戶端ip |
notify_url | 支付結果通知回調地址 |
trade_type | 交易類型,我們這里填JSAPI |
openid | 因為是JSAPI方式,所以必傳,openid其實很好獲取,參考地址如何獲取openid |
但是下完單之后,怎么樣才能讓前端調用微信支付呢?這里需要查看uniapp官方文檔微信小程序支付
我們發現其實主要是拿到預支付id prepay_id以及簽名。
代碼實現:
public Map<String, String> unifiedOrderByJsp(Order order, String clientIp) throws Exception { long begin = System.currentTimeMillis(); //使用map封裝微信支付需要的固定參數 Map<String, String> m = new HashMap<>(); //設置支付參數 m.put("appid", OrderUtils.WX_APP_ID); m.put("mch_id", OrderUtils.MCH_ID); m.put("nonce_str", WXPayUtil.generateNonceStr()); //商品描述 例如:天天愛消除-游戲充值 m.put("body", "換芯易-" + order.getGoodsModel() + "購買"); //訂單號 m.put("out_trade_no", order.getOrderSn()); m.put("total_fee", order.getActualPrice().multiply(new BigDecimal("100")).longValue() + ""); m.put("spbill_create_ip", clientIp); m.put("notify_url", OrderUtils.NOTIFY_URL); m.put("trade_type", "JSAPI"); //trade_type=JSAPI時(即JSAPI支付),必須要openid UserThreeDao userThreeDao = new UserThreeDaoImpl(); UserThree userThree = userThreeDao.queryOpenid(order.getUserId()); if (userThree == null) { return null; } m.put("openid", userThree.getThreeOpenid()); //生成簽名 m.put("sign", WXPayUtil.generateSignature(m, OrderUtils.APP_KEY)); //發送請求,請求路徑:UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" HttpClient client = new HttpClient(OrderUtils.UNIFIED_ORDER_URL); //設置xml格式的參數,要把map轉為xml client.setXmlParam(WXPayUtil.mapToXml(m)); //設置支持https client.setHttps(true); //執行請求發送 client.post(); //xml轉為map接受結果 Map<String, String> response = WXPayUtil.xmlToMap(client.getContent()); long end = System.currentTimeMillis(); System.out.println("請求https://api.mch.weixin.qq.com/pay/unifiedorder耗時:" + (end - begin) + "ms"); System.out.println("請求結果:" + JSON.toJSONString(response)); if ("SUCCESS".equals(response.get("return_code")) && "SUCCESS".equals(response.get("result_code"))) { Map<String, String> param = new HashMap<>(); //返回結果格式參照https://uniapp.dcloud.io/api/plugins/payment?id=requestpayment //隨機字符串 //時間戳,但是單位為s,不是毫秒 param.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); param.put("nonceStr", WXPayUtil.generateNonceStr()); param.put("package", "prepay_id=" + response.get("prepay_id")); param.put("signType", "MD5"); param.put("paySign", WXPayUtil.generateSignature(param, OrderUtils.APP_KEY)); return param; } //為空代表下單失敗 return null; }
主要應用場景是PC端掃碼支付。
必填參數歸納如下:
appid | 微信小程序app_id |
mch_id | 商戶號 |
nonce_str | 隨機字符串,可以調用WXPayUtil下generateNonceStr方法生成 |
sign | 可以調用WXPayUtil下generateSignature方法生成,這個方法需要用到準備工作中的商戶API密鑰(APP_KEY)來加密。 |
body | 商品描述,建議軟件名字+產品操作,例如天天愛消除-游戲充值 |
out_trade_no | 我們自己生成的訂單號,保證同一商號下唯一即可 |
total_fee | 金額,注意單位是分 |
spbill_create_ip | 用戶客戶端ip |
notify_url | 支付結果通知回調地址 |
trade_type | 交易類型,我們這里填NATIVE |
product_id | trade_type=NATIVE時,此參數必傳。此參數為二維碼中包含的商品ID,我們可以自行定義。 |
代碼如下:
public Map<String, String> unifiedOrderByNative(Order order, String clientIp) throws Exception { long begin = System.currentTimeMillis(); //使用map封裝微信支付需要的固定參數 Map<String, String> m = new HashMap<>(); //1、設置支付參數 m.put("appid", OrderUtils.WX_APP_ID); m.put("mch_id", OrderUtils.MCH_ID); m.put("nonce_str", WXPayUtil.generateNonceStr()); //商品描述 例如:天天愛消除-游戲充值 m.put("body", "換芯易-" + order.getGoodsModel() + "購買"); //訂單號 m.put("out_trade_no", order.getOrderSn()); m.put("total_fee", order.getActualPrice().multiply(new BigDecimal("100")).longValue() + ""); m.put("spbill_create_ip", clientIp); m.put("notify_url", OrderUtils.NOTIFY_URL); m.put("trade_type", "NATIVE"); //trade_type=NATIVE時,此參數必傳 m.put("product_id", order.getId()); //生成簽名 m.put("sign", WXPayUtil.generateSignature(m, OrderUtils.APP_KEY)); //發送請求,請求路徑:UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" HttpClient client = new HttpClient(OrderUtils.UNIFIED_ORDER_URL); //設置xml格式的參數,要把map轉為xml client.setXmlParam(WXPayUtil.mapToXml(m)); //設置支持https client.setHttps(true); //執行請求發送 client.post(); //xml轉為map接受結果 Map<String, String> response = WXPayUtil.xmlToMap(client.getContent()); long end = System.currentTimeMillis(); System.out.println("請求https://api.mch.weixin.qq.com/pay/unifiedorder耗時:" + (end - begin) + "ms"); System.out.println("請求結果:" + JSON.toJSONString(response)); if ("SUCCESS".equals(response.get("return_code")) && "SUCCESS".equals(response.get("result_code"))) { Map<String, String> param = new HashMap<>(); //二維碼地址 param.put("code_url", response.get("code_url")); param.put("order_sn", order.getOrderSn()); param.put("order_id", order.getId()); param.put("total_fee", order.getActualPrice().multiply(new BigDecimal("100")).longValue() + ""); return param; } //為空代表下單失敗 return null; }
上面代碼中最重要的返回結果就是code_url。即生成的支付二維碼地址,然后用微信掃碼并付款即可。
應用場景是APP端調用微信支付,對接uniapp開發的app
歸納如下:
appid | APP的app_id |
mch_id | 商戶號 |
nonce_str | 隨機字符串,可以調用WXPayUtil下generateNonceStr方法生成 |
sign | 可以調用WXPayUtil下generateSignature方法生成,這個方法需要用到準備工作中的商戶API密鑰(APP_KEY)來加密。 |
body | 商品描述,建議軟件名字+產品操作,例如天天愛消除-游戲充值 |
out_trade_no | 我們自己生成的訂單號,保證同一商號下唯一即可 |
total_fee | 金額,注意單位是分 |
spbill_create_ip | 用戶客戶端ip |
notify_url | 支付結果通知回調地址 |
trade_type | 交易類型,我們這里填APP |
但是下完單之后,怎么樣才能讓前端調用微信支付呢?這里需要查看uniapp官方文檔微信小程序支付:
我們可以看到常規的比如appid、商戶號需要,充要的是預支付id prepayid和簽名。
代碼實現如下:
public Map<String, String> unifiedOrderByApp(Order order, String clientIp) throws Exception { long begin = System.currentTimeMillis(); //使用map封裝微信支付需要的固定參數 Map<String, String> m = new HashMap<>(); //1、設置支付參數 m.put("appid", OrderUtils.APP_APP_ID); m.put("mch_id", OrderUtils.MCH_ID); m.put("nonce_str", WXPayUtil.generateNonceStr()); //商品描述 例如:天天愛消除-游戲充值 m.put("body", "換芯易-" + order.getGoodsModel() + "購買"); //訂單號 m.put("out_trade_no", order.getOrderSn()); m.put("total_fee", order.getActualPrice().multiply(new BigDecimal("100")).longValue() + ""); m.put("spbill_create_ip", clientIp); m.put("notify_url", OrderUtils.NOTIFY_URL); m.put("trade_type", "APP"); //生成簽名 m.put("sign", WXPayUtil.generateSignature(m, OrderUtils.APP_KEY)); //發送請求,請求路徑:UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" HttpClient client = new HttpClient(OrderUtils.UNIFIED_ORDER_URL); //設置xml格式的參數,要把map轉為xml client.setXmlParam(WXPayUtil.mapToXml(m)); //設置支持https client.setHttps(true); //執行請求發送 client.post(); //xml轉為map接受結果 Map<String, String> response = WXPayUtil.xmlToMap(client.getContent()); long end = System.currentTimeMillis(); System.out.println("請求https://api.mch.weixin.qq.com/pay/unifiedorder耗時:" + (end - begin) + "ms"); System.out.println("請求結果:" + JSON.toJSONString(response)); if ("SUCCESS".equals(response.get("return_code")) && "SUCCESS".equals(response.get("result_code"))) { Map<String, String> param = new HashMap<>(); param.put("appid", OrderUtils.APP_APP_ID); //隨機字符串 param.put("noncestr", WXPayUtil.generateNonceStr()); //固定值 param.put("package", "Sign=WXPay"); param.put("partnerid", OrderUtils.MCH_ID); param.put("prepayid", response.get("prepay_id")); //時間戳,但是單位為s,不是毫秒 param.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); //param.put("package", response.get("sign")); param.put("sign", WXPayUtil.generateSignature(param, OrderUtils.APP_KEY)); return param; } //為空代表下單失敗 return null; }
按照uniapp官方要求的格式封裝返回結果給前端。
對于上面三種支付方式,他們的返回結果我都沒有細細分析,因為官方文檔寫得很詳細,我們非常需要關注的也就預支付id這個返回結果。所以貼一下官方文檔,有興趣可以仔細看看(重要的我會標注)
共有
其中APP和JSAPI得著重關注預支付id
native掃碼支付主要關注code_url
我們實際業務中,過于依賴微信支付的回調結果來判斷訂單狀態顯然是不太合適的,所以自己去查單這種操作十分常見。查單需要的必填參數歸納如下:
appid | app_id |
mch_id | 商戶號 |
nonce_str | 隨機字符串,可以調用WXPayUtil下generateNonceStr方法生成 |
sign | 可以調用WXPayUtil下generateSignature方法生成,這個方法需要用到準備工作中的商戶API密鑰(APP_KEY)來加密。 |
out_trade_no | 我們自己生成的訂單號,保證同一商號下唯一即可,這個訂單號需要和我們當時調用統一下單傳過去的單號一致 |
ps:這里的out_trade_no可以用transaction_id代替,但是一般我們查單的時候可能還沒有transaction_id,所以只能用我們自己程序業務訂單號out_trade_no去查,因為transaction_id需要調查單接口才能得到,也就是說如果我們以后"二次查單"可以用這個參數。
代碼實現入下:
public Map<String, String> queryOrderStatus(String orderSn) throws Exception { long beginTime = System.currentTimeMillis(); //1、封裝參數 Map<String, String> m = new HashMap<>(); m.put("appid", OrderUtils.APP_APP_ID); m.put("mch_id", OrderUtils.MCH_ID); m.put("out_trade_no", orderSn); m.put("nonce_str", WXPayUtil.generateNonceStr()); //生成簽名 m.put("sign", WXPayUtil.generateSignature(m, OrderUtils.APP_KEY)); //發送請求,請求路徑:UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/orderquery" HttpClient client = new HttpClient(OrderUtils.ORDER_QUERY_URL); client.setXmlParam(WXPayUtil.mapToXml(m)); client.setHttps(true); client.post(); //3、返回第三方的數據 Map<String, String> result = WXPayUtil.xmlToMap(client.getContent()); long endTime = System.currentTimeMillis(); System.out.println("請求https://api.mch.weixin.qq.com/pay/orderquery耗時:" + (endTime - beginTime) + "ms"); System.out.println("請求結果:" + JSON.toJSONString(result)); return result; }
返回結果參數部分如下,重要的標注起來:
一般情況下主要是利用trade_state來判斷用戶是否支付成功從而更新訂單或記錄交易成功物流等業務操作。
Map<String, String> map = orderDao.queryOrderStatus(orderSn); if (map == null) { return ApiResponse.createApiResponse(ApiResponse.HTTP_STATE_400_ERROR_10001, "微信支付出錯"); } //微信 if ("SUCCESS".equals(map.get("trade_state"))) { //更新訂單或記錄交易成功物流等業務操作 }
言簡意賅,就是方便(rnm,退錢)
重要參數
appid | app_id |
mch_id | 商戶號 |
nonce_str | 隨機字符串,可以調用WXPayUtil下generateNonceStr方法生成 |
sign | 可以調用WXPayUtil下generateSignature方法生成,這個方法需要用到準備工作中的商戶API密鑰(APP_KEY)來加密。 |
out_trade_no | 我們自己生成的訂單號,保證同一商號下唯一即可,這個訂單號需要和我們當時調用統一下單傳過去的單號一致 |
transaction_id | 和上面的out_trade_no二選一,官方推薦transaction_id |
out_refund_no | 其實和統一下單的時候的訂單號原理差不多,我們自己隨機生成一個,保證同一商戶系統下唯一即可 |
total_fee | 要退款的訂單的金額 |
refund_fee | 實際要退款的金額 |
代碼實現如下:
public Map<String, String> refundOrder(RefundTrace refundTrace, Order order) throws Exception { long begin = System.currentTimeMillis(); //使用map封裝微信支付需要的固定參數 Map<String, String> m = new HashMap<>(); //1、設置支付參數 m.put("appid", OrderUtils.WX_APP_ID); m.put("mch_id", OrderUtils.MCH_ID); m.put("nonce_str", WXPayUtil.generateNonceStr()); //微信支付訂單號 m.put("transaction_id", order.getPayId()); //商戶退款單號 m.put("out_refund_no", refundTrace.getRefundSn()); //訂單金額 m.put("total_fee", order.getActualPrice().multiply(new BigDecimal("100")).longValue() + ""); //退款金額 即實際退款金額 m.put("refund_fee", refundTrace.getRefundAmount().multiply(new BigDecimal("100")).longValue() + ""); //退款原因 m.put("refund_desc", refundTrace.getRefundReason()); m.put("notify_url", OrderUtils.NOTIFY_URL); //生成簽名 m.put("sign", WXPayUtil.generateSignature(m, OrderUtils.APP_KEY)); 發送請求,請求路徑:ORDER_REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund" String content = HttpRequestUtils.refundRequest(WXPayUtil.mapToXml(m)); //xml轉為map接受結果 Map<String, String> response = WXPayUtil.xmlToMap(content); long end = System.currentTimeMillis(); System.out.println("請求https://api.mch.weixin.qq.com/secapi/pay/refund耗時:" + (end - begin) + "ms"); System.out.println("請求結果:" + JSON.toJSONString(response)); if ("SUCCESS".equals(response.get("return_code")) && "SUCCESS".equals(response.get("result_code"))) { Map<String, String> param = new HashMap<>(); param.put("refund_id", response.get("refund_id")); param.put("refund_fee", response.get("refund_fee")); return param; } //為空代表退款失敗 return null; }
這里注意一下!!!!因為操作涉及到把商戶方的錢轉回到買方的操作,所以安全系數比較高,調用退款api官方要求證書
證書申請途徑如下:
微信商戶平臺(pay.weixin.qq.com)-->賬戶中心-->賬戶設置-->API安全-->申請API證書
申請到證書,需要放到項目下:
那么我們這里的http請求就不能用之前的了,需要配置該證書
上面代碼中使用的refundRequest函數細節如下:
public static String refundRequest(String order) throws Exception { try { KeyStore clientStore = KeyStore.getInstance("PKCS12"); // 讀取項目存放的PKCS12證書文件 FileInputStream instream = new FileInputStream("apiclient_cert.p12"); try { // 指定PKCS12的密碼(商戶ID) clientStore.load(instream, OrderUtils.MCH_ID.toCharArray()); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(clientStore, OrderUtils.MCH_ID.toCharArray()).build(); // 指定TLS版本 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); // 設置httpclient的SSLSocketFactory CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); try { HttpPost httpost = new HttpPost(OrderUtils.ORDER_REFUND_URL); // 設置響應頭信息 // httpost.addHeader("Connection", "keep-alive"); // httpost.addHeader("Accept", "*/*"); // httpost.addHeader("Content-Type", CONTENT_TYPE_FORM.toString()); // httpost.addHeader("X-Requested-With", "XMLHttpRequest"); // httpost.addHeader("Cache-Control", "max-age=0"); // httpost.addHeader("User-Agent", DEFAULT_USER_AGENT); httpost.setEntity(new StringEntity(order, "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 RuntimeException(e); } }
我所使用的HttpClient工具類源碼,我這里使用主要用于發送帶xml參數的post請求
public class HttpClient { private String url; private Map<String, String> param; private int statusCode; private String content; private String xmlParam; private boolean isHttps; public boolean isHttps() { return isHttps; } public void setHttps(boolean isHttps) { this.isHttps = isHttps; } public String getXmlParam() { return xmlParam; } public void setXmlParam(String xmlParam) { this.xmlParam = xmlParam; } public HttpClient(String url, Map<String, String> param) { this.url = url; this.param = param; } public HttpClient(String url) { this.url = url; } public void setParameter(Map<String, String> map) { param = map; } public void addParameter(String key, String value) { if (param == null) { param = new HashMap<String, String>(); } param.put(key, value); } public void post() throws ClientProtocolException, IOException { HttpPost http = new HttpPost(url); setEntity(http); execute(http); } public void put() throws ClientProtocolException, IOException { HttpPut http = new HttpPut(url); setEntity(http); execute(http); } public void get() throws ClientProtocolException, IOException { if (param != null) { StringBuilder url = new StringBuilder(this.url); boolean isFirst = true; for (String key : param.keySet()) { if (isFirst) { url.append("?"); isFirst = false; } else { url.append("&"); } url.append(key).append("=").append(param.get(key)); } System.out.println("請求的url:" + url.toString()); this.url = url.toString(); } HttpGet http = new HttpGet(url); execute(http); } /** * set http post,put param */ private void setEntity(HttpEntityEnclosingRequestBase http) { if (param != null) { List<NameValuePair> nvps = new LinkedList<NameValuePair>(); for (String key : param.keySet()) nvps.add(new BasicNameValuePair(key, param.get(key))); // 參數 http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 設置參數 } if (xmlParam != null) { http.setEntity(new StringEntity(xmlParam, Consts.UTF_8)); } } private void execute(HttpUriRequest http) throws ClientProtocolException, IOException { CloseableHttpClient httpClient = null; try { if (isHttps) { SSLContext sslContext = new SSLContextBuilder() .loadTrustMaterial(null, new TrustStrategy() { // 信任所有 @Override public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslContext); httpClient = HttpClients.custom().setSSLSocketFactory(sslsf) .build(); } else { httpClient = HttpClients.createDefault(); } CloseableHttpResponse response = httpClient.execute(http); try { if (response != null) { if (response.getStatusLine() != null) statusCode = response.getStatusLine().getStatusCode(); HttpEntity entity = response.getEntity(); // 響應內容 content = EntityUtils.toString(entity, Consts.UTF_8); } } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } finally { httpClient.close(); } } public int getStatusCode() { return statusCode; } public String getContent() throws ParseException, IOException { return content; } }
到此,相信大家對“Java后端如何對接微信支付”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。