Java接入微信支付超級詳細(xì)保姆級教程
本文介紹了“二維碼付款”的代碼。其他微信支付方式的代碼都在源碼中。
一、準(zhǔn)備開發(fā)所需的賬號以及配置信息
首先想要接入微信支付我們需要兩個(gè)玩意:
一是公眾號/小程序/企業(yè)微信(開發(fā)用的)這個(gè)是為了獲取 APPID
一是微信支付商戶(收錢用的) 獲取 api_key mch_id
1、前往:https://mp.weixin.qq.com/ (微信公眾平臺)注冊一個(gè)應(yīng)用,類型只能是:公眾號/小程序/企業(yè)微信,注冊完成需要完成”微信認(rèn)證“(微信需要收取300元)。
2、前往:https://pay.weixin.qq.com(微信支付商戶平臺)注冊一個(gè)商戶,支付成功后的錢就會在這個(gè)賬號里面。
? 1、APPID:應(yīng)用id也就是 公眾號/小程序的ID

? 2、Api_key: 對應(yīng)的APIv2密鑰

? 3、mch_Id:商戶ID (收錢的商家ID)對應(yīng)的是 【微信支付商戶號】

4.將申請的下來的APPID綁定到商戶號下,添加成功后再次到工作號里面
【廣告與服務(wù)—>微信支付】這個(gè)時(shí)候會看到關(guān)聯(lián)申請,同意就可以了。到這一步前置工作就完成了

