<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      微信支付功能的設計實現與關鍵實踐(UniApp+Java)全代碼

      微信支付功能的設計實現與關鍵實踐(UniApp+Java)全代碼

      感覺本篇對你有幫助可以關注一下我的微信公眾號(深入淺出談java),會不定期更新知識和面試資料、技巧!!!

      概述

      在移動互聯網時代,支付功能已成為應用開發的核心能力之一。本文將以 UniApp前端+Java后端技術棧為例,系統解析微信支付功能的設計實現與關鍵實踐,為開發者提供從技術架構到安全防護的全景視角。

      微信支付功能是跨平臺應用(UniApp前端 + Java后端)與微信支付系統對接的核心模塊,實現用戶從下單到支付完成的閉環流程。支持多種支付場景(如APP支付、小程序支付、H5支付),確保交易安全、實時性和數據一致性

      對于支付系統,公司一般會對其進行獨立,確保服務的安全、可靠、穩定。因此支付系統是現代互聯網的關鍵核心系統。本篇實現基本功能,提供思路,部分代碼不嚴謹,可以自行優化

      微信支付流程圖

      大致如下:流程圖中和流程步驟 描述的2、3 步進行了調換,不受影響,但是建議可以先創建訂單

      流程步驟

      1、用戶提交訂單請求

      • 行為主體:用戶(通過UniApp前端操作)

      • 動作描述:用戶在UniApp中選擇商品或服務,點擊支付按鈕,前端將訂單信息(如商品ID、數量、金額等)發送至Java后端。

      2、生成業務訂單

      • 行為主體:Java后端

      • 動作描述

        1. 后端接收訂單請求后,驗證數據合法性(如金額、商品庫存等)。

        2. 在數據庫中生成唯一業務訂單號(out_trade_no),并記錄訂單狀態為「待支付」

      3、調用微信統一下單API

      • 行為主體:Java后端 → 微信支付平臺

      • 動作描述

        1. 后端構造統一下單請求參數(包括商戶號、訂單號、金額、回調地址等)。

        2. 使用商戶API密鑰生成簽名(保障請求安全性)。

        3. 向微信支付平臺發送HTTP請求,調用統一下單接口(URL: https://api.mch.weixin.qq.com/pay/unifiedorder)。

      4、接收預支付交易單響應

      • 行為主體:微信支付平臺 → Java后端

      • 動作描述

        1. 微信驗證請求參數和簽名,確認無誤后生成預支付交易單。

        2. 返回XML格式響應數據,包含關鍵字段:

          • prepay_id(預支付交易標識,用于后續支付)

          • return_code(通信狀態碼,如SUCCESS/FAIL)

          • result_code(業務結果碼,如SUCCESS/FAIL)

      5、返回支付參數至前端

      • 行為主體:Java后端 → UniApp前端
      • 動作描述
        1. 后端解析微信返回的prepay_id,重新構造前端支付參數(需二次簽名)。
        2. 返回JSON數據給UniApp,包含:
          • appId(微信應用ID)
          • timeStamp(時間戳)
          • nonceStr(隨機字符串)
          • package(固定值Sign=WXPay
          • signType(簽名類型,通常為MD5或HMAC-SHA256)
          • paySign(最終支付簽名)

      6、調起微信支付界面

      • 行為主體:用戶(UniApp前端) → 微信客戶端

      • 動作描述

        1. UniApp通過uni.requestPayment API,傳入后端返回的支付參數。

        2. 微信客戶端(APP/小程序)根據參數拉起支付界面,用戶確認金額并輸入密碼。

      7、用戶完成支付

      • 行為主體:微信支付平臺 → 用戶
      • 動作描述
        1. 微信驗證支付密碼和賬戶余額,扣款成功后,向用戶展示支付結果頁面(成功/失敗)。

      8、異步通知支付結果

      • 行為主體:微信支付平臺 → Java后端
      • 動作描述
        1. 微信通過POST請求調用后端預設的notify_url(需公網可訪問)。
        2. 推送XML格式回調數據,包含:
          • out_trade_no(商戶訂單號)
          • transaction_id(微信支付單號)
          • total_fee(實際支付金額)
          • result_code(支付結果,如SUCCESS/FAIL)

      9、處理回調并響應微信

      • 行為主體:Java后端 → 微信支付平臺
      • 動作描述
        1. 后端接收回調數據后:
          • 驗證簽名防止偽造請求
          • 檢查訂單金額與業務系統是否一致
          • 更新訂單狀態為「已支付」(需做冪等處理,避免重復更新)
        2. 返回XML響應(必須包含<return_code><![CDATA[SUCCESS]]></return_code>),告知微信已正確處理。

      10、通知前端最終結果

      • 行為主體:Java后端 → UniApp前端
      • 動作描述
        1. 若前端未實時感知支付結果(如用戶關閉頁面),可通過兩種方式同步:
          • 輪詢查詢:前端定期請求后端訂單狀態接口
          • WebSocket推送:后端主動推送支付結果
        2. 更新前端界面顯示支付成功/失敗狀態。

      賬號準備工作

      申請微信小程序賬號

      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"
                  });
                }
              }
            })
          },
      

      效果圖

      前端傳遞參數,后端生成預訂單,返回前端,前端喚醒支付頁面,隨后支付即可,再調用訂單狀態查詢接口,對不同狀態的訂單進行自己的業務邏輯判斷。

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

      posted @ 2025-04-24 15:52  古渡藍按  閱讀(1273)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 丰满少妇被猛烈进出69影院| 老鸭窝在线视频| 湘阴县| 久久精品国产99久久美女| 成人免费无遮挡无码黄漫视频| 亚洲人成网站观看在线观看 | 少妇高潮喷水正在播放| 亚洲成人一区二区av| 亚洲国产精品综合久久20| 欧美一区内射最近更新| 国产一区二区三区18禁| 亚洲国产美国产综合一区| 男人av无码天堂| 日韩丝袜欧美人妻制服| 中日韩黄色基地一二三区| 日韩精品一区二区三区视频| 无码福利写真片视频在线播放| 国产精品三级黄色小视频| 久久亚洲精精品中文字幕| 久久精品波多野结衣| 日韩内射美女人妻一区二区三区| 婷婷丁香五月激情综合| 22222se男人的天堂| 2020精品自拍视频曝光| 99热这里只有成人精品国产 | 国产成人无码av大片大片在线观看| 日本无人区一区二区三区| 欧美猛少妇色xxxxx猛叫| 999国产精品999久久久久久 | 国产精品无码素人福利不卡| 国色天香成人一区二区| 风流少妇bbwbbw69视频| 亚洲一精品一区二区三区| 视频一区视频二区制服丝袜 | 亚洲中文字幕精品第一页| 欧美成人精品在线| 欧美大胆老熟妇乱子伦视频| 亚洲www永久成人网站| 老司机精品成人无码AV| 亚洲男人电影天堂无码| 麻豆成人精品国产免费|