对于一个从未接触过微信支付的小白来说,开发中处处受挫,开发中主要遇到的问题一是微信支付平台序列号是要自己通过接口去拿取的以及等等的百度。以下是自己开发中遇到的真实经历,有点菜,请各位大佬多多指点
1.商户平台部署:开通商家转账到零钱功能。若要可以正常使用则需要产品设置设置调用的ip以及api发起勾选,还需设置用户的转账额度等信息,模式为为0的是发起转账是转不出去的
2.api文档开发
商家转账到零钱-文档中心-微信支付商户平台https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
以下主要是生成token的工具类直接套用即可
public class VechatPayV3Util {
private final static String SINGINSTANCE = "SHA256withRSA";
private final static String CIPHERINSTANCE = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
/**
* @param method 请求方法 post
* @param canonicalUrl 请求地址
* @param body 请求参数
* @param merchantId 这里用的商户号
* @param certSerialNo 商户证书序列号
* @param privateKey 商户V3私钥
* @return
* @throws Exception
*/
public static String getTokens(String method, String canonicalUrl, String body, String merchantId, String certSerialNo,
String privateKey) throws Exception {
//获取32位随机字符串
String nonceStr = StringUtils.getSalt(32);
//当前系统运行时间
long timestamp = System.currentTimeMillis() / 1000;
//签名操作
String message = buildMessage(method, canonicalUrl, timestamp, nonceStr, body);
//签名操作
String signature = sign(message.getBytes(StandardCharsets.UTF_8), privateKey);
//组装参数
String strToken= "mchid=\"" + merchantId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + certSerialNo + "\","
+ "signature=\"" + signature + "\"";
return strToken;
}
public static String buildMessage(String method, String canonicalUrl, long timestamp, String nonceStr, String body) {
HttpUrl httpurl = HttpUrl.parse(canonicalUrl);
canonicalUrl = httpurl.encodedPath();
if (httpurl.encodedQuery() != null) {
canonicalUrl += "?" + httpurl.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
public static String sign(byte[] message, String privateKey) throws Exception {
Signature sign = Signature.getInstance(SINGINSTANCE);
sign.initSign(loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8))));
sign.update(message);
return java.util.Base64.getEncoder().encodeToString(sign.sign());
}
public static PrivateKey loadPrivateKey(InputStream inputStream) {
try {
ByteArrayOutputStream array = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
array.write(buffer, 0, length);
}
String privateKey = array.toString("utf-8")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
} catch (IOException e) {
throw new RuntimeException("无效的密钥");
}
}
/***
*加密
* @param message
* @param certificate
* @return
* @throws IllegalBlockSizeException
* @throws IOException
*/
public static String rsaEncryptOAEP(String message, X509Certificate certificate)
throws IllegalBlockSizeException, IOException {
try {
Cipher cipher = Cipher.getInstance(CIPHERINSTANCE);
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] cipherdata = cipher.doFinal(data);
return java.util.Base64.getEncoder().encodeToString(cipherdata);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("无效的证书", e);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
}
}
/**
* 解密
*
* @param ciphertext
* @param privateKey
* @return
* @throws BadPaddingException
* @throws IOException
*/
public static String rsaDecryptOAEP(String ciphertext, PrivateKey privateKey)
throws BadPaddingException {
try {
Cipher cipher = Cipher.getInstance(CIPHERINSTANCE);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] data = Base64.decodeBase64(ciphertext);
return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("无效的私钥", e);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new BadPaddingException("解密失败");
}
}
/**
* 获得微信支付平台证书
* @param inputStream
* @return
*/
public static X509Certificate loadCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
还需拿取微信平台证书序列号,,该信息是需要通过接口去拿取的,请求头部需要使用
签名验证-接口规则 | 微信支付商户平台文档中心https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
String merchantId = "";商户号
String v3PrivateKey ="";v3密钥
String privateKey ="";api证书私钥
String merchantSerino ="";商户证书序列号
PrivateKey merchantPrivateKey = VechatPayV3Util.loadPrivateKey(new ByteArrayInputStream("V3密钥".getBytes(StandardCharsets.UTF_8)));
//使用自动更新的签名验证器,不需要传入证书
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId , new PrivateKeySigner(merchantSerino, merchantPrivateKey)),
privateKey.getBytes(StandardCharsets.UTF_8));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId , merchantSerino , merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
logger.debug("查询微信平台证书列表请求地址:{}","https://api.mch.weixin.qq.com/v3/certificates");
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/certificates");
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
String str = EntityUtils.toString(entity);
返回信息里面就有你需要的微信平台序列号
若需要得到微信平台证书的公钥也可以使用里面的数据进行解密即可
公钥主要是对用户姓名等私密信息进行加密操作的工具类里面有说明
AesUtil aesKey = new AesUtil(v3PrivateKey.getBytes(StandardCharsets.UTF_8));
//得到微信支付平台公钥(此公钥和微信商户支付公钥不一样)
String decrypt = aesKey.decryptToString(associatedData.toString().getBytes(StandardCharsets.UTF_8),
nonce.toString().getBytes(StandardCharsets.UTF_8), ciphertext.toString());
}catch (Exception e){
e.printStackTrace();
} finally {
response.close();
httpClient.close();
}
部署好上面的信息就可以进行转账了
CloseableHttpResponse response = null;
try {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/transfer/batches");
httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
httpPost.addHeader("Wechatpay-Serial", "微信平台证书序列号");
String requestData = JSONObject.toJSONString(map);
String token = VechatPayV3Util.getTokens(RequestMethod.POST.toString(), "https://api.mch.weixin.qq.com/v3/transfer/batches",
"请求参数", "商户号", "商户序列号","商户私钥");
logger.info("生成的token为:{}", token);
httpPost.addHeader(AUTHORIZATION, "WECHATPAY2-SHA256-RSA2048"+ " " + token);
httpPost.setEntity(new StringEntity(requestData, StandardCharsets.UTF_8));
response = httpclient.execute(httpPost);
HttpEntity entity = response.getEntity();
resultData = EntityUtils.toString(entity, StandardCharsets.UTF_8);
logger.info("商家转账返回的数据为:{}", resultData);
} catch (Exception e) {
e.printStackTrace();
} finally {
response.close();
}
返回如下信息就说明接口已经调试成功了
{ "out_batch_no": "53456454545445", "batch_id": "10300000711009999911820200507000194832401", "create_time": "2022-05-20T13:29:35.120+08:00" }
友情提示:该接口并不能说明转账成功,只能说明发起了转账,要转账成功还需通过调用明细接口来保证他的状态