Java接入微信支付超級詳細保姆級教程
本文介紹了“二維碼付款”的代碼。其他微信支付方式的代碼都在源碼中。
一、準(zhǔn)備開發(fā)所需的賬號以及配置信息
首先想要接入微信支付我們需要兩個玩意:
一是公眾號/小程序/企業(yè)微信(開發(fā)用的)這個是為了獲取 APPID
一是微信支付商戶(收錢用的) 獲取 api_key mch_id
1、前往:https://mp.weixin.qq.com/ (微信公眾平臺)注冊一個應(yīng)用,類型只能是:公眾號/小程序/企業(yè)微信,注冊完成需要完成”微信認證“(微信需要收取300元)。
2、前往:https://pay.weixin.qq.com(微信支付商戶平臺)注冊一個商戶,支付成功后的錢就會在這個賬號里面。
? 1、APPID:應(yīng)用id也就是 公眾號/小程序的ID
? 2、Api_key: 對應(yīng)的APIv2密鑰
? 3、mch_Id:商戶ID (收錢的商家ID)對應(yīng)的是 【微信支付商戶號】
4.將申請的下來的APPID綁定到商戶號下,添加成功后再次到工作號里面
【廣告與服務(wù)—>微信支付】這個時候會看到關(guān)聯(lián)申請,同意就可以了。到這一步前置工作就完成了
二、準(zhǔn)備環(huán)境
項目采用SpringBoot
微信支付有兩種版本:V3和V2,本文的接入版本為V2
1、導(dǎo)入jar包
1.1微信支付jar包
<dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
1.2導(dǎo)入hutool工具類jar包
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.12</version> </dependency>
2、設(shè)置開發(fā)參數(shù)
如果自己就是商戶 那么可以將參數(shù)設(shè)置到配置文件application.yml中,如果是多商戶則建立商戶收款配置表 將信息維護到數(shù)據(jù)庫中
在application.yml,設(shè)置好開發(fā)參數(shù)
pay: appid: wx123456789a439 #微信公眾號appid api_key: gwxkjvfewvfabvcrxgrawvgs924ceaxj #公眾號設(shè)置的api密鑰 mch_id: 1603596731 #微信商戶平臺 商戶id
本文是多商戶
數(shù)據(jù)庫參考
微信支付工具類
package com.manage.common.utils; import javax.net.ssl.HttpsURLConnection; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.net.URL; /** * 微信支付工具類 * */ public class WxChatPayCommonUtil { /** * 發(fā)送 http 請求 * @param requestUrl 請求路徑 * @param requestMethod 請求方式(GET/POST/PUT/DELETE/...) * @param outputStr 請求參數(shù)體 * @return 結(jié)果信息 */ public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) { try { URL url = new URL(requestUrl); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setDoInput(true); conn.setUseCaches(false); // 設(shè)置請求方式(GET/POST) conn.setRequestMethod(requestMethod); conn.setRequestProperty("content-type", "application/x-www-form-urlencoded"); // 當(dāng)outputStr不為null時向輸出流寫數(shù)據(jù) if (null != outputStr) { OutputStream outputStream = conn.getOutputStream(); // 注意編碼格式 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 從輸入流讀取返回內(nèi)容 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 (Exception e) { e.printStackTrace(); } return null; } /** * 獲取ip * @param request 請求 * @return ip 地址 */ public static String getIp(HttpServletRequest request) { if (request == null) { return ""; } String ip = request.getHeader("X-Requested-For"); if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } /** * 從流中讀取微信返回的xml數(shù)據(jù) * @param httpServletRequest * @return * @throws IOException */ public static String readXmlFromStream(HttpServletRequest httpServletRequest) throws IOException { InputStream inputStream = httpServletRequest.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); final StringBuffer sb = new StringBuffer(); String line = null; try { while ((line = bufferedReader.readLine()) != null) { sb.append(line); } } finally { bufferedReader.close(); inputStream.close(); } return sb.toString(); } /** * 設(shè)置返回給微信服務(wù)器的xml信息 * @param returnCode * @param returnMsg * @return */ public static String setReturnXml(String returnCode, String returnMsg) { return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>"; } }
- 微信支付接口地址
package com.manage.common.utils; /** * 微信支付接口地址 * */ public class WeChatPayUrl { //統(tǒng)一下單預(yù)下單接口url public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //訂單狀態(tài)查詢接口URL public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery"; //訂單申請退款 public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund"; //付款碼 支付 public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay"; //微信網(wǎng)頁授權(quán) 獲取“code”請求地址 public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize"; }
- 錢 工具類
package com.manage.common.utils; import com.manage.common.object.YouNumberUtil; import java.math.BigDecimal; /** * 錢 工具類 * * Created by YouHan on 2019-06-28 09:12:00 * Copyright ? 2019 YouHan All rights reserved. */ public class MoneyUtils { public static final String YUAN = "元"; public static final String FEN = "分"; /** * 元轉(zhuǎn)分 * * @param s * @return java.lang.Integer * @date 2020/9/10 9:03 * @author YouHan */ public static Integer yuanToFen(String s) { if (!YouNumberUtil.isNumber(s)) { return 0; } return new BigDecimal(s).multiply(new BigDecimal(100)).intValue(); } /** * 處理分 * * @param s * @return java.lang.Integer * @author YouHan * @date 2022/7/23 */ public static Integer handleFen(String s) { if (!YouNumberUtil.isNumber(s)) { return 0; } return new BigDecimal(s).intValue(); } /** * 分轉(zhuǎn)元 * 可以為正負小數(shù)(這里保留2位小數(shù)) * * @param s * @return java.lang.String * @date 2020/9/10 9:01 * @author YouHan */ public static String fenToYuan(String s) { if (!YouNumberUtil.isNumber(s) || "0".equals(s) || "-0".equals(s)) { return "0.00"; } return new BigDecimal(s) .divide(new BigDecimal(100)) .setScale(2, BigDecimal.ROUND_DOWN) .toString(); } /** * 處理元 * 可以為正負小數(shù)(這里保留2位小數(shù)) * * @param s * @return java.lang.String * @author YouHan * @date 2022/7/23 */ public static String handleYuan(String s) { if (!YouNumberUtil.isNumber(s) || "0".equals(s) || "-0".equals(s)) { return "0.00"; } return new BigDecimal(s) .setScale(2, BigDecimal.ROUND_DOWN) .toString(); } public static void main(String[] args) { System.out.println(yuanToFen("10.00")); } }
- 數(shù)字 client
package com.manage.common.object; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * String client * * Created by YouHan on 2019-09-11 08:57:56 * Copyright ? 2019 YouHan All rights reserved. */ public class YouStringUtil { /** * 下劃線 */ public static final Pattern LINE = Pattern.compile("_(\\w)"); /** * 駝峰 */ public static final Pattern HUMP = Pattern.compile("[A-Z]"); /** * 添加內(nèi)容 * * @param content * @param length * @return java.lang.String * @author YouHan * @date 2021/6/17 9:59 */ public static String appendContent(String content, int length) { if (length <= 0) { return ""; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i ++) { sb.append(content); } return sb.toString(); } /** * 添加前綴內(nèi)容 * * @param s * @param content * @param length * @return java.lang.String * @date 2019-08-12 09:53 * @author YouHan */ public static String appendPrefixContent(String s, String content, int length) { if (length <= 0) { return null; } StringBuilder sb = new StringBuilder(s); for (int i = 0; i < length; i ++) { sb.append(content, 0, content.length()); } return sb.toString(); } /** * 添加后綴內(nèi)容 * * @param s * @param content * @param length * @return java.lang.String * @date 2019-08-12 09:56 * @author YouHan */ public static String appendSuffixContent(String s, String content, int length) { if (length <= 0) { return null; } StringBuilder sb = new StringBuilder(s); for (int i = 0; i < length; i ++) { sb.append(content); } return sb.toString(); } /** * Set 轉(zhuǎn) String * * @param stringSet * @return java.lang.String * @author YouHan * @date 2021/6/3 9:26 */ public static String setToString(Set<String> stringSet) { return setToString(stringSet, null); } /** * Set 轉(zhuǎn) String * * @param stringSet * @param regex * @return java.lang.String * @date 2021/6/3 9:21 * @author YouHan */ public static String setToString(Set<String> stringSet, String regex) { // 參數(shù)校驗 if (CollectionUtils.isEmpty(stringSet)) { return null; } if (StringUtils.isBlank(regex)) { regex = ","; } // List to String StringBuilder sb = new StringBuilder(stringSet.size()); for (String s : stringSet) { sb.append(s).append(regex); } // 返回結(jié)果 return sb.substring(0, sb.length() - 1); } /** * 字符串列表轉(zhuǎn)字符串 * * @author YouHan * @generatedDate: 2018/10/9 17:25 * @param stringList 要轉(zhuǎn)換的字符串列表 * @return */ public static String listToString(List<String> stringList) { return listToString(stringList, null); } /** * 字符串列表轉(zhuǎn)字符串 * * @author YouHan * @generatedDate: 2018/10/9 17:25 * @param stringList 要轉(zhuǎn)換的字符串列表 * @return */ public static String listToString(List<String> stringList, String regex) { // 參數(shù)校驗 if (CollectionUtils.isEmpty(stringList)) { return null; } if (StringUtils.isBlank(regex)) { regex = ","; } // List to String StringBuilder sb = new StringBuilder(stringList.size()); for (String s : stringList) { sb.append(s).append(regex); } // 返回結(jié)果 return sb.substring(0, sb.length() - 1); } /** * 字符串轉(zhuǎn)列表 * * @param s * @return java.client.List<java.lang.String> * @date 2019-09-11 09:11 * @author YouHan */ public static List<String> stringToList(String s) { /** * 參數(shù)校驗 */ if (StringUtils.isBlank(s)) { return null; } return stringToList(s, null); } /** * 字符串轉(zhuǎn)列表 * * @param s * @param regex 分割規(guī)則,默認為逗號 * @return java.client.List<java.lang.String> * @date 2019-09-11 09:11 * @author YouHan */ public static List<String> stringToList(String s, String regex) { /** * 參數(shù)校驗 */ if (StringUtils.isBlank(s)) { return null; } /** * 默認逗號隔開 */ if (StringUtils.isBlank(regex)) { regex = ","; } /** * 去除首尾空格 */ String blankString = " "; while (s.startsWith(blankString)) { s = s.substring(1); } while (s.endsWith(blankString)) { s = s.substring(0, s.length() -1); } /** * 返回結(jié)果列表 */ List<String> resultList = new ArrayList<>(); /** * 只有單個元素 */ if (!s.contains(regex)) { resultList.add(s); return resultList; } String[] strings = s.split(regex); for (String e : strings) { resultList.add(e); } return resultList; } /** * 過濾逗號 * @param s * @return */ public static String filterCommaString(String s) { // 數(shù)據(jù)為空校驗 if (StringUtils.isEmpty(s)) { return null; } // 去除 并列逗號 s = s.replace(",,", ","); // 去除 首逗號 if (s.startsWith(",")) { s = s.substring(1, s.length() - 1); } // 去除 尾逗號 if (s.endsWith(",")) { s = s.substring(0, s.length() - 1); } return s; } /** * 是否包含中文(包括中文標(biāo)點符號和空格) * * @param s * @return boolean * @date 2020/9/9 13:30 * @author YouHan */ public static Boolean isContainChinese(String s) { /** * 參數(shù)校驗 */ if (StringUtils.isBlank(s)) { return false; } if (s.contains(" ")) { return true; } /** * 中文正則表達式 */ String regex = "[\u4e00-\u9fa5]"; if (s.matches(regex)) { return Boolean.TRUE; } /** * 中文標(biāo)點符號處理 */ char[] chars = s.toCharArray(); for (char c : chars) { if (isChinesePunctuation(c)) { return true; } } return false; } /** * 過濾中文(包括標(biāo)點符號和空格) * * @param s * @return java.lang.String * @date 2020/9/9 14:08 * @author YouHan */ public static String filterChinese(String s) { /** * 參數(shù)校驗 */ if (StringUtils.isBlank(s)) { return ""; } s = s.replace(" ", ""); if (!isContainChinese(s)) { return s; } /** * 過濾中文字符 */ char[] chars = s.toCharArray(); StringBuilder sb = new StringBuilder(chars.length); for (char c : chars) { if (isContainChinese(String.valueOf(c))) { continue; } sb.append(c); } return sb.toString(); } /** * 判斷是否為中文標(biāo)點符號 * * @param c * @return java.lang.Boolean * @date 2020/9/9 13:43 * @author YouHan */ public static boolean isChinesePunctuation(char c) { Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); if (ub == Character.UnicodeBlock.GENERAL_PUNCTUATION || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS || ub == Character.UnicodeBlock.VERTICAL_FORMS) { return true; } return false; } /** * 獲取 UUID * * @param * @return java.lang.String * @date 2021/4/9 14:08 * @author YouHan */ public static String getUUID() { return UUID.randomUUID().toString().replace("-", ""); } /** * 安全比較(可防止時序攻擊 timing attack) */ public static boolean safeEqual(String a, String b) { if (StringUtils.isBlank(a) || StringUtils.isBlank(b)) { return false; } if (a.length() != b.length()) { return false; } int equal = 0; for (int i = 0; i < a.length(); i++) { equal |= a.charAt(i) ^ b.charAt(i); } return equal == 0; } /** * 駝峰轉(zhuǎn)下劃線 * * @param s * @return java.lang.String * @date 2021/5/6 22:20 * @author YouHan */ public static String humpToLine(String s) { Matcher matcher = HUMP.matcher(s); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase()); } if (sb.toString().startsWith("_")) { sb.deleteCharAt(0); } matcher.appendTail(sb); return sb.toString(); } /** * 下劃線轉(zhuǎn)駝峰 * * @param s * @return java.lang.String * @date 2021/5/6 22:21 * @author YouHan */ public static String lineToHump(String s) { s = s.toLowerCase(); Matcher matcher = LINE.matcher(s); StringBuffer sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, matcher.group(1).toUpperCase()); } matcher.appendTail(sb); return sb.toString(); } /** * 生成加密的內(nèi)容 * * @param s * @return java.lang.String * @author YouHan * @date 2021/6/17 10:10 */ public static String hide(String s) { /** * 1 * 1* * 1** * 1*** * 1***5 * 12***6 * 12***67 * 123***78 * 123***789 * 123****890 * 123*****8901 */ if (s.isEmpty() || s.length() == 1) { return s; } if (s.length() == 2) { return s.substring(0, 1) + "*"; } if (s.length() == 3 || s.length() == 4) { return s.substring(0, 1) + appendContent("*", s.length() - 1); } if (s.length() == 5) { return s.substring(0, 1) + "***" + s.substring(4); } if (s.length() == 6 || s.length() == 7) { return s.substring(0, 2) + appendContent("*", 3) + s.substring(5); } if (s.length() == 8) { return s.substring(0, 3) + "***" + s.substring(6); } return s.substring(0, 3) + appendContent("*", s.length() - 6) + s.substring(s.length() - 3); } }
- String client
package com.manage.common.object; import org.apache.commons.lang3.StringUtils; import java.util.concurrent.ThreadLocalRandom; /** * 數(shù)字 client * * Created by YouHan on 2020-09-09 13:28:40 * Copyright ? 2021 YouHan All rights reserved. */ public class YouNumberUtil { /** * 整數(shù)正則表達式 */ public static final String INTEGER_REGEX = "^[-\\+]?[\\d]*$"; /** * 數(shù)字正則表達式 */ public static final String NUMBER_REGEX = "^-?\\d+(\\.\\d+)?$"; /** * 是否是整數(shù) * * @param s * @return java.lang.Boolean * @date 2020/9/12 8:38 * @author YouHan */ public static boolean isInteger(String s) { if (StringUtils.isBlank(s)) { return false; } return s.matches(INTEGER_REGEX); } /** * 判斷給定字符串是否為十六進制數(shù) * * @param value 值 * @return 是否為十六進制 */ public static boolean isHex(String value) { final int index = (value.startsWith("-") ? 1 : 0); if (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index)) { try { Long.decode(value); } catch (NumberFormatException e) { return false; } return true; } return false; } /** * 是否是數(shù)字(包括小數(shù)) * * @param s * @return java.lang.Boolean * @date 2020/9/9 14:01 * @author YouHan */ public static boolean isNumber(String s) { if (StringUtils.isBlank(s)) { return false; } return s.matches(NUMBER_REGEX); } /** * 十進制轉(zhuǎn)十六進制 * * @param n 十進制數(shù) * @return java.lang.String * @date 2019/4/8 09:22 * @author YouHan */ public static String intToHex(Integer n) { if (null == n) { return null; } return String.format("%X", n); } /** * 十進制轉(zhuǎn)十六進制 * * @param n 十進制數(shù) * @return java.lang.String * @date 2019/4/8 09:22 * @author YouHan */ public static String longToHex(Long n) { if (null == n) { return null; } return String.format("%X", n); } /** * 十進制轉(zhuǎn)十六進制 * * @param n * @param length * @return java.lang.String * @date 2019-08-12 09:56 * @author YouHan */ public static String intToHexPrefix(Integer n, Integer length) { if (null == n) { return null; } if (null == length || length <= 0) { return null; } String result = String.format("%X", n); if (result.length() < length) { result = YouStringUtil.appendPrefixContent(result, "0", length - result.length()); } return result; } /** * 十進制轉(zhuǎn)十六進制 * * @param n * @param length * @return java.lang.String * @date 2019-08-12 09:56 * @author YouHan */ public static String longToHexPrefix(Long n, Integer length) { if (null == n) { return null; } if (null == length || length <= 0) { return null; } String result = String.format("%X", n); if (result.length() < length) { result = YouStringUtil.appendPrefixContent(result, "0", length - result.length()); } return result; } /** * 十進制轉(zhuǎn)十六進制 * * @param n 十進制數(shù) * @return java.lang.String * @date 2019/4/8 09:22 * @author YouHan */ public static String intToHexSuffix(Integer n, Integer length) { if (null == n) { return null; } if (null == length || length <= 0) { return null; } String result = String.format("%X", n); if (result.length() < length) { result = YouStringUtil.appendSuffixContent(result, "0", length - result.length()); } return result; } /** * 十進制轉(zhuǎn)十六進制 * * @param n 十進制數(shù) * @return java.lang.String * @date 2019/4/8 09:22 * @author YouHan */ public static String longToHexSuffix(Long n, Integer length) { if (null == n) { return null; } if (null == length || length <= 0) { return null; } String result = String.format("%X", n); if (result.length() < length) { result = YouStringUtil.appendSuffixContent(result, "0", length - result.length()); } return result; } /** * 十六進制轉(zhuǎn)十進制 * * @param hex * @return java.lang.Integer * @date 2019/4/8 09:49 * @author YouHan */ public static Integer hexToInt(String hex) { Long n = hexToLong(hex); if (null == n) { return null; } // 超出整數(shù)最大值,不予處理 if (Integer.MAX_VALUE < n) { return null; } return Integer.valueOf(String.valueOf(n)); } /** * 十六進制轉(zhuǎn)十進制 * * @param hex * @return java.lang.Integer * @date 2019/4/8 09:49 * @author YouHan */ public static Long hexToLong(String hex) { if (StringUtils.isBlank(hex)) { return null; } // 去除前綴為 0 的 十六進制 if (hex.length() > 1 && hex.startsWith("0")) { hex = hex.substring(1); } return Long.valueOf(hex, 16); } /** * 字符串轉(zhuǎn)十六進制 * * @param s * @return */ public static String stringToHex(String s) { char[] chars = "0123456789ABCDEF".toCharArray(); StringBuilder sb = new StringBuilder(""); byte[] bs = s.getBytes(); int bit; for (int i = 0; i < bs.length; i++) { bit = (bs[i] & 0x0f0) >> 4; sb.append(chars[bit]); bit = bs[i] & 0x0f; sb.append(chars[bit]); } return sb.toString().trim(); } /** * 十六進制轉(zhuǎn)字符串 * * @param s * @return */ public static String hexToString(String s) { String str = "0123456789ABCDEF"; char[] hexs = s.toCharArray(); byte[] bytes = new byte[s.length() / 2]; int n; for (int i = 0; i < bytes.length; i++) { n = str.indexOf(hexs[2 * i]) * 16; n += str.indexOf(hexs[2 * i + 1]); bytes[i] = (byte) (n & 0xff); } return new String(bytes); } /** * 去除末尾多余的 0 * * @param s * @return java.lang.String * @author YouHan * @date 2021/7/2 15:39 */ public static String stripTrailingZeros(String s) { if (StringUtils.isBlank(s)) { return "0"; } if (!isNumber(s)) { return "0"; } if (!s.contains(".")) { return s; } while (s.endsWith("0")) { s = s.substring(0, s.length() - 1); } if (s.endsWith(".")) { s = s.substring(0, s.length() - 1); } return s; } /** * int 轉(zhuǎn) String * 1024以內(nèi)高效,超出后,正常轉(zhuǎn)換 */ static int cacheSize = 1024; static String[] caches = new String[cacheSize]; static { for (int i = 0; i < cacheSize; i++) { caches[i] = String.valueOf(i); } } public static String int2String(int data) { if (data < cacheSize) { return caches[data]; } return String.valueOf(data); } /** * 獲取幾位的 int 隨機數(shù) * * @param length * @return int * @author YouHan * @date 2021/12/19 */ public static int getRandomInt(int length) { return (int) ((Math.random() * 9 + 1) * 10 * length); } /** * 獲取幾位的 long 隨機數(shù) * * @param length * @return long * @author YouHan * @date 2021/12/19 */ public static long getRandomLong(long length) { return (long) ((Math.random() * 9 + 1) * 10 * length); } /** * 獲取隨機數(shù) * * @param * @return java.client.concurrent.ThreadLocalRandom * @author YouHan * @date 2021/6/3 10:29 */ public static ThreadLocalRandom getRandom() { return ThreadLocalRandom.current(); } /** * 獲取緩存穿透時間(單位秒),最長不超過 5 分鐘 * * @param * @return java.lang.Long * @date 2021/4/26 9:50 * @author YouHan */ public static Long getCachePenetrationTime() { return Long.valueOf(int2String(getRandom().nextInt(300))); } /** * 獲取數(shù)據(jù)庫緩存時間(單位秒),最長不超過 1 小時 * * @param * @return java.lang.Long * @date 2021/4/26 9:50 * @author YouHan */ public static Long getDBCacheTime() { return Long.valueOf(int2String(getRandom().nextInt(3600))); } /** * 十六進制高低位轉(zhuǎn)換 * * @param hexString * @return java.lang.String * @author YouHan * @date 2021/12/11 */ public static String hexHighLowPositionConvert(String hexString) { if (StringUtils.isBlank(hexString) || hexString.length() % 2 != 0) { return null; } StringBuilder result = new StringBuilder(); for (int i = hexString.length() - 2; i >= 0; i = i - 2) { result.append(hexString.substring(i, i + 2)); } return result.toString(); } public static void main(String[] args) { System.out.println(Long.MAX_VALUE); } }
- 上業(yè)務(wù)代碼
Controller
/** * 調(diào)用統(tǒng)一下單接口,并組裝生成支付所需參數(shù)對象. * * @param orderInfoVO 統(tǒng)一下單請求參數(shù) */ @Operation(summary = "調(diào)用統(tǒng)一下單接口") @PostMapping("/unifiedOrder") public AjaxResult unifiedOrder(HttpServletRequest request, @RequestBody OrderInfoVO orderInfoVO) { return orderInfoService.unifiedOrder(request, orderInfoVO); }
Service
AjaxResult unifiedOrder(HttpServletRequest request, OrderInfoVO orderInfoVO);
ServiceImpl
@Override public AjaxResult unifiedOrder(HttpServletRequest request, OrderInfoVO orderInfoVO) { //根據(jù)typeId查詢支付金額 根據(jù)自己的業(yè)務(wù)邏輯自行處理 SysFunctionType sysFunctionType = sysFunctionTypeMapper.selectFunctionTypeById(orderInfoVO.getTypeId()); //通過customId 查詢用戶信息 根據(jù)自己的業(yè)務(wù)邏輯自行處理 SysCustom sysCustom = sysCustomMapper.selectSysCustomById(orderInfoVO.getCustomId()); //根據(jù)自己的業(yè)務(wù)邏輯自行處理 OrderInfo為我自己業(yè)務(wù)中的實體類 OrderInfo orderInfo = new OrderInfo(); orderInfo.setId(orderInfoVO.getOrderId()); //支付類型 orderInfo.setPaymentType(orderInfoVO.getPayType()); //交易類型 orderInfo.setTradeType("NATIVE"); //支付金額(BigDecimal 例子:10.00) orderInfo.setPaymentPrice(sysFunctionType.getPrice()); orderInfo.setName(sysCustom.getName()+"體檢報告"); String body = orderInfo.getName(); body = body.length() > 40 ? body.substring(0,39) : body; //更新編號防止不同終端微信報重復(fù)訂單號 orderInfo.setOrderNo(IdUtil.getSnowflake(0,0).nextIdStr()); //公眾號 req.put("appid", payConfig.getAppId()); // 商戶號 req.put("mch_id", payConfig.getMchId()); // 32位隨機字符串 req.put("nonce_str", WXPayUtil.generateNonceStr()); // 商品描述 req.put("body", body); // 商戶訂單號 req.put("out_trade_no", orderInfo.getOrderNo()); // 標(biāo)價金額(分) req.put("total_fee", String.valueOf(MoneyUtils.yuanToFen(String.valueOf(orderInfo.getPaymentPrice())))); // 終端IP req.put("spbill_create_ip", request.getRemoteAddr()); // 回調(diào)地址+攜帶的返回參數(shù) domain 為配置的域名[不可為ip地址] req.put("notify_url", domain+"/system/order/info/notify-order-wx"+"/"+sysDevice.getTenantId()+"/"+orderInfo.getId()+"/"+orderInfoVO.getTypeId()); // 交易類型 req.put("trade_type", "NATIVE"); // 簽名 req.put("attach", String.valueOf(orderInfo.getTenantId())); try { // 簽名 req.put("sign", WXPayUtil.generateSignature(req, payConfig.getMchKey(), WXPayConstants.SignType.MD5)); String xmlBody = WXPayUtil.generateSignedXml(req, payConfig.getMchKey()); System.err.println(String.format("微信支付預(yù)下單請求 xml 格式:\n%s", xmlBody)); String result = WxChatPayCommonUtil.httpsRequest(WeChatPayUrl.Uifiedorder, "POST", xmlBody); this.updateOrderInfo(orderInfo); System.err.println(String.format("%s", result)); Map<String, String> WxResultMap = WXPayUtil.xmlToMap(result); WxResultMap.put("orderNo",orderInfo.getOrderNo()); if (ObjectUtil.isNotEmpty(WxResultMap.get("return_code")) && WxResultMap.get("return_code").equals("SUCCESS")) { if (WxResultMap.get("result_code").equals("SUCCESS")) { System.out.println("預(yù)下單成功"); System.out.println("QrCode:"+WxResultMap.get("code_url")); return AjaxResult.success(WxResultMap); } } } catch (Exception e) { throw new RuntimeException(e); } }
參數(shù)疑惑解釋:
?notify_url:此參數(shù)為回調(diào)通知地址(公網(wǎng)必須可以訪問),當(dāng)這筆訂單用戶支付成功之后,”微信“會異步請求你這個地址告訴你 某個訂單支付成功了。后面會講到這個怎么寫這個接口 包括如何在本地環(huán)境進行測試。
完成上面的代碼,簡單的一個支付后端接口實現(xiàn)就完成了。
3:題外篇 回調(diào)地址
異步回調(diào)通知官方文檔:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
簡單來說就是:
在一筆訂單支付成功之后微信會告訴你的服務(wù)器這筆訂單支付成功了,然后你就需要根據(jù)你的項目業(yè)務(wù)邏輯進行處理,改變數(shù)據(jù)庫的支付結(jié)果,然后發(fā)貨。所以你需要寫一個接口放到你們項目中,讓微信來調(diào)用你的接口就行了。
回調(diào)的接口地址必須是公網(wǎng)可以進行訪問的,如果開發(fā)中您的項目公網(wǎng)沒有辦法訪問的話,微信是無法調(diào)用的。所以我們需要弄一個內(nèi)網(wǎng)穿透 花生殼:https://hsk.oray.com/(免費)
這個時候把域名地址配置到 application.yml
company: domain: https://33q716k372.imdo.co
4.回調(diào)方法
Controller
/** * 支付回調(diào)(微信) * * @param xmlData 微信XML數(shù)據(jù) * @param tenantId 商家id * @param orderId 訂單id * @param typeId 類型id * @return 返回支付結(jié)果 */ @Operation(summary = "支付回調(diào)(微信)") @PostMapping("/notify-order-wx/{tenantId}/{orderId}/{typeId}") public String notifyOrderWx(HttpServletRequest request, HttpServletResponse response, @RequestBody String xmlData, @PathVariable("tenantId") Long tenantId, @PathVariable("orderId") Long orderId, @PathVariable("typeId") Long typeId) throws IOException { log.info("支付回調(diào)(微信):" + xmlData); if(tenantId == null || orderId == null || typeId == null){ System.out.println("驗簽失敗"); response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>"); } String resXml = ""; try { //通過商家id查詢支付配置 PayConfig payConfig = payConfigService.selectPayConfigByTenantId(tenantId,"1"); Map<String, Object> ResultMap = orderInfoService.WeChatPayCallback(xmlData, payConfig.getMchKey()); Map<String, String> WxResultMapData = new HashMap<>(); if (ResultMap.get("Verify").equals("YES")) { //驗簽成功 System.out.println("驗簽成功"); WxResultMapData = (Map<String, String>) ResultMap.get("data"); System.out.println("WxResultMapData:" + JSONUtil.toJsonStr(WxResultMapData)); log.info("收到微信回調(diào):{}", JSONUtil.toJsonStr(WxResultMapData)); OrderInfo orderInfo = orderInfoService.selectOrderInfoByOrderNo(WxResultMapData.get("out_trade_no")); System.out.println("通知微信驗簽成功"); //自信業(yè)務(wù)邏輯處理 orderInfoService.notifyOrder(orderInfo,tenantId,orderId,typeId,WxResultMapData); resXml = String.valueOf(ResultMap.get("returnWeChat")); } else if (ResultMap.get("Verify").equals("NO")) { resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" + WxResultMapData.get("err_code_des") + "]]></return_msg>" + "</xml> "; } }catch (Exception e) { throw new RuntimeException(e); }finally { BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } return WxPayNotifyResponse.success("成功"); }
Service
public Map<String, Object> WeChatPayCallback(String xmlData, String apiKey);
ServiceImpl
@Override public Map<String, Object> WeChatPayCallback(String xmlData, String apiKey) { Map<String, Object> ResultMap = new HashMap<String, Object>(); //解析到微信返回過來的xml數(shù)據(jù) try { //xml轉(zhuǎn)Map Map<String, String> WxResultMap = WXPayUtil.xmlToMap(xmlData); //驗證簽名 boolean SignStatus = WXPayUtil.isSignatureValid(WxResultMap, apiKey); if (SignStatus) { //驗證成功 //要返回給微信的xml數(shù)據(jù) String returnWeChat = WxChatPayCommonUtil.setReturnXml("SUCCESS", "OK"); ResultMap.put("Verify", "YES"); ResultMap.put("returnWeChat", returnWeChat); ResultMap.put("data", WxResultMap); } else { //驗證失敗(表示可能接口被他人調(diào)用 需要留意) ResultMap.put("Verify", "NO"); ResultMap.put("msg", "驗簽失敗。"); } return ResultMap; } catch (IOException e) { throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } }
總結(jié)
到此這篇關(guān)于Java接入微信支付的文章就介紹到這了,更多相關(guān)Java接入微信支付內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實現(xiàn)TCP連接并進行數(shù)據(jù)互傳的方法
本文詳細介紹了微服務(wù)架構(gòu)中的翻譯組件使用場景,以及多種開源翻譯組件的解決方案,文中分析了國內(nèi)外多個翻譯服務(wù)如百度翻譯、谷歌翻譯等,以及如何在微服務(wù)項目中集成這些翻譯組件,感興趣的朋友跟隨小編一起看看吧2024-11-11Java使用itextpdf實現(xiàn)PDF轉(zhuǎn)文本以及轉(zhuǎn)圖片
PDF轉(zhuǎn)文本的插件常用的有pdfbox ,itextpdf 和 spire.pdf,本文主要介紹如何使用itextpdf實現(xiàn)PDF轉(zhuǎn)文本以及轉(zhuǎn)圖片,需要的可以參考一下2025-01-01SpringCloudStream原理和深入使用小結(jié)
Spring?Cloud?Stream是一個用于構(gòu)建與共享消息傳遞系統(tǒng)連接的高度可擴展的事件驅(qū)動型微服務(wù)的框架,本文給大家介紹SpringCloudStream原理和深入使用,感興趣的朋友跟隨小編一起看看吧2024-06-06SpringBoot lombok(注解@Getter @Setter)詳解
通過使用Lombok庫,SpringBoot應(yīng)用可以自動化生成常用的方法如setter和getter,顯著降低了代碼冗余并提高了開發(fā)效率,Lombok的@Getter和@Setter注解用于自動生成屬性的訪問和修改方法,而@Data注解則提供了一個全面的解決方案2024-11-11springboot2如何集成ElasticSearch6.4.3
這篇文章主要介紹了springboot2如何集成ElasticSearch6.4.3問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07MyBatis-Plus MetaObjectHandler的原理及使用
MyBatis-Plus的MetaObjectHandler接口允許開發(fā)者自動填充實體類字段,如創(chuàng)建時間、更新時間等公共字段,減少代碼重復(fù),提高數(shù)據(jù)一致性和完整性,感興趣的可以了解一下2024-10-10java中的Integer的toBinaryString()方法實例
這篇文章主要介紹了java中的Integer的toBinaryString()方法實例,有需要的朋友可以參考一下2013-12-12