首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

微信支付 V3 統一下單 API 完整整合指南 (Java 實現)

     編輯:Java 相關知識

微信支付統一下單是 JSAPI/小程序/APP 支付的核心前置步驟。本文基於微信支付 V3 版介面,詳細講解商戶號配置、請求參數構建、簽名算法 (RSA-SHA256)、XML/JSON 轉換、HTTP 客戶端調用、應答解析與錯誤處理,附生產級 Java 代碼結構。

前置準備:商戶平台配置

  1. 登錄 微信商戶平台 獲取:AppID (appid)、商戶號 (mch_id)、APIv3 密鑰 (api_v3_key)、商戶私鑰 (PKCS#8 PEM)、微信支付平台公鑰 (用於驗簽)。
  2. 設置 **支付授權目錄** (JSAPI 支付頁面域名)、**回調通知地址** (需外網可訪問 HTTPS)。
  3. 下載並配置 **API 證書** (用於雙向 TLS,V3 必須)。

核心流程時序

  1. 前端獲取用戶 openid (靜默授權 snsapi_base)。
  2. 後端組裝統一下單參數 → **請求體簽名** → 發起 HTTPS POST /v3/pay/transactions/jsapi。
  3. 微信返回 prepay_id (預支付交易會話標識)。
  4. 後端再次簽名生成 paySign 返回前端 (小程序 wx.requestPayment / JSAPI WeixinJSBridge)。
  5. 用戶確認支付 → 微信異步回調 notify_url → 後端驗簽、更新訂單狀態。

Java 實現核心代碼結構

1. 統一下單請求模型 (對應 JSON 體)


public class UnifiedOrderRequest {
    private String appid;           // 公眾號/小程序 AppID
    private String mchid;           // 商戶號
    private String description;     // 商品描述 (body)
    private String outTradeNo;      // 商戶訂單號 (唯一)
    private String notifyUrl;       // 回調地址
    private Amount amount;          // 金額對象
    private Payer payer;            // 支付者 openid
    // getter/setter 省略
}

public class Amount {
    private int total;              // 總金額,**分** 為單位
    private String currency = "CNY";
    // ...
}

public class Payer {
    private String openid;          // 用戶 openid
}

2. 簽名工具類 (RSA-SHA256 with Private Key)


public class WeChatPaySigner {
    private final PrivateKey merchantPrivateKey; // PKCS#8 載入
    private final String merchantId;             // mch_id
    private final String serialNo;               // 商戶證書序列號

    /** 
     * 構建待簽名字串:
     * HTTP_METHOD\n
     * URL_PATH\n
     * TIMESTAMP\n
     * NONCE_STR\n
     * REQUEST_BODY_JSON\n
     */
    public String sign(String httpMethod, String urlPath, String requestBody) {
        long timestamp = System.currentTimeMillis() / 1000;
        String nonce = UUID.randomUUID().toString().replace("-", "");
        String message = String.join("\n", httpMethod, urlPath, 
                                     String.valueOf(timestamp), nonce, requestBody);
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            byte[] signature = sign.sign();
            return Base64.getEncoder().encodeToString(signature);
        } catch (Exception e) {
            throw new RuntimeException("簽名失敗", e);
        }
    }

    /** 生成 Authorization 標頭 */
    public String buildAuthorization(String httpMethod, String urlPath, String requestBody) {
        String signature = sign(httpMethod, urlPath, requestBody);
        long timestamp = System.currentTimeMillis() / 1000;
        String nonce = UUID.randomUUID().toString().replace("-", "");
        return String.format("WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\"," +
                             "timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"",
                             merchantId, nonce, timestamp, serialNo, signature);
    }
}

3. HTTP 客戶端調用 (OkHttp / HttpClient 5 + 雙向 TLS)


public class WeChatPayClient {
    private final OkHttpClient httpClient; // 預配置雙向 TLS: 憑證 + 私鑰
    private final WeChatPaySigner signer;
    private final String baseUrl = "https://api.mch.weixin.qq.com";

