微信支付功能的設計實現與關鍵實踐(UniApp+Java)全代碼
微信支付功能的設計實現與關鍵實踐(UniApp+Java)全代碼
感覺本篇對你有幫助可以關注一下我的微信公眾號(深入淺出談java),會不定期更新知識和面試資料、技巧!!!

概述
在移動互聯網時代,支付功能已成為應用開發的核心能力之一。本文將以 UniApp前端+Java后端技術棧為例,系統解析微信支付功能的設計實現與關鍵實踐,為開發者提供從技術架構到安全防護的全景視角。
微信支付功能是跨平臺應用(UniApp前端 + Java后端)與微信支付系統對接的核心模塊,實現用戶從下單到支付完成的閉環流程。支持多種支付場景(如APP支付、小程序支付、H5支付),確保交易安全、實時性和數據一致性
對于支付系統,公司一般會對其進行獨立,確保服務的安全、可靠、穩定。因此支付系統是現代互聯網的關鍵核心系統。本篇實現基本功能,提供思路,部分代碼不嚴謹,可以自行優化
微信支付流程圖
大致如下:流程圖中和流程步驟 描述的2、3 步進行了調換,不受影響,但是建議可以先創建訂單

流程步驟
1、用戶提交訂單請求
-
行為主體:用戶(通過UniApp前端操作)
-
動作描述:用戶在UniApp中選擇商品或服務,點擊支付按鈕,前端將訂單信息(如商品ID、數量、金額等)發送至Java后端。
2、生成業務訂單
-
行為主體:Java后端
-
動作描述:
-
后端接收訂單請求后,驗證數據合法性(如金額、商品庫存等)。
-
在數據庫中生成唯一業務訂單號(
out_trade_no),并記錄訂單狀態為「待支付」
-
3、調用微信統一下單API
-
行為主體:Java后端 → 微信支付平臺
-
動作描述:
-
后端構造統一下單請求參數(包括商戶號、訂單號、金額、回調地址等)。
-
使用商戶API密鑰生成簽名(保障請求安全性)。
-
向微信支付平臺發送HTTP請求,調用統一下單接口(URL:
https://api.mch.weixin.qq.com/pay/unifiedorder)。
-
4、接收預支付交易單響應
-
行為主體:微信支付平臺 → Java后端
-
動作描述:
-
微信驗證請求參數和簽名,確認無誤后生成預支付交易單。
-
返回XML格式響應數據,包含關鍵字段:
-
prepay_id(預支付交易標識,用于后續支付) -
return_code(通信狀態碼,如SUCCESS/FAIL) -
result_code(業務結果碼,如SUCCESS/FAIL)
-
-
5、返回支付參數至前端
- 行為主體:Java后端 → UniApp前端
- 動作描述:
- 后端解析微信返回的
prepay_id,重新構造前端支付參數(需二次簽名)。 - 返回JSON數據給UniApp,包含:
appId(微信應用ID)timeStamp(時間戳)nonceStr(隨機字符串)package(固定值Sign=WXPay)signType(簽名類型,通常為MD5或HMAC-SHA256)paySign(最終支付簽名)
- 后端解析微信返回的
6、調起微信支付界面
-
行為主體:用戶(UniApp前端) → 微信客戶端
-
動作描述:
-
UniApp通過
uni.requestPaymentAPI,傳入后端返回的支付參數。 -
微信客戶端(APP/小程序)根據參數拉起支付界面,用戶確認金額并輸入密碼。
-
7、用戶完成支付
- 行為主體:微信支付平臺 → 用戶
- 動作描述:
- 微信驗證支付密碼和賬戶余額,扣款成功后,向用戶展示支付結果頁面(成功/失敗)。
8、異步通知支付結果
- 行為主體:微信支付平臺 → Java后端
- 動作描述:
- 微信通過POST請求調用后端預設的
notify_url(需公網可訪問)。 - 推送XML格式回調數據,包含:
out_trade_no(商戶訂單號)transaction_id(微信支付單號)total_fee(實際支付金額)result_code(支付結果,如SUCCESS/FAIL)
- 微信通過POST請求調用后端預設的
9、處理回調并響應微信
- 行為主體:Java后端 → 微信支付平臺
- 動作描述:
- 后端接收回調數據后:
- 驗證簽名防止偽造請求
- 檢查訂單金額與業務系統是否一致
- 更新訂單狀態為「已支付」(需做冪等處理,避免重復更新)
- 返回XML響應(必須包含
<return_code><![CDATA[SUCCESS]]></return_code>),告知微信已正確處理。
- 后端接收回調數據后:
10、通知前端最終結果
- 行為主體:Java后端 → UniApp前端
- 動作描述:
- 若前端未實時感知支付結果(如用戶關閉頁面),可通過兩種方式同步:
- 輪詢查詢:前端定期請求后端訂單狀態接口
- WebSocket推送:后端主動推送支付結果
- 更新前端界面顯示支付成功/失敗狀態。
- 若前端未實時感知支付結果(如用戶關閉頁面),可通過兩種方式同步:
賬號準備工作
申請微信小程序賬號
1、開發小程序的第一步,你需要擁有一個小程序賬號,因此先申請小程序賬號
小程序注冊地址:小程序

2、信息填好,進行注冊,就會產生AppID、AppSecret

小程序的 AppID 相當于小程序平臺的一個身份證,后續你會在很多地方要用到 AppID (注意這里要區別于服務號或訂閱號的 AppID)
微信支付 官網開通商戶支付能力
微信支付官網:微信支付 - 中國領先的第三方支付平臺 | 微信支付提供安全快捷的支付方式
1、找到接入微信支付,進行綁定注冊
注冊需要營業執照、法人信息,按照要求填寫即可

2、注冊微信支付商戶號

3、填寫必要信息進行注冊

4、申請證書和AIPv3秘鑰,v2現在有淘汰趨勢,不需要申請v2,直接v3走起。

5、下載和保存好 秘鑰及證書(PS:一定要好好保存)

6、獲取商戶號

接入實現
服務端代碼
1、導入maven依賴
<!-- 微信支付API -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
<scope>compile</scope>
</dependency>
<!-- 微信支付SDK(官方或第三方封裝) -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.7</version>
</dependency>
2、配置商戶信息
# 微信支付配置
pay:
appId: xxx #應用id
mchId: xxx #商戶id
notifyUrl: https://服務器ip或對應域名/wxpay/weixin/callback #支付回調地址
3、實體類代碼
這個部分是需要用到的實體類代碼
WeChatPay:微信支付預下單實體類
@Data
@Accessors(chain = true)
public class WeChatPay {
/**
* 返回狀態碼 此字段是通信標識,非交易標識,交易是否成功需要查看result_code來判斷
*/
public String return_code;
/**
* 返回信息 當return_code為FAIL時返回信息為錯誤原因 ,例如 簽名失敗 參數格式校驗錯誤
*/
private String return_msg;
/**
* 公眾賬號ID 調用接口提交的公眾賬號ID
*/
private String appid;
/**
* 商戶號 調用接口提交的商戶號
*/
private String mch_id;
/**
* api密鑰 詳見:https://pay.weixin.qq.com/index.php/extend/employee
*/
private String api_key;
/**
* 設備號 自定義參數,可以為請求支付的終端設備號等
*/
private String device_info;
/**
* 隨機字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的隨機字符串
*/
private String nonce_str;
/**
* 簽名 微信返回的簽名值,詳見簽名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
*/
private String sign;
/**
* 簽名類型
*/
private String sign_type;
/**
* 業務結果 SUCCESS SUCCESS/FAIL
*/
private String result_code;
/**
* 錯誤代碼 當result_code為FAIL時返回錯誤代碼,詳細參見下文錯誤列表
*/
private String err_code;
/**
* 錯誤代碼描述 當result_code為FAIL時返回錯誤描述,詳細參見下文錯誤列表
*/
private String err_code_des;
/**
* 交易類型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 說明詳見;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String trade_type;
/**
* 預支付交易會話標識 微信生成的預支付會話標識,用于后續接口調用中使用,該值有效期為2小時
*/
private String prepay_id;
/**
* 二維碼鏈接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE時有返回,此url用于生成支付二維碼,然后提供給用戶進行掃碼支付。注意:code_url的值并非固定,使用時按照URL格式轉成二維碼即可
*/
private String code_url;
/**
* 商品描述 商品簡單描述,該字段請按照規范傳遞,具體請見 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String body;
/**
* 商家訂單號 商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|* 且在同一個商戶號下唯一。詳見商戶訂單號 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String out_trade_no;
/**
* 標價金額 訂單總金額,單位為分,詳見支付金額 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
*/
private String total_fee;
/**
* 終端IP 支持IPV4和IPV6兩種格式的IP地址。用戶的客戶端IP
*/
private String spbill_create_ip;
/**
* 通知地址 異步接收微信支付結果通知的回調地址,通知url必須為外網可訪問的url,不能攜帶參數。公網域名必須為https,如果是走專線接入,使用專線NAT IP或者私有回調域名可使用http
*/
private String notify_url;
/**
* 子商戶號 sub_mch_id 非必填(商戶不需要傳入,服務商模式才需要傳入) 微信支付分配的子商戶號
*/
private String sub_mch_id;
/**
* 附加數據,在查詢API和支付通知中原樣返回,該字段主要用于商戶攜帶訂單的自定義數據
*/
private String attach;
/**
* 商戶系統內部的退款單號,商戶系統內部唯一,只能是數字、大小寫字母_-|*@ ,同一退款單號多次請求只退一筆。
*/
private String out_refund_no;
/**
* 退款總金額,單位為分,只能為整數,可部分退款。詳見支付金額 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
*/
private String refund_fee;
/**
* 退款原因 若商戶傳入,會在下發給用戶的退款消息中體現退款原因 注意:若訂單退款金額≤1元,且屬于部分退款,則不會在退款消息中體現退款原因
*/
private String refund_desc;
/**
* 交易結束時間 訂單失效時間,格式為yyyyMMddHHmmss,如2009年12月27日9點10分10秒表示為20091227091010。其他詳見時間規則 注意:最短失效時間間隔必須大于5分鐘
*/
private String time_expire;
/**
* 用戶標識 trade_type=JSAPI,此參數必傳,用戶在主商戶appid下的唯一標識。openid和sub_openid可以選傳其中之一,如果選擇傳sub_openid,則必須傳sub_appid。下單前需要調用【網頁授權獲取用戶信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口獲取到用戶的Openid。
*/
private String openid;
/**
* 時間戳
*/
private String time_stamp;
/**
* 會員類型
*/
private String memberShipType;
}
PayParameterVO:微信支付,商品信息對象
@Data
public class PayParameterVO {
/** 商品價格(單位:分) */
private String price;
/** 微信openId */
private String wxOpenId;
/** 商品描述 */
private String goodsTitle;
}
OrderReturnInfo:預下單成功之后返回結果對象
@Data
public class OrderReturnInfo {
/** 返回狀態碼 */
private String return_code;
/** 返回信息 */
private String return_msg;
/** 業務結果 */
private String result_code;
/** 小程序appID */
private String appid;
/** 商戶號 */
private String mch_id;
/** 隨機字符串 */
private String nonce_str;
/** 簽名 */
private String sign;
/** 預支付交易會話標識。用于后續接口調用中使用,該值有效期為2小時 */
private String prepay_id;
/** 交易類型 */
private String trade_type;
}
QueryReturnInfo:查詢訂單返回實體類
@Data
public class QueryReturnInfo {
/** 返回狀態碼 */
private String return_code;
/** 返回信息 */
private String return_msg;
/** 業務結果 */
private String result_code;
/** 錯誤代碼 */
private String err_code;
/** 錯誤代碼描述 */
private String err_code_des;
/** 小程序appID */
private String appid;
/** 商戶號 */
private String mch_id;
/** 隨機字符串 */
private String nonce_str;
/** 簽名 */
private String sign;
/** 簽名類型 */
private String sign_type;
private String prepay_id;
/** 交易類型 */
private String trade_type;
/** 設備號 */
private String device_info;
/** 用戶標識 */
private String openid;
/** 是否關注公眾賬號 */
private String is_subscribe;
private String trade_state;
/** 付款銀行 */
private String bank_type;
/** 訂單金額 */
private int total_fee;
/** 應結訂單金額 */
private int settlement_total_fee;
/** 貨幣種類 */
private String fee_type;
/** 現金支付金額 */
private int cash_fee;
/** 現金支付貨幣類型 */
private String cash_fee_type;
/** 總代金券金額 */
private int coupon_fee;
/** 代金券使用數量 */
private int coupon_count;
/** 代金券類型 */
private String coupon_type_$n;
/** 代金券ID */
private String coupon_id_$n;
/** 單個代金券支付金額 */
private String coupon_fee_$n;
/** 微信支付訂單號 */
private String transaction_id;
/** 商戶訂單號 */
private String out_trade_no;
/** 支付完成時間 */
private String time_end;
private String trade_state_desc;
/** 商家數據包 */
private String attach;
}
SignInfo:簽名實體類
@Data
public class SignInfo {
private String appId;//小程序ID
private String timeStamp;//時間戳
private String nonceStr;//隨機串
@XStreamAlias("package")
private String repay_id;
private String signType;//簽名方式
public void setSignType(String signType) {
this.signType = signType;
}
}
4、工具類代碼
SignUtils:簽名工具類
@Slf4j
public class SignUtils {
/**
* 簽名算法
*
* @param o 要參與簽名的數據對象
* @return 簽名
* @throws IllegalAccessException
*/
public static String getSign(Object o) throws IllegalAccessException {
ArrayList<String> list = new ArrayList<String>();
Class cls = o.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
if (f.get(o) != null && f.get(o) != "") {
String name = f.getName();
XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
if (anno != null) {
name = anno.value();
}
list.add(name + "=" + f.get(o) + "&");
}
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + Configure.getKey();
log.info("簽名數據:" + result);
result = MD5.MD5Encode(result).toUpperCase();
return result;
}
public static String getSign(Map<String, Object> map) {
ArrayList<String> list = new ArrayList<String>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() != "") {
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + Configure.getKey();
//Util.log("Sign Before MD5:" + result);
result = MD5.MD5Encode(result).toUpperCase();
//Util.log("Sign Result:" + result);
return result;
}
}
MD5:MD5 加密工具類
public class MD5 {
private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "a", "b", "c", "d", "e", "f"};
/**
* 轉換字節數組為16進制字串
*
* @param b 字節數組
* @return 16進制字串
*/
public static String byteArrayToHexString(byte[] b) {
StringBuilder resultSb = new StringBuilder();
for (byte aB : b) {
resultSb.append(byteToHexString(aB));
}
return resultSb.toString();
}
/**
* 轉換byte到16進制
*
* @param b 要轉換的byte
* @return 16進制格式
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* MD5編碼
*
* @param origin 原始字符串
* @return 經過MD5加密之后的結果
*/
public static String MD5Encode(String origin) {
String resultString = null;
try {
resultString = origin;
MessageDigest md = MessageDigest.getInstance("MD5");
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
return resultString;
}
}
MapToObject:map 轉化對象工具類
public class MapToObject {
/**
* Map數據轉為java對象
* @param map map數據
* @param targetType 對象類型
* @return
* @param <T>
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T> T convertMapToObject(Map<String, String> map, Class<T> targetType) throws IllegalAccessException, InstantiationException {
T targetObject = targetType.newInstance();
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
try {
// 使用反射獲取字段
Field field = targetType.getDeclaredField(key);
// 設置字段可訪問(如果是私有字段)
field.setAccessible(true);
// 獲取字段的類型
Class<?> fieldType = field.getType();
// 將字符串值轉換為字段類型
Object convertedValue = convertStringToType(value, fieldType);
// 設置字段的值
field.set(targetObject, convertedValue);
} catch (NoSuchFieldException e) {
// 處理字段不存在的異常
e.printStackTrace();
}
}
return targetObject;
}
private static Object convertStringToType(String value, Class<?> targetType) {
if (targetType == int.class || targetType == Integer.class) {
return Integer.parseInt(value);
}
// 添加其他可能的類型轉換邏輯,例如 double、float、Date 等
// 默認情況下,返回字符串值
return value;
}
}
HttpRequest:請求工具類
public class HttpRequest {
//連接超時時間,默認10秒
private static final int socketTimeout = 10000;
//傳輸超時時間,默認30秒
private static final int connectTimeout = 30000;
/**
* post請求
*
* @throws IOException
* @throws ClientProtocolException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
* @throws KeyManagementException
* @throws UnrecoverableKeyException
*/
public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException {
HttpPost httpPost = new HttpPost(url);
//解決XStream對出現雙下劃線的bug
XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
xStreamForRequestPostData.alias("xml", xmlObj.getClass());
//將要提交給API的數據對象轉換成XML格式數據Post給API
String postDataXML = xStreamForRequestPostData.toXML(xmlObj);
//得指明使用UTF-8編碼,否則到API服務器XML的中文不能被成功識別
StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
//設置請求器的配置
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
httpPost.setConfig(requestConfig);
HttpClient httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
return result;
}
/**
* 自定義證書管理器,信任所有證書
*
* @author pc
*/
public static class MyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] arg0, String arg1)
throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
}
}
5、配置類代碼
WxPayConfig:微信支付配置
@Data
@Component
@Configuration
@ConfigurationProperties(prefix = "pay")
public class WxPayConfig {
/**
* 微信小程序appid
*/
private String appId;
/**
* 小程序設置的API v2密鑰
*/
private String apiKey;
/**
* 微信商戶平臺 商戶id
*/
private String mchId;
/**
*小程序密鑰
*/
private String appSecret;
/**
* 小程序支付異步回調地址
*/
private String notifyUrl;
}
Configure:商戶支付秘鑰
public class Configure {
/**
* 商戶支付秘鑰
*/
@Getter
private static String key = "此處填寫秘鑰";
public static void setKey(String key) {
Configure.key = key;
}
}
6、常量類代碼
WeChatPayUrlConstants:微信支付API地址常量
public class WeChatPayUrlConstants {
/**
* 統一下單預下單接口url
*/
public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 訂單狀態查詢接口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";
/**
* 微信網頁授權 獲取“code”請求地址
*/
public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
/**
* 微信網頁授權 獲取“code” 回調地址
*/
public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html";
}
7、業務實現類代碼
Controller 層
@Slf4j
@RestController
@RequestMapping("/system/wxpay")
public class WxPayController {
@Autowired
private WxPayInfoService wxPayInfoService;
/**
* 小程序支付下單接口
*
* @return 返回結果
*/
@ApiOperation("小程序支付功能")
@PostMapping("/pay")
public InvokeResult wxPay(@RequestBody PayParameterVO payParameterVO) {
PayParameterVO parameterVO = new PayParameterVO();
parameterVO.setWxOpenId(payParameterVO.getWxOpenId());
parameterVO.setPrice("1");
parameterVO.setGoodsTitle("測試支付商品");
HashMap<String, String> payHistory = wxPayInfoService.insertPayRecord(parameterVO);
return InvokeResultBuilder.success(payHistory);
}
/**
* 查詢訂單
*/
@ApiOperation("訂單查詢")
@PostMapping("/wx/query")
public InvokeResult orderQuery(@RequestParam("out_trade_no") String out_trade_no) {
QueryReturnInfo queryReturnInfo = wxPayInfoService.orderQuery(out_trade_no);
// return InvokeResultBuilder.success(queryReturnInfo.getTrade_state_desc(), queryReturnInfo);
return InvokeResultBuilder.success(queryReturnInfo);
}
/**
* 微信小程序支付成功回調
*
* @param request 請求
* @param response 響應
* @return 返回結果
* @throws Exception 異常處理
*/
@RequestMapping("/weixin/callback")
public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("接收到微信支付回調信息");
String notifyXml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
// 解析返回結果
Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml);
// 判斷支付是否成功
if ("SUCCESS".equals(notifyMap.get("result_code"))) {
//支付成功時候,處理業務邏輯
wxPayInfoService.payCallbackSuccess(notifyMap);
//返回處理成功的格式數據,避免微信重復回調
return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
}
// 創建響應對象:微信接收到校驗失敗的結果后,會反復的調用當前回調函數
Map<String, String> returnMap = new HashMap<>();
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "");
String returnXml = WXPayUtil.mapToXml(returnMap);
response.setContentType("text/xml");
System.out.println("校驗失敗");
return returnXml;
}
、
}
接口層
public interface WxPayInfoService {
/**
* 創建統一支付訂單
*/
HashMap<String, String> insertPayRecord(PayParameterVO payParameterVO);
/**
* 查詢訂單
* @param out_trade_no 訂單號
* @return 返回結果
*/
QueryReturnInfo orderQuery(String out_trade_no);
/**
* 微信小程序支付成功回調
* @param notifyMap
*/
void payCallbackSuccess(Map<String, String> notifyMap);
}
實現層
@Slf4j
@Service
public class WxPayInfoServiceImpl implements WxPayInfoService {
//這是注入的業務處理類
// @Autowired
// private IFPayOrderService ifPayOrderService;
@Resource
private WxPayConfig payProperties;
private static final DecimalFormat df = new DecimalFormat("#");
/**
* 創建統一支付訂單
*
* @param payParameterVO 商品信息
* @return 返回結果
*/
@Override
@Transactional
public HashMap<String, String> insertPayRecord(PayParameterVO payParameterVO) {
String title = payParameterVO.getGoodsTitle();
//金額 * 100 以分為單位
BigDecimal fee = BigDecimal.valueOf(1);
BigDecimal RMB = new BigDecimal(payParameterVO.getPrice());
BigDecimal totalFee = fee.multiply(RMB);
try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppId());
weChatPay.setMch_id(payProperties.getMchId());
weChatPay.setNonce_str(getRandomStringByLength(32));
weChatPay.setBody(title);
weChatPay.setOut_trade_no(getRandomStringByLength(32));
weChatPay.setTotal_fee(df.format(Double.parseDouble(String.valueOf(totalFee))));
// 獲取當前服務器IP地址
// weChatPay.setSpbill_create_ip(IpUtils());
weChatPay.setNotify_url(payProperties.getNotifyUrl());
weChatPay.setTrade_type("JSAPI");
//這里直接使用當前用戶的openid
weChatPay.setOpenid(payParameterVO.getWxOpenId());
weChatPay.setSign_type("MD5");
//生成簽名
String sign = SignUtils.getSign(weChatPay);
weChatPay.setSign(sign);
log.info("訂單號:" + weChatPay.getOut_trade_no());
//向微信發送下單請求
String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay);
//將返回結果從xml格式轉換為map格式
Map<String, String> wxResultMap = WXPayUtil.xmlToMap(result);
if (StringUtils.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")) {
if (wxResultMap.get("result_code").equals("FAIL")) {
log.error("微信統一下單失敗!");
return null;
}
}
OrderReturnInfo returnInfo = MapToObject.convertMapToObject(wxResultMap, OrderReturnInfo.class);
// 二次簽名
if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
SignInfo signInfo = new SignInfo();
signInfo.setAppId(payProperties.getAppId());
long time = System.currentTimeMillis() / 1000;
signInfo.setTimeStamp(String.valueOf(time));
signInfo.setNonceStr(WXPayUtil.generateNonceStr());
signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
signInfo.setSignType("MD5");
//生成簽名
String sign1 = SignUtils.getSign(signInfo);
HashMap<String, String> payInfo = new HashMap<>();
payInfo.put("timeStamp", signInfo.getTimeStamp());
payInfo.put("nonceStr", signInfo.getNonceStr());
payInfo.put("package", signInfo.getRepay_id());
payInfo.put("signType", signInfo.getSignType());
payInfo.put("paySign", sign1);
payInfo.put("placeOrderJsonMsg", JSON.toJSONString(weChatPay));
payInfo.put("orderNum", weChatPay.getOut_trade_no());
// 業務邏輯結束 回傳給小程序端喚起支付
return payInfo;
}
return null;
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
/**
* 查詢訂單
*
* @param out_trade_no 訂單號
* @return 返回結果
*/
@Override
public QueryReturnInfo orderQuery(String out_trade_no) {
try {
WeChatPay weChatPay = new WeChatPay();
weChatPay.setAppid(payProperties.getAppId());
weChatPay.setMch_id(payProperties.getMchId());
weChatPay.setNonce_str(WXPayUtil.generateNonceStr());
weChatPay.setOut_trade_no(out_trade_no);
//order.setSign_type("MD5");
//生成簽名
String sign = SignUtils.getSign(weChatPay);
weChatPay.setSign(sign);
//向微信發送查詢訂單詳情請求
String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay);
Map<String, String> xmlToMap = WXPayUtil.xmlToMap(result);
// 將 Map 轉換為對象
return MapToObject.convertMapToObject(xmlToMap, QueryReturnInfo.class);
} catch (Exception e) {
log.error("查詢支付訂單失敗:[{}]", e.getMessage());
}
return null;
}
/**
* 微信小程序支付成功回調
*
* @param notifyMap 回調Map數據
*/
@Override
public void payCallbackSuccess(Map<String, String> notifyMap) {
//保存相關支付數據
try {
QueryReturnInfo queryReturnInfo = MapToObject.convertMapToObject(notifyMap, QueryReturnInfo.class);
log.info("支付回調信息:" + queryReturnInfo);
//處理回調信息,此處根據自己的項目業務進行處理
// ifPayOrderService.receivePayCallback(queryReturnInfo);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 獲取一定長度的隨機字符串
*
* @param length 指定字符串長度
* @return 一定長度的字符串
*/
public static String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
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();
}
}
前端核心代碼
支付方法
goPay () {
console.log('goPay');
const that = this
//向服務器發送下單請求(服務端會返回預下單信息)
uni.request({
url: common.api_base_url + "/system/wxpay/pay",
header: {
"Content-Type": "application/json",
"Authorization": "Bearer " + uni.getStorageSync('token'),
},
method: 'POST',
data: {
goodsId: that.nowGoodsId,
},
success(res) {
console.log("下單信息結果",res)
if (res.data.code == 200){
//成功,調用微信支付接口進行支付()
uni.requestPayment({
provider: 'wxpay',
timeStamp: res.data.data.timeStamp,
nonceStr: res.data.data.nonceStr,
package: res.data.data.package,
signType: res.data.data.signType,
paySign: res.data.data.paySign,
// appId: app.globalData.appid,
success: function (ress) {
console.log("支付完成:",ress)
//支付成功后,查詢訂單情況,或者2 秒自動跳轉到其他頁面
uni.showToast({
title: '支付成功',
duration: 2000
});
},
fail: function (err) {
uni.showToast({
title: '支付失敗!',err,
icon:"none",
duration: 2000
});
}
});
}else {
//彈出下單失敗的提示
uni.showToast({
title:res.data.msg,
icon:"none"
});
}
}
})
},
效果圖
前端傳遞參數,后端生成預訂單,返回前端,前端喚醒支付頁面,隨后支付即可,再調用訂單狀態查詢接口,對不同狀態的訂單進行自己的業務邏輯判斷。

最后文章有啥不對,歡迎大佬在評論區指點!!!
如果感覺對你有幫助就點贊推薦或者關注一下吧!!!
****

浙公網安備 33010602011771號