接口定位
適用於:前端輪詢支付結果、後端定時任務對賬、退款前確認訂單狀態、支付超時關單前查詢。
請求參數
| 字段 | 必填 | 說明 |
| transaction_id | 二選一 | 微信支付訂單號 |
| out_trade_no | 二選一 | 商戶系統訂單號 |
| nonce_str | 是 | 隨機字符串 |
核心實現代碼
@Controller
@RequestMapping("/shop/weiXinPayOrderSearch")
public class WeiXinPayOrderSearchController {
@Autowired WeiXinPayService weiXinPayService;
@RequestMapping(value = "/getWeiXinPayOrder", method = RequestMethod.POST)
@ResponseBody
public Object queryOrder(HttpServletRequest request,
@RequestParam(required = false) String transactionId,
@RequestParam(required = false) String outTradeNo) {
// 1. 參數校驗
if (StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo)) {
return error("transaction_id 與 out_trade_no 至少填一個");
}
// 2. 構造請求 XML
String nonceStr = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String xml = ClientCustomSSL.SearchNativePackage(
StringUtils.trimToEmpty(transactionId),
StringUtils.trimToEmpty(outTradeNo),
nonceStr
);
// 3. 雙向 HTTPS 發送
try (CloseableHttpClient client = buildHttpClient()) {
HttpPost post = new HttpPost("https://api.mch.weixin.qq.com/pay/orderquery");
post.setEntity(new StringEntity(xml, "UTF-8"));
try (CloseableHttpResponse resp = client.execute(post)) {
Document doc = new SAXReader().read(resp.getEntity().getContent());
Element root = doc.getRootElement();
String returnCode = root.elementText("return_code");
String resultCode = root.elementText("result_code");
// 4. 通信層失敗
if (!"SUCCESS".equals(returnCode)) {
return error("通信失敗: " + root.elementText("return_msg"));
}
// 5. 業務層失敗
if (!"SUCCESS".equals(resultCode)) {
return error("業務失敗: " + root.elementText("err_code_des"),
"errCode", root.elementText("err_code"));
}
// 6. 成功解析關鍵字段
JSONObject result = new JSONObject();
result.put("tradeState", root.elementText("trade_state")); // SUCCESS/REFUND/NOTPAY/CLOSED/REVOKED/USERPAYING/PAYERROR
result.put("transactionId", root.elementText("transaction_id"));
result.put("outTradeNo", root.elementText("out_trade_no"));
result.put("totalFee", root.elementText("total_fee")); // 分
result.put("status", "success");
return result;
}
} catch (Exception e) {
log.error("查詢訂單異常", e);
return error("系統異常: " + e.getMessage());
}
}
private CloseableHttpClient buildHttpClient() throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
try (FileInputStream fis = new FileInputStream(certPath)) {
ks.load(fis, mchId.toCharArray());
}
SSLContext ssl = SSLContexts.custom()
.loadKeyMaterial(ks, mchId.toCharArray())
.build();
return HttpClients.custom()
.setSSLSocketFactory(new SSLConnectionSocketFactory(ssl,
new String[]{"TLSv1"}, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER))
.build();
}
private JSONObject error(String msg, String... extra) {
JSONObject r = new JSONObject();
r.put("status", "error");
r.put("msg", msg);
for (int i = 0; i < extra.length; i += 2) r.put(extra[i], extra[i+1]);
return r;
}
}
trade_state 狀態機說明
| 狀態值 | 含義 | 建議動作 |
| SUCCESS | 支付成功 | 更新本地訂單為已支付,發貨/發碼 |
| REFUND | 已退款 | 標記退款完成 |
| NOTPAY | 未支付 | 繼續輪詢或引導用戶支付 |
| CLOSED | 已關單 | 標記關閉,可重新下單 |
| REVOKED | 已撤銷 | 同 CLOSED |
| USERPAYING | 用戶支付中 | 繼續輪詢 |
| PAYERROR | 支付失敗 | 記錄失敗原因,引導重試 |
最佳實踐建議
- 優先用 out_trade_no 查詢:自主可控,不依賴微信單號。
- 輪詢策略:前端 3s/次,最長 2 分鐘;後端定時任務每 10 分鐘掃描 24 小時內未完成訂單。
- 幂等處理:查詢到 SUCCESS 後更新訂單狀態需加樂觀鎖或狀態機判斷,防重複發貨。
- 異常重試:網絡超時/5xx 錯誤指數退避重試 3 次,記錄失敗告警。
- 日誌留存:完整請求/響應 XML 入庫(脫敏),便於對賬溯源。
常見錯誤碼速查
SYSTEMERROR 系統錯誤,重試
APPID_NOT_EXIST AppId 不存在,檢查配置
MCHID_NOT_EXIST MchId 不存在,檢查配置
OUT_TRADE_NO_USED 商戶訂單號重複,換單號
SIGNERROR 簽名錯誤,核對 Key/參數排序
XML_FORMAT_ERROR XML 格式錯誤,檢查特殊字符轉義
REQUIRE_POST_METHOD 請求方法非 POST