微信支付是中國市場不可或缺的支付方式。本文基於微信支付 V3 版 API (JSAPI/APP/NATIVE/H5),梳理服務端核心流程:統一下單 → 簽名驗證 → 支付通知回調 → 退款/查單,並提供 Spring Boot 整合範例、關鍵工具類與安全性建議。註:原始碼片段為 V2 風格,本文升級為 V3 規範。
cert/ 目錄)。Authorization: WECHATPAY2-SHA256-RSA2048 ...。resource.ciphertext (AEAD_AES_256_GCM) 並驗證簽名。// 以 JSAPI (小程序/公眾號) 為例
WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
request.setAppid(appId);
request.setMchid(mchId);
request.setDescription("商品描述 - 測試訂單");
request.setOutTradeNo(orderNo); // 商戶訂單號,唯一
request.setNotifyUrl("https://api.yourdomain.com/pay/notify"); // 回調地址 (HTTPS, 443)
request.setAmount(new Amount().setTotal(1).setCurrency("CNY")); // 單位:分
request.setPayer(new Payer().setOpenid(userOpenId)); // JSAPI 必填
// 可選:scene_info, detail, goods_tag 等
使用官方 wechatpay-java SDK (含自動簽名、驗證、解密):
@Autowired
private WxPayService wxPayService; // 封裝了 WxPayClient
public String createJsapiOrder(WxPayUnifiedOrderRequest request) {
try {
WxPayUnifiedOrderResponse response = wxPayService.createOrderV3(request);
String prepayId = response.getPrepayId();
// 2.3 為前端生成二次簽名
return generateJsapiPaySign(prepayId);
} catch (Exception e) {
log.error("下單失敗", e);
throw new BizException("下單失敗: " + e.getMessage());
}
}
private String generateJsapiPaySign(String prepayId) {
long timeStamp = System.currentTimeMillis() / 1000;
String nonceStr = WxPayUtil.generateNonceStr();
String message = appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + "prepay_id=" + prepayId + "\n";
String paySign = WxPayUtil.signWithPrivateKey(message, merchantPrivateKey); // RSA2048
// 返回給前端: appId, timeStamp, nonceStr, package="prepay_id=...", signType="RSA", paySign
return buildJsonResponse(appId, timeStamp, nonceStr, "prepay_id=" + prepayId, paySign);
}
@PostMapping("/pay/notify")
public ResponseEntity handleNotify(@RequestHeader Map headers,
@RequestBody String body) {
try {
// 1. 驗證簽名 (SDK 內建)
// 2. 解密資料
WxPayNotifyResponse notify = wxPayService.parseNotify(body, headers);
String outTradeNo = notify.getOutTradeNo();
String transactionId = notify.getTransactionId();
String tradeState = notify.getTradeState(); // SUCCESS
if ("SUCCESS".equals(tradeState)) {
// 3. 業務邏輯:更新訂單狀態、發送通知、發貨等 (冪等性處理!)
orderService.handlePaySuccess(outTradeNo, transactionId);
}
// 4. 回應微信 (必須在 5 秒內返回 SUCCESS,否則會重試)
return ResponseEntity.ok().build();
} catch (Exception e) {
log.error("回調處理異常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
out_trade_no 或 transaction_id 做唯一索引,防止重複處理。Wechatpay-Serial 對應的平台公鑰,防止偽造通知。public class WxPayUtil {
// 生成隨機字串
public static String generateNonceStr() { return UUID.randomUUID().toString().replace("-", ""); }
// 簽名 (RSA2048 with SHA256)
public static String signWithPrivateKey(String data, PrivateKey privateKey) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
}
// 驗證簽名
public static boolean verifySign(String data, String signBase64, PublicKey publicKey) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(publicKey);
sign.update(data.getBytes(StandardCharsets.UTF_8));
return sign.verify(Base64.getDecoder().decode(signBase64));
}
// 解密回調資料 (AEAD_AES_256_GCM)
public static String decryptCallback(String associatedData, String nonce, String ciphertext, String apiV3Key) {
// 使用 javax.crypto.Cipher "AES/GCM/NoPadding" 實作,參考官方文件
}
}
POST /v3/refund/domestic/refunds,需 out_refund_no, out_trade_no, amount, notify_url。同樣有回調通知。GET /v3/pay/transactions/id/{transaction_id} 或 /v3/pay/transactions/out-trade-no/{out_trade_no}。POST /v3/pay/transactions/out-trade-no/{out_trade_no}/close (僅未支付訂單)。微信支付 V3 雖然簽名/加密流程較 V2 複雜,但安全性大幅提升。善用官方 wechatpay-java SDK 可屏蔽大部分底層細節,讓開發者專注業務邏輯。完整 Spring Boot 整合專案已開源至 GitHub (連結略),歡迎參考貢獻。