二、準(zhǔn)備環(huán)境
項(xiàng)目采用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中,如果是多商戶則建立商戶收款配置表 將信息維護(hù)到數(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í)向輸出流寫數(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)元
* 可以為正負(fù)小數(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();
}
/**
* 處理元
* 可以為正負(fù)小數(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ù)校驗(yàn)
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ù)校驗(yàn)
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ù)校驗(yàn)
*/
if (StringUtils.isBlank(s)) {
return null;
}
return stringToList(s, null);
}
/**
* 字符串轉(zhuǎn)列表
*
* @param s
* @param regex 分割規(guī)則,默認(rèn)為逗號
* @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ù)校驗(yàn)
*/
if (StringUtils.isBlank(s)) {
return null;
}
/**
* 默認(rèn)逗號隔開
*/
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<>();
/**
* 只有單個(gè)元素
*/
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ù)為空校驗(yàn)
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)點(diǎn)符號和空格)
*
* @param s
* @return boolean
* @date 2020/9/9 13:30
* @author YouHan
*/
public static Boolean isContainChinese(String s) {
/**
* 參數(shù)校驗(yàn)
*/
if (StringUtils.isBlank(s)) {
return false;
}
if (s.contains(" ")) {
return true;
}
/**
* 中文正則表達(dá)式
*/
String regex = "[\u4e00-\u9fa5]";
if (s.matches(regex)) {
return Boolean.TRUE;
}
/**
* 中文標(biāo)點(diǎn)符號處理
*/
char[] chars = s.toCharArray();
for (char c : chars) {
if (isChinesePunctuation(c)) {
return true;
}
}
return false;
}
/**
* 過濾中文(包括標(biāo)點(diǎn)符號和空格)
*
* @param s
* @return java.lang.String
* @date 2020/9/9 14:08
* @author YouHan
*/
public static String filterChinese(String s) {
/**
* 參數(shù)校驗(yàn)
*/
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)點(diǎn)符號
*
* @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("-", "");
}
/**
* 安全比較(可防止時(shí)序攻擊 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ù)正則表達(dá)式
*/
public static final String INTEGER_REGEX = "^[-\\+]?[\\d]*$";
/**
* 數(shù)字正則表達(dá)式
*/
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);
}
/**
* 判斷給定字符串是否為十六進(jìn)制數(shù)
*
* @param value 值
* @return 是否為十六進(jìn)制
*/
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);
}
/**
* 十進(jìn)制轉(zhuǎn)十六進(jìn)制
*
* @param n 十進(jì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);
}
/**
* 十進(jìn)制轉(zhuǎn)十六進(jìn)制
*
* @param n 十進(jì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);
}
/**
* 十進(jìn)制轉(zhuǎn)十六進(jì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;
}
/**
* 十進(jìn)制轉(zhuǎn)十六進(jì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;
}
/**
* 十進(jìn)制轉(zhuǎn)十六進(jìn)制
*
* @param n 十進(jì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;
}
/**
* 十進(jìn)制轉(zhuǎn)十六進(jìn)制
*
* @param n 十進(jì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;
}
/**
* 十六進(jìn)制轉(zhuǎn)十進(jì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));
}
/**
* 十六進(jìn)制轉(zhuǎn)十進(jì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 的 十六進(jìn)制
if (hex.length() > 1 && hex.startsWith("0")) {
hex = hex.substring(1);
}
return Long.valueOf(hex, 16);
}
/**
* 字符串轉(zhuǎn)十六進(jì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();
}
/**
* 十六進(jìn)制轉(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 隨機(jī)數(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 隨機(jī)數(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);
}
/**
* 獲取隨機(jī)數(shù)
*
* @param
* @return java.client.concurrent.ThreadLocalRandom
* @author YouHan
* @date 2021/6/3 10:29
*/
public static ThreadLocalRandom getRandom() {
return ThreadLocalRandom.current();
}
/**
* 獲取緩存穿透時(shí)間(單位秒),最長不超過 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ù)庫緩存時(shí)間(單位秒),最長不超過 1 小時(shí)
*
* @param
* @return java.lang.Long
* @date 2021/4/26 9:50
* @author YouHan
*/
public static Long getDBCacheTime() {
return Long.valueOf(int2String(getRandom().nextInt(3600)));
}
/**
* 十六進(jìn)制高低位轉(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ù)中的實(shí)體類
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()+"體檢報(bào)告");
String body = orderInfo.getName();
body = body.length() > 40 ? body.substring(0,39) : body;
//更新編號防止不同終端微信報(bào)重復(fù)訂單號
orderInfo.setOrderNo(IdUtil.getSnowflake(0,0).nextIdStr());
//公眾號
req.put("appid", payConfig.getAppId());
// 商戶號
req.put("mch_id", payConfig.getMchId());
// 32位隨機(jī)字符串
req.put("nonce_str", WXPayUtil.generateNonceStr());
// 商品描述
req.put("body", body);
// 商戶訂單號
req.put("out_trade_no", orderInfo.getOrderNo());
// 標(biāo)價(jià)金額(分)
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)這筆訂單用戶支付成功之后,”微信“會異步請求你這個(gè)地址告訴你 某個(gè)訂單支付成功了。后面會講到這個(gè)怎么寫這個(gè)接口 包括如何在本地環(huán)境進(jìn)行測試。

完成上面的代碼,簡單的一個(gè)支付后端接口實(shí)現(xiàn)就完成了。
3:題外篇 回調(diào)地址
異步回調(diào)通知官方文檔:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
簡單來說就是:
在一筆訂單支付成功之后微信會告訴你的服務(wù)器這筆訂單支付成功了,然后你就需要根據(jù)你的項(xiàng)目業(yè)務(wù)邏輯進(jìn)行處理,改變數(shù)據(jù)庫的支付結(jié)果,然后發(fā)貨。所以你需要寫一個(gè)接口放到你們項(xiàng)目中,讓微信來調(diào)用你的接口就行了。
回調(diào)的接口地址必須是公網(wǎng)可以進(jìn)行訪問的,如果開發(fā)中您的項(xiàng)目公網(wǎng)沒有辦法訪問的話,微信是無法調(diào)用的。所以我們需要弄一個(gè)內(nèi)網(wǎng)穿透 花生殼:https://hsk.oray.com/(免費(fèi))



這個(gè)時(shí)候把域名地址配置到 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("驗(yàn)簽失敗");
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")) {
//驗(yàn)簽成功
System.out.println("驗(yàn)簽成功");
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àn)簽成功");
//自信業(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);
//驗(yàn)證簽名
boolean SignStatus = WXPayUtil.isSignatureValid(WxResultMap, apiKey);
if (SignStatus) {
//驗(yàn)證成功
//要返回給微信的xml數(shù)據(jù)
String returnWeChat = WxChatPayCommonUtil.setReturnXml("SUCCESS", "OK");
ResultMap.put("Verify", "YES");
ResultMap.put("returnWeChat", returnWeChat);
ResultMap.put("data", WxResultMap);
} else {
//驗(yàn)證失敗(表示可能接口被他人調(diào)用 需要留意)
ResultMap.put("Verify", "NO");
ResultMap.put("msg", "驗(yàn)簽失敗。");
}
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自帶的緩存@EnableCaching用法
這篇文章主要介紹了springboot自帶的緩存@EnableCaching用法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
解決在IDEA中創(chuàng)建多級package的問題
這篇文章主要介紹了解決在IDEA中創(chuàng)建多級package的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Spring?Aop+Redis實(shí)現(xiàn)優(yōu)雅記錄接口調(diào)用情況
通常情況下,開發(fā)完一個(gè)接口,無論是在測試階段還是生產(chǎn)上線,我們都需要對接口的執(zhí)行情況做一個(gè)監(jiān)控,所以本文為大家整理了Spring統(tǒng)計(jì)接口調(diào)用的多種方法,希望對大家有所幫助2023-06-06
詳解在Spring MVC中使用注解的方式校驗(yàn)RequestParams
本篇文章主要介紹了詳解在Spring MVC中使用注解的方式校驗(yàn)RequestParams ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03

