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

微信 App 支付 Java 服務端完整實現:統一下單、簽名、回調驗證

     編輯:Java 相關知識

業務流程圖解

  1. App 調起微信 SDK → 獲取 prepay_id。
  2. 服務端調用「統一下單」接口 → 返回 prepay_id。
  3. 服務端二次簽名 → 返回給 App (appid, partnerid, prepayid, noncestr, timestamp, package, sign)。
  4. App 唤起微信支付 → 用戶輸入密碼/指紋 → 支付完成。
  5. 微信異步通知服務端回調地址 (notify_url) → 服務端驗證簽名、更新訂單狀態。

核心依賴



  org.apache.httpcomponents
  httpclient
  4.5.14


  org.json
  json
  20231013


統一下單核心代碼


public class WeChatPayService {
    private static final String APPID = "wx0378bf81abfe3d26";
    private static final String MCH_ID = "1252196606";
    private static final String API_KEY = "b8b9c2bbe92d57cc38fde49745056167"; // 商戶平台設置的 Key
    private static final String NOTIFY_URL = "https://your.domain.com/pay/notify";
    private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    public JSONObject createAppOrder(String outTradeNo, String body, double amount) {
        String nonceStr = RandomStringUtils.randomAlphanumeric(32);
        String totalFee = String.valueOf((long) (amount * 100)); // 單位:分

        // 1. 組裝簽名參數 (按 ASCII 排序)
        Map params = new TreeMap<>();
        params.put("appid", APPID);
        params.put("body", body);
        params.put("mch_id", MCH_ID);
        params.put("nonce_str", nonceStr);
        params.put("notify_url", NOTIFY_URL);
        params.put("out_trade_no", outTradeNo);
        params.put("spbill_create_ip", "127.0.0.1"); // 實際應獲取客戶端真實 IP
        params.put("total_fee", totalFee);
        params.put("trade_type", "APP");

        String sign = generateSign(params, API_KEY);
        params.put("sign", sign);

        // 2. 轉 XML 請求體
        String xml = mapToXml(params);

        // 3. 發送 HTTPS POST
        String responseXml = HttpClientUtil.postXml(UNIFIED_ORDER_URL, xml);

        // 4. 解析響應
        Map result = xmlToMap(responseXml);
        if (!"SUCCESS".equals(result.get("return_code")) || !"SUCCESS".equals(result.get("result_code"))) {
            throw new RuntimeException("下單失敗: " + result.get("err_code_des"));
        }

        // 5. 二次簽名返回給 App
        String prepayId = result.get("prepay_id");
        Map appParams = new TreeMap<>();
        appParams.put("appid", APPID);
        appParams.put("partnerid", MCH_ID);
        appParams.put("prepayid", prepayId);
        appParams.put("package", "Sign=WXPay");
        appParams.put("noncestr", nonceStr);
        appParams.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String appSign = generateSign(appParams, API_KEY);
        appParams.put("sign", appSign);

        return new JSONObject(appParams);
    }

    // MD5 簽名 (按 key 排序拼接 key=value&...&key=API_KEY)
    private String generateSign(Map params, String apiKey) {
        StringBuilder sb = new StringBuilder();
        params.forEach((k, v) -> { if (v != null && !v.isEmpty()) sb.append(k).append('=').append(v).append('&'); });
        sb.append("key=").append(apiKey);
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }
    // mapToXml / xmlToMap / HttpClientUtil 省略,建議用成熟 XML 工具 (dom4j/jaxb)
}

支付回調驗證 (Controller)


@PostMapping("/pay/notify")
public String notify(@RequestBody String xmlBody) {
    Map params = XmlUtil.xmlToMap(xmlBody);
    // 1. 驗證 return_code & result_code 為 SUCCESS
    if (!"SUCCESS".equals(params.get("return_code")) || !"SUCCESS".equals(params.get("result_code"))) {
        return XmlUtil.mapToXml(Map.of("return_code", "FAIL", "return_msg", "業務失敗"));
    }
    // 2. 驗證簽名
    String sign = params.remove("sign");
    if (!sign.equals(WeChatPayService.generateSign(params, API_KEY))) {
        return XmlUtil.mapToXml(Map.of("return_code", "FAIL", "return_msg", "簽名驗證失敗"));
    }
    // 3. 業務幂等性處理 (防重複通知)
    String outTradeNo = params.get("out_trade_no");
    if (orderService.isPaid(outTradeNo)) {
        return XmlUtil.mapToXml(Map.of("return_code", "SUCCESS", "return_msg", "OK"));
    }
    // 4. 更新訂單狀態、發送 MQ 等
    orderService.markPaid(outTradeNo, params.get("transaction_id"));
    return XmlUtil.mapToXml(Map.of("return_code", "SUCCESS", "return_msg", "OK"));
}

安全與最佳實踐

  1. API Key 極度機密:存放在配置中心/密碼管理器,**嚴禁硬編碼、提交代碼倉庫**。
  2. 簽名算法升級:微信已推廣 HMAC-SHA256 (sign_type=HMAC-SHA256),新接入優先使用。
  3. 幂等性:回調可能重複,基於 out_trade_no 或 transaction_id 去重。
  4. 金額一致性:回調中核對 total_fee 與訂單金額一致。
  5. IP 白名單:服務端驗證回調來源 IP 是否為微信官方段 (可選)。
  6. 使用官方 SDK:wechat-pay-java-sdk 封裝了簽名、加密、證書管理,強烈建議生產環境直接用 SDK。

總結

微信 App 支付核心在於「統一下單 → 二次簽名 → 回調驗證」三步。掌握 XML 組裝、MD5/HMAC-SHA256 簽名、幂等性處理,配合官方 SDK,即可構建安全可靠的支付服務端。

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