在此講明,此回調文檔,可通用於微信支付分的所有回調,在此就只寫一次回調為例!!!!!!!!
一.在做授權回調通知我們需要了解以下要求
1.商戶系統對於服務授權/解除授權結果通知的內容一定要做簽名驗證,並校驗通知的信息是否與商戶側的信息一致,防止數據泄漏導致出現“假通知”,造成資金損失。(簽名)
2.該鏈接是通過聯系微信支付運營人員配置[商戶入駐配置申請表]提交service_notify_url設置,必須為https協議。如果鏈接無法訪問,商戶將無法接收到微信通知。 通知url必須為直接可訪問的url,不能攜帶參數。示例: “https://pay.weixin.qq.com/wxpay/pay.action”(配置的回調地址,配置好,微信會自動回調)
3.同時還需要設置APIV3密鑰
4.服務授權/解除授權結果通知是以POST方法訪問商戶設置的通知url,通知的數據以JSON格式通過請求主體(BODY)傳輸。通知的數據包括了加密的授權/解除授權結果詳情。(對返回的數據進行解密)
/** * 授權/解除授權服務回調 * * @param params * @return */ @RequestMapping(value = "/xx/impowerBack.do", method = RequestMethod.POST) @SuppressWarnings("unchecked") public String weixinCallback(@RequestBody Map<String, Object> params) { LOGGER.info("======================================進入授權/解除授權服務回調"); JSONObject resource = new JSONObject(params.get("resource")); //獲得驗簽字符串 Map<String, String> certByAPI = null; try { certByAPI = Sign.getCertByAPIs(商戶MCHID, “"https://api.mch.weixin.qq.com/v3/certificates"”, 10, null, 商戶MCHSERIALNO, "D:\\xx\\xx\\apiclient_key.pem私鑰地址"); } catch (Exception e) { e.printStackTrace(); } //拼接驗簽字符串 String signStr = certByAPI.get("Wechatpay-Timestamp") + "\n" + certByAPI.get("Wechatpay-Nonce") + "\n" + certByAPI.get("result") + "\n"; //驗簽 boolean verify = Sign.verify(signStr, certByAPI.get("Wechatpay-Signature"), "導出的公鑰路徑.txt"); System.out.println("======================================verify=" + verify); if (verify == true) { try { String data = AesUtil.decryptToString("商戶設置Api3密鑰".getBytes(), resource.get("associated_data").toString().getBytes(), resource.get("nonce").toString().getBytes(), resource.get("ciphertext").toString()); } catch (GeneralSecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } return ""; }
public static Map<String, String> getCertByAPIs(String merchantId, String url, int timeout, String body, String certSerialNo, String keyPath) throws UnsupportedEncodingException, Exception { String result = ""; //創建http請求 HttpGet httpGet = new HttpGet(url); httpGet.addHeader("Content-Type", "application/json"); httpGet.addHeader("Accept", "application/json"); //設置認證信息 httpGet.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + getToken("GET", url, null, merchantId, certSerialNo, keyPath)); //設置請求器配置:如超時限制等 RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build(); httpGet.setConfig(config); List<X509Certificate> x509Certs = new ArrayList<X509Certificate>(); try { CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpGet); Header[] allHeaders = response.getAllHeaders(); Map<String, String> headers = new HashMap<String, String>(); for (int i = 0; i < allHeaders.length; i++) { String key = (String) allHeaders[i].getName(); String value = allHeaders[i].getValue(); headers.put(key, value); } String Nonce = headers.get("Wechatpay-Nonce"); String Signature = headers.get("Wechatpay-Signature"); String Timestamp = headers.get("Wechatpay-Timestamp"); System.out.println(Timestamp); int statusCode = response.getStatusLine().getStatusCode(); HttpEntity httpEntity = response.getEntity(); result = EntityUtils.toString(httpEntity, "UTF-8"); headers.put("result", result); //String result1 = Timestamp + "\n" + Nonce + "\n" + result + "\n"; System.out.println("下載平台證書返回結果:" + result); return headers; } catch (Exception e) { e.printStackTrace(); LOGGER.error("下載平台證書返回結果:" + e); } return null; }
//method(請求類型GET、POST url(請求url) body(請求body,GET請求時body傳"",POST請求時body為請求參數的json串) merchantId(商戶號) certSerialNo(API證書序列號) keyPath(API證書路徑) public static String getToken(String method, String url, String body, String merchantId, String certSerialNo, String keyPath) throws Exception { String signStr = ""; HttpUrl httpurl = HttpUrl.parse(url); // String nonceStr = getNonceStr(); String nonceStr = "隨機字符串"; long timestamp = System.currentTimeMillis() / 1000; if (StringUtils.isEmpty(body)) { body = ""; } String message = buildMessage(method, httpurl, timestamp, nonceStr, body); String signature = sign(message.getBytes("utf-8"), keyPath); signStr = "mchid=\"" + merchantId + "\",nonce_str=\"" + nonceStr + "\",timestamp=\"" + timestamp + "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\""; LOGGER.info("Authorization Token:" + signStr); System.out.println("signStr:--" + signStr); return signStr; }
public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { String canonicalUrl = url.encodedPath(); if (url.encodedQuery() != null) { canonicalUrl += "?" + url.encodedQuery(); } return method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body + "\n"; }
public static String sign(byte[] message, String keyPath) throws Exception { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(getPrivateKey(keyPath)); sign.update(message); return Base64.encodeBase64String(sign.sign()); }
/** * 驗簽 * * @param srcData * @param signedData * @param publicKeyPath * @return */ public static boolean verify(String srcData, String signedData, String publicKeyPath) { if (srcData == null || signedData == null || publicKeyPath == null) { return false; } try { PublicKey publicKey = readPublic(publicKeyPath); Signature sign = Signature.getInstance(ALGORITHM); sign.initVerify(publicKey); sign.update(srcData.getBytes(CHARSET_ENCODING)); System.out.println("publicKey = " + publicKey); return sign.verify(java.util.Base64.getDecoder().decode(signedData)); } catch (Exception e) { System.out.println("e = " + e); e.printStackTrace(); } return false; }
/** * 讀取公鑰 * * @param publicKeyPath * @return */ private static PublicKey readPublic(String publicKeyPath) { if (publicKeyPath == null) { return null; } PublicKey pk = null; FileInputStream bais = null; try { CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509"); bais = new FileInputStream(publicKeyPath); X509Certificate cert = (X509Certificate) certificatefactory.generateCertificate(bais); pk = cert.getPublicKey(); } catch (CertificateException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (bais != null) { try { bais.close(); } catch (IOException e) { e.printStackTrace(); } } } return pk; }
獲取公鑰代碼
public static List<X509Certificate> getCertByAPI(String merchantId, String url, int timeout, String body, String certSerialNo, String keyPath) throws UnsupportedEncodingException, Exception { String result = ""; //創建http請求 HttpGet httpGet = new HttpGet(url); httpGet.addHeader("Content-Type", "application/json"); httpGet.addHeader("Accept", "application/json"); //設置認證信息 httpGet.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + getToken("GET", url, null, merchantId, certSerialNo, keyPath)); //設置請求器配置:如超時限制等 RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build(); httpGet.setConfig(config); List<X509Certificate> x509Certs = new ArrayList<X509Certificate>(); try { CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); HttpEntity httpEntity = response.getEntity(); result = EntityUtils.toString(httpEntity, "UTF-8"); if (statusCode == 200) { LOGGER.info("下載平台證書返回結果:" + result); List<CertificateItem> certList = new ArrayList<CertificateItem>(); JSONObject json = JSONObject.parseObject(result); LOGGER.info("查詢結果json字符串轉證書List:" + json.get("data")); JSONArray jsonArray = (JSONArray) json.get("data"); for (int i = 0; i < jsonArray.size(); i++) { CertificateItem certificateItem = new CertificateItem(); EncryptedCertificateItem encryptCertificate = new EncryptedCertificateItem(); JSONObject bo = JSONObject.parseObject(jsonArray.get(i).toString()); certificateItem.setSerial_no(bo.get("serial_no").toString()); certificateItem.setEffective_time(bo.get("effective_time").toString()); certificateItem.setExpire_time(bo.get("expire_time").toString()); JSONObject encryptBo = JSONObject.parseObject(bo.get("encrypt_certificate").toString()); encryptCertificate.setAlgorithm(encryptBo.get("algorithm").toString()); encryptCertificate.setNonce(encryptBo.get("nonce").toString()); encryptCertificate.setAssociated_data(encryptBo.get("associated_data").toString()); encryptCertificate.setCiphertext(encryptBo.get("ciphertext").toString()); certificateItem.setEncrypt_certificate(encryptCertificate); certList.add(certificateItem); } LOGGER.info("證書List:" + certList); List<PlainCertificateItem> plainList = decrypt(certList, response); if (CollectionUtils.isNotEmpty(plainList)) { LOGGER.info("平台證書開始保存"); //x509Certs = saveCertificate(plainList); } } response.close(); httpClient.close(); //throw return x509Certs; } catch (Exception e) { e.printStackTrace(); LOGGER.error("下載平台證書返回結果:" + e); } return x509Certs; }
private static List<PlainCertificateItem> decrypt(List<CertificateItem> certList, CloseableHttpResponse response) throws GeneralSecurityException, IOException { List<PlainCertificateItem> plainCertificateList = new ArrayList<PlainCertificateItem>(); AesUtil aesUtil = new AesUtil(("Api3密鑰").getBytes(StandardCharsets.UTF_8)); for (CertificateItem item : certList) { PlainCertificateItem bo = new PlainCertificateItem(); bo.setSerialNo(item.getSerial_no()); bo.setEffectiveTime(item.getEffective_time()); bo.setExpireTime(item.getExpire_time()); LOGGER.info("平台證書密文解密"); bo.setPlainCertificate(aesUtil.decryptToString(item.getEncrypt_certificate().getAssociated_data().getBytes(StandardCharsets.UTF_8), item.getEncrypt_certificate().getNonce().getBytes(StandardCharsets.UTF_8), item.getEncrypt_certificate().getCiphertext())); LOGGER.info("平台證書公鑰明文:" + bo.getPlainCertificate()); System.out.println("平台證書公鑰明文:" + bo.getPlainCertificate()); plainCertificateList.add(bo); } return plainCertificateList; }
對微信返回數據進行解密
/** * 對加密的授權/解除授權結果進行解密 * * @param * @return */ public class AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; public static String decryptToString(byte[] aesKey,byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { if (aesKey.length != KEY_LENGTH_BYTE) { throw new IllegalArgumentException("無效的ApiV3Key,長度必須為32個字節"); } try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.updateAAD(associatedData); return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8"); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); } } }