最近要做一個微信小程式,需要微信支付,所以研究了下怎麼在 java 上整合微信支付功能,特此記錄下。
本文完整程式碼:點選跳轉
這裡比較麻煩,需要認真點。
目前微信支付的 api 有 V2 和 V3 兩個版本,V2 是 xml 的資料結構不建議用了,很麻煩(雖然 V3 也不簡單).
以下內容全部基於微信支付 V3 的版本
你需要獲取如下東西:
由於支付屬於比較敏感的操作,所以建議將引數設定放在後臺,前端請求獲取到引數後直接調起微信支付。
整個微信支付流程如下:
實際開發中,小程式端的開發內容很少,98%的工作量都在後臺。
雖然微信官方目前沒有正式放出官方 sdk,但是 github 上已經有了 sdk 的倉庫,目前正在開發中,地址.這個工具做了一定程度的封裝,極大簡化了加密解密證書設定等繁瑣的操作。
和微信的請求需要做雙向加密,因此要在系統啟動時建立一個專用的 httpClient,用來呼叫微信支付 api.程式碼如下:
@PostConstruct
public void init() throws Exception {
log.info("私鑰路徑:{}", certKeyPath);
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(certKeyPath));
// 獲取證書管理器範例
certificatesManager = CertificatesManager.getInstance();
sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA, merchantPrivateKey.getEncoded(), null);
// 向證書管理器增加需要自動更新平臺證書的商戶資訊
certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
// 從證書管理器中獲取verifier
Verifier verifier = certificatesManager.getVerifier(merchantId);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
httpClient = builder.build();
}
程式碼如下:
JSONObject obj = new JSONObject();
obj.put("mchid", merchantId);
obj.put("appid", appId);
obj.put("description", body.getDescription());
obj.put("out_trade_no", body.getSn());
obj.put("notify_url", "https://backend/asdfasdf/notify");
Map<String, String> attach = new HashMap<>();
attach.put("sn", body.getSn());
obj.put("attach", JSON.toJSONString(attach));
JSONObject amount = new JSONObject();
amount.put("total", body.getPrice().multiply(BigDecimal.valueOf(100)).intValue());
obj.put("amount", amount);
JSONObject payer = new JSONObject();
//放入使用者的openId
payer.put("openid", "");
obj.put("payer", payer);
log.info("請求引數為:" + JSON.toJSONString(obj));
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(obj.toJSONString(), "UTF-8"));
try {
CloseableHttpResponse response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
String prePayId = JSONObject.parseObject(bodyAsString).getString("prepay_id");
//準備小程式端的請求引數
Map<String, String> map = new HashMap<>(6);
map.put("appId", appId);
String timeStamp = String.valueOf(now / 1000);
map.put("timeStamp", timeStamp);
String nonceStr = IdUtil.fastSimpleUUID();
map.put("nonceStr", nonceStr);
String packageStr = "prepay_id=" + prePayId;
map.put("package", packageStr);
map.put("signType", "RSA");
// 注意\n必須新增
map.put("paySign", Base64.encode(sign.sign(appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n")));
return map;
} catch (IOException e) {
throw new RuntimeException(e);
}
注意在生成小程式請求引數 paySign 時,\n 必須新增
小程式端獲取請求引數後,直接呼叫wx.requestPayment(後臺返回的引數)
,即可調起支付
微信支付成功後,會通知伺服器端支付成功,通過之前設定的回撥介面。注意回撥介面必須為 https。
微信回撥引數也是加密的,必須要經過解密後才能獲取,程式碼如下:
注意:部分引數是通過請求頭提供的,nginx 等代理在轉發請求時可能會將請求頭過濾掉,導致無法獲取對應引數
@Override
public void notify(HttpServletRequest servletRequest) {
String timeStamp = servletRequest.getHeader("Wechatpay-Timestamp");
String nonce = servletRequest.getHeader("Wechatpay-Nonce");
String signature = servletRequest.getHeader("Wechatpay-Signature");
String certSn = servletRequest.getHeader("Wechatpay-Serial");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(servletRequest.getInputStream()))) {
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String obj = stringBuilder.toString();
log.info("回撥資料:{},{},{},{},{}", obj, timeStamp, nonce, signature, certSn);
Verifier verifier = certificatesManager.getVerifier(merchantId);
String sn = verifier.getValidCertificate().getSerialNumber().toString(16).toUpperCase(Locale.ROOT);
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(sn)
.withNonce(nonce)
.withTimestamp(timeStamp)
.withSignature(signature)
.withBody(obj)
.build();
NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
// 驗籤和解析請求體
Notification notification = handler.parse(request);
JSONObject res = JSON.parseObject(notification.getDecryptData());
//做一些操作
JSONObject attach = res.getJSONObject("attach");
} catch (Exception e) {
log.error("微信支付回撥失敗", e);
}
}
以上只有關鍵的支付和回撥介面範例,其他的介面請求也是類似的,就不一一列出。