业务介绍
微信付款到银行卡,也就是转账。需要将银行卡号、卡主真实姓名按照微信支付制定的加密规则进行加密,(开发完感觉加密比转账复杂,哈哈哈)。加密的秘钥,也就是钥匙,也要通过请求向微信获取,重点是获取到之后,并不能直接使用。还要进行两次处理。(太难了)。最后用处理好的秘钥,对银行卡号、姓名进行加密。发送转账请求就完成了。
前置工作
证书(apiclient_cert.p12)、商户号(mch_id)、secret。
工具类
加密用到的工具类(常量PKCS8_PUBLIC需要改成自己的,后面会讲)
public class RSAUtils {
/**
* rsa加密公钥
* 请求微信api获得pkcs1格式
* 通过转换工具转换成pkcs8格式
*/
private static final String PKCS8_PUBLIC ="";
public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
int keyByteSize = keyLength / 8;
int decryptBlockSize = keyByteSize - reserveSize;
int nBlock = encryptedBytes.length / keyByteSize;
ByteArrayOutputStream outbuf = null;
try {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
int inputLen = encryptedBytes.length - offset;
if (inputLen > keyByteSize) {
inputLen = keyByteSize;
}
byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
outbuf.write(decryptedBlock);
}
outbuf.flush();
return outbuf.toByteArray();
} catch (Exception e) {
throw new Exception("DEENCRYPT ERROR:", e);
} finally {
try{
if(outbuf != null){
outbuf.close();
}
}catch (Exception e){
outbuf = null;
throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
}
}
}
public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
int keyByteSize = keyLength / 8;
int encryptBlockSize = keyByteSize - reserveSize;
int nBlock = plainBytes.length / encryptBlockSize;
if ((plainBytes.length % encryptBlockSize) != 0) {
nBlock += 1;
}
ByteArrayOutputStream outbuf = null;
try {
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
int inputLen = plainBytes.length - offset;
if (inputLen > encryptBlockSize) {
inputLen = encryptBlockSize;
}
byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
outbuf.write(encryptedBlock);
}
outbuf.flush();
return outbuf.toByteArray();
} catch (Exception e) {
throw new Exception("ENCRYPT ERROR:", e);
} finally {
try{
if(outbuf != null){
outbuf.close();
}
}catch (Exception e){
outbuf = null;
throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
}
}
}
public static PrivateKey getPriKey(String privateKeyPath,String keyAlgorithm){
PrivateKey privateKey = null;
InputStream inputStream = null;
try {
if(inputStream==null){
System.out.println("hahhah1!");
}
inputStream = new FileInputStream(privateKeyPath);
System.out.println("hahhah2!");
privateKey = getPrivateKey(inputStream,keyAlgorithm);
System.out.println("hahhah3!");
} catch (Exception e) {
System.out.println("加载私钥出错!");
} finally {
if (inputStream != null){
try {
inputStream.close();
}catch (Exception e){
System.out.println("加载私钥,关闭流时出错!");
}
}
}
return privateKey;
}
/*public static PublicKey getPubKey(String publicKeyPath,String keyAlgorithm){
PublicKey publicKey = null;
InputStream inputStream = null;
try
{
System.out.println("getPubkey 1......");
inputStream = new FileInputStream(publicKeyPath);
System.out.println("getPubkey 2......");
publicKey = getPublicKey(inputStream,keyAlgorithm);
System.out.println("getPubkey 3......");
} catch (Exception e) {
e.printStackTrace();//EAD PUBLIC KEY ERROR
System.out.println("加载公钥出错!");
} finally {
if (inputStream != null){
try {
inputStream.close();
}catch (Exception e){
System.out.println("加载公钥,关闭流时出错!");
}
}
}
return publicKey;
} */
public static PublicKey getPublicKey(String keyAlgorithm) throws Exception {
try
{
/*System.out.println("b1.........");
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
System.out.println("b2.........");
StringBuilder sb = new StringBuilder();
String readLine = null;
System.out.println("b3.........");
while ((readLine = br.readLine()) != null) {
if (readLine.charAt(0) == '-') {
continue;
} else {
sb.append(readLine);
sb.append('\r');
}
}
System.out.println("b4.........");*/
//加载公钥
X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(decodeBase64(PKCS8_PUBLIC));
/*//读取公钥
X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(decodeBase64(sb.toString())); */
System.out.println("b5.........");
KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
System.out.println("b6.........");
//下行出错 java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=127, too big.
PublicKey publicKey = keyFactory.generatePublic(pubX509);
System.out.println("b7.........");
return publicKey;
} catch (Exception e) {
e.printStackTrace();
System.out.println("b8.........");
throw new Exception("READ PUBLIC KEY ERROR:", e);
} /*finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
inputStream = null;
throw new Exception("INPUT STREAM CLOSE ERROR:", e);
}
} */
}
public static PrivateKey getPrivateKey(InputStream inputStream, String keyAlgorithm) throws Exception {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String readLine = null;
while ((readLine = br.readLine()) != null) {
if (readLine.charAt(0) == '-') {
continue;
} else {
sb.append(readLine);
sb.append('\r');
}
}
System.out.println("hahhah4!"+decodeBase64(sb.toString()));
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decodeBase64(sb.toString()));
System.out.println("hahhah5!");
KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
System.out.println("hahhah6!");
PrivateKey privateKey = keyFactory.generatePrivate(priPKCS8);
System.out.println("hahhah7!");
return privateKey;
} catch (Exception e) {
throw new Exception("READ PRIVATE KEY ERROR:" ,e);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
inputStream = null;
throw new Exception("INPUT STREAM CLOSE ERROR:", e);
}
}
}
//一下面是base64的编码和解码
public static String encodeBase64(byte[]input) throws Exception{
Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
Method mainMethod= clazz.getMethod("encode", byte[].class);
mainMethod.setAccessible(true);
Object retObj=mainMethod.invoke(null, new Object[]{input});
return (String)retObj;
}
/***
* decode by Base64
*/
public static byte[] decodeBase64(String input) throws Exception{
Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
Method mainMethod= clazz.getMethod("decode", String.class);
mainMethod.setAccessible(true);
Object retObj=mainMethod.invoke(null, input);
return (byte[])retObj;
}
}
MD5工具类
public final class Md5Util {
private static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private static Md5Util instance = null;
private Md5Util() {
}
public synchronized static Md5Util getInstance() {
if(instance==null){
instance=new Md5Util();
}
return instance;
}
public String getShortToken(String arg0) {
return encoder(arg0).substring(8,24);
}
public String getLongToken(String arg0) {
return encoder(arg0).toString();
}
private StringBuffer encoder(String arg){
if(arg==null){
arg="";
}
MessageDigest md5 = null;
try {
md5=MessageDigest.getInstance("MD5");
md5.update(arg.getBytes("UTF8"));
} catch (Exception e) {
e.printStackTrace();
}
return toHex(md5.digest());
}
private StringBuffer toHex(byte[] bytes) {
StringBuffer str = new StringBuffer(32);
int length=bytes.length;
for (int i = 0; i < length; i++) {
str.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
str.append(hexDigits[bytes[i] & 0x0f]);
}
bytes=null;
return str;
}
public static void main(String a[]){
Md5Util instance = Md5Util.getInstance();
String longToken = instance.getLongToken("123456");
System.out.println(longToken);
}
}
向微信发送请求用到的工具类
public class PayUtil {
/**
* <p>
* 携带证书post请求
* </p>
*
* @param mchId
* @param url
* @param data
* @param certPath
* @return java.lang.String
* @author Winder
* @date 2021/1/25 下午2:51
*/
public static String doPublicKey(String mchId, String url, String data,String certPath) throws Exception {
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
FileInputStream instream = new FileInputStream(certPath);
try {
keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mchId.toCharArray())//这里也是写密码的
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpost = new HttpPost(url); //有些api会因为设置请求头无法正常响应
// httpost.addHeader("Connection", "keep-alive");
// httpost.addHeader("Accept", "*/*");
// httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
// httpost.addHeader("Host", "api.mch.weixin.qq.com");
// httpost.addHeader("X-Requested-With", "XMLHttpRequest");
// httpost.addHeader("Cache-Control", "max-age=0");
// httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}
Java Bean
/**
* <p>
* 获取RSA公钥bean
* </p>
*
* @author Winder
* @since 2021/1/25 上午11:33
*/
public class RSAPublicKeyBean implements Serializable {
private static final long serialVersionUID = 1L;
/**商户号*/
private String mch_id;
/**随机字符串*/
private String nonce_str;
/**签名*/
private String sign;
/**签名类型*/
private String sign_type;
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getSign_type() {
return sign_type;
}
public void setSign_type(String sign_type) {
this.sign_type = sign_type;
}
}
/**
* <p>
* 企业付款到银行卡bean
* </p>
*
* @author Winder
* @since 2021/1/25 上午10:13
*/
public class TransferBank implements Serializable {
private static final long serialVersionUID = 1L;
/**商户号*/
private String mch_id;
/**商户订单号*/
private String partner_trade_no;
/**随机字符串*/
private String nonce_str;
/**签名*/
private String sign;
/**收款方银行卡号*/
private String enc_bank_no;
/**收款方用户名*/
private String enc_true_name;
/**收款方开户行代号 https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=24_4*/
private String bank_code;
/**付款金额 单位分*/
private int amount;
/**付款说明,即备注(非必填)*/
private String desc;
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getPartner_trade_no() {
return partner_trade_no;
}
public void setPartner_trade_no(String partner_trade_no) {
this.partner_trade_no = partner_trade_no;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getEnc_bank_no() {
return enc_bank_no;
}
public void setEnc_bank_no(String enc_bank_no) {
this.enc_bank_no = enc_bank_no;
}
public String getEnc_true_name() {
return enc_true_name;
}
public void setEnc_true_name(String enc_true_name) {
this.enc_true_name = enc_true_name;
}
public String getBank_code() {
return bank_code;
}
public void setBank_code(String bank_code) {
this.bank_code = bank_code;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
配置类(放在resources目录下)
文件名为wxpay.properties(该文件主要的作用是方便管理这些变量,非必须)
#转账到银行卡接口
TRANSFERSBANKURL=https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank
#获取微信共钥接口
GETPUBLICKEY=https://fraud.mch.weixin.qq.com/risk/getpublickey
##商户号
MCHId=这个当然不能展示了
##secret
APIKEY=哈哈
#商户号支付证书
certs.path=证书的存放目录 eg:/home/app/../apiclient_cert.p12
到这里该准备的工具类、JavaBean、配置文件都准备好了。接下来开始进入业务逻辑。
首先我们向微信发送请求,获取PKCS#1 格式密钥:
/**
* <p>
* 生成获取rsa公钥的签名
* </p>
*
* @param bean
* @return java.lang.String
* @author Winder
* @date 2021/1/25 下午3:41
*/
public static String createGetPublicKeySign(RSAPublicKeyBean bean){
StringBuffer sign = new StringBuffer();
sign.append("mch_id=").append(bean.getMch_id());
sign.append("&nonce_str=").append(bean.getNonce_str());
sign.append("&sign_type=").append(bean.getSign_type());
sign.append("&key=").append(PropertyTool.getValue("/wxpay.properties", "APIKEY"));
System.out.println("获取公钥签名参数:" + sign.toString());
String signStr = Md5Util.getInstance().getLongToken(sign.toString()).toUpperCase();
System.out.println("获取公钥签名:" + signStr);
return signStr;
}
/**
* <p>
* 获取rsa公钥
* </p>
*
* @param
* @return java.util.Map
* @author Winder
* @date 2021/1/26 上午9:17
*/
public static Map getPublicKey() throws Exception {
//封装请求参数
RSAPublicKeyBean bean = new RSAPublicKeyBean();
String str = UUIDUtil.getUUID();
bean.setMch_id(PropertyTool.getValue("/wxpay.properties", "MCHId"));
bean.setNonce_str(str);
bean.setSign_type("MD5");
String sign = createGetPublicKeySign(bean);
bean.setSign(sign);
XMLUtil xmlUtil = new XMLUtil();
xmlUtil.xstream().alias("xml", bean.getClass());
String xml = xmlUtil.xstream().toXML(bean);
System.out.println("请求xml:" + xml);
String mchId = PropertyTool.getValue("/wxpay.properties", "MCHId");
String certFilePath = PropertyTool.getValue("/wxpay.properties", "certs.path.linux");;//证书目录位置
//发送请求
String response = PayUtil.doPublicKey(mchId, PropertyTool.getValue("/wxpay.properties", "GETPUBLICKEY"), xml, certFilePath);
System.out.println(response);
Map<String, String> map = xmlUtil.parseXml(response);
return map;
}
成功的话在输出中可以看到以下内容:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEArT82k67xybiJS9AD8nNAeuDYdrtCRaxkS6cgs8L9h83eqlDTlrdw
zBVSv5V4imTq/URbXn4K0V/KJ1TwDrqOI8hamGB0fvU13WW1NcJuv41RnJVua0QA
lS3tS1JzOZpMS9BEGeFvyFF/epbi/m9+2kUWG94FccArNnBtBqqvFncXgQsm98JB
3a62NbS1ePP/hMI7Kkz+JNMyYsWkrOUFDCXAbSZkWBJekY4nGZtK1erqGRve8Jbx
TWirAm/s08rUrjOuZFA21/EI2nea3DidJMTVnXVPY2qcAjF+595shwUKyTjKB8v1
REPB3hPF1Z75O6LwuLfyPiCrCTmVoyfqjwIDAQAB
-----END RSA PUBLIC KEY-----
到这里就拿到微信提供的公钥了,为PKCS#1 格式密钥,但是Java的同学是不能直接使用的,还需要转换。转换成PKCS#8 格式密钥。
到这儿已经完成一半了,大家可以休息一会了,放松一下。
这是微信官方提供的转换方式
PKCS#1 转 PKCS#8:
openssl rsa -RSAPublicKey_in -in -pubout
PKCS#8 转 PKCS#1:
openssl rsa -pubin -in -RSAPublicKey_out
我是在这儿转换的:
https://www.ssleye.com/web/pkcs
转换后的秘钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArT82k67xybiJS9AD8nNA
euDYdrtCRaxkS6cgs8L9h83eqlDTlrdwzBVSv5V4imTq/URbXn4K0V/KJ1TwDrqO
I8hamGB0fvU13WW1NcJuv41RnJVua0QAlS3tS1JzOZpMS9BEGeFvyFF/epbi/m9+
lkUWG94FccArNnBtBqqvFncXgQsm98JB3a42NbS1ePP/hMI7Kkz+JNMyYsWkrOUF
DCXAbSZkWBJekY4nGZtK1erqGRve8JbxTWirAm/s08rUrjOuZFA21/EI2nea3Did
JMTVnXVPY2qcAjF+595shwUKyTjKB8v1REPB3hPF1Z75O6LwuLfyPiCrCTmVoyfq
jwIDAQAB
-----END PUBLIC KEY-----
大家还记得第一个工具类里的常量吗?。。。。。。没错那儿填的就是这个(去除-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----)。
/**
* <p>
* 按照微信要求进行加密
* </p>
*
* @param data 加密前的开户姓名/银行卡号
* @return java.lang.String 加密后秘文
* @author Winder
* @date 2021/1/26 上午10:28
*/
public static String encrypt(String data){
PublicKey pub = null;
try {
//rsa加密
pub = RSAUtils.getPublicKey("RSA");
String rsa = "RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING";
byte[] estrName = RSAUtils.encrypt(data.getBytes(), pub, 2048, 11, rsa);
//base64加密
data = Base64Utils.encodeToString(estrName);
return data;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* <p>
* 生成企业付款到银行卡的签名
* </p>
*
* @param bean
* @return java.lang.String
* @author Winder
* @date 2021/1/25 上午10:43
*/
public static String createTransferBankSign(TransferBank bean){
StringBuffer sign = new StringBuffer();
sign.append("amount=").append(bean.getAmount());
sign.append("&bank_code=").append(bean.getBank_code());
if(StringUtils.isNotBlank(bean.getDesc())){
sign.append("&desc=").append(bean.getDesc());//非必填
}
sign.append("&enc_bank_no=").append(bean.getEnc_bank_no());
sign.append("&enc_true_name=").append(bean.getEnc_true_name());
sign.append("&mch_id=").append(bean.getMch_id());
sign.append("&nonce_str=").append(bean.getNonce_str());
sign.append("&partner_trade_no=").append(bean.getPartner_trade_no());
sign.append("&key=").append(PropertyTool.getValue("/wxpay.properties", "APIKEY"));
System.out.println("付款到银行卡签名参数:" + sign.toString());
String signStr = Md5Util.getInstance().getLongToken(sign.toString()).toUpperCase();
System.out.println("付款到银行卡签名:" + signStr);
return signStr;
}
/**
* <p>
* 转账
* </p>
*
* @param
* @return void
* @author Winder
* @date 2021/1/28 下午5:53
*/
public static void transferBank() throws Exception {
TransferBank transferBank = new TransferBank();
String name = encrypt("姓名");/**修改*/
String bank = encrypt("银行卡");/**修改*/
String str = UUIDUtil.getUUID();
String oderId = UUIDUtil.getUUID();
transferBank.setAmount(1);/**修改*/
transferBank.setBank_code("银行代码");/**修改*/
transferBank.setEnc_bank_no(bank);
transferBank.setEnc_true_name(name);
transferBank.setMch_id(PropertyTool.getValue("/wxpay.properties", "MCHId"));
transferBank.setNonce_str(str);
transferBank.setPartner_trade_no(oderId);
transferBank.setDesc("测试转账,每次转一分钱");
String sign = createTransferBankSign(transferBank);
transferBank.setSign(sign);
XMLUtil xmlUtil = new XMLUtil();
xmlUtil.xstream().alias("xml", transferBank.getClass());
String xml = xmlUtil.xstream().toXML(transferBank);
System.out.println("请求xml:" + xml);
String mchId = PropertyTool.getValue("/wxpay.properties", "MCHId");
String certFilePath = PropertyTool.getValue("/wxpay.properties", "certs.path.linux");
String response = PayUtil.doPublicKey(mchId, PropertyTool.getValue("/wxpay.properties", "TRANSFERSBANKURL"), xml, certFilePath);
System.out.println(response);
}
总结
到这儿转账工具类就全部结束了,具体业务逻辑还要自己控制,就不多做赘述了。很荣幸为大家分享。最后祝大家都能开发用户喜欢的好的产品、拿到好的薪资。