    public UnifiedOrderResponse unifiedOrder(UnifiedOrderRequest req) {
        String urlPath = "/v3/pay/transactions/jsapi";
        String requestBody = toJson(req); // Jackson 序列化
        String authHeader = signer.buildAuthorization("POST", urlPath, requestBody);

        Request request = new Request.Builder()
            .url(baseUrl + urlPath)
            .addHeader("Authorization", authHeader)
            .addHeader("Content-Type", "application/json")
            .addHeader("Accept", "application/json")
            .post(RequestBody.create(requestBody, MediaType.get("application/json")))
            .build();

        try (Response response = httpClient.newCall(request).execute()) {
            String respBody = response.body().string();
            if (!response.isSuccessful()) {
                throw new WeChatPayException("HTTP " + response.code() + ": " + respBody);
            }
            // 驗證回應簽名 (略)
            return fromJson(respBody, UnifiedOrderResponse.class);
        } catch (IOException e) {
            throw new WeChatPayException("網路異常", e);
        }
    }
}

4. 回應模型與前端二次簽名


public class UnifiedOrderResponse {
    private String prepayId;  // 關鍵: 預支付 ID
    // ...
}

// 後端返回給前端的支付參數 (小程序範例)
public class JsapiPayParams {
    private String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
    private String nonceStr = UUID.randomUUID().toString().replace("-", "");
    private String packageValue = "prepay_id=" + prepayId;
    private String signType = "RSA";
    private String paySign;   // 二次簽名

    // 待簽字串: timeStamp\nnonceStr\npackageValue\n
    // 使用同一套簽名工具 signer.signForPay(timeStamp, nonceStr, packageValue)
}

關鍵差異:V2 (XML/MD5) vs V3 (JSON/RSA)

| 特性 | V2 (舊版) | V3 (現行) | |------|-----------|-----------| | 協議 | HTTP/HTTPS | **強制 HTTPS + 雙向 TLS** | | 格式 | XML | **JSON** | | 簽名 | MD5 + API Key | **RSA-SHA256 + 商戶私鑰** | | 密鑰管理 | 單一 API Key | **APIv3 密鑰 + 憑證序列號 + 私鑰/公鑰對** | | 統一下單端點 | /pay/unifiedorder | /v3/pay/transactions/jsapi (按場景分) | | 回調驗簽 | MD5 | **RSA-SHA256 + 平台公鑰** |

常見錯誤與排查

錯誤碼 / 現象原因解決
SYSTEM_ERROR / 500微信端暫時故障指數退避重試 (冪等鍵 out_trade_no 保證不重複扣款)
INVALID_REQUEST 簽名驗證失敗待簽字串組裝順序/換行/編碼錯誤、私鑰格式不符 (PKCS#1 vs PKCS#8)對照官方示例逐字節核對、用 OpenSSL 驗證私鑰
PARAM_ERROR 金額不合法單位不是「分」、或超過限額金額轉換: int totalFen = yuan * 100
回調收不到 / 重複回調防火牆攔截、回調未在 2s 內返回 SUCCESS回調邏輯冪等、異步處理、快速返回 SUCCESS
雙向 TLS 握手失敗證書路徑錯誤、密碼錯誤、未載入平台公鑰用 openssl s_client -connect api.mch.weixin.qq.com:443 -cert ... -key ... 測試

最佳實踐清單

  1. 冪等鍵:out_trade_no 業務唯一,重試不變,防重複扣款。
  2. 金額單位:統一用 **分 (int)** 存儲傳輸,避免浮點誤差。
  3. 異步回調:驗簽 → 查詢訂單狀態 → 更新 DB → 返回 SUCCESS,全程無阻塞。
  4. 敏感資訊不入日誌:openid、prepay_id、簽名值脫敏記錄。
  5. SDK 選型:推薦官方 wechatpay-java SDK (封裝簽名/雙向 TLS/重試),減少造輪子風險。

總結

微信支付 V3 統一下單雖涉及雙向 TLS、RSA 簽名、JSON 協議等較複雜環節,但只要掌握 **「請求體 → 待簽字串 → 私鑰簽名 → Authorization 標頭 → HTTPS POST」** 核心鏈路,配合官方 SDK 與完善的異常處理,即可穩健接入生產環境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved