ji准备工作:
1.微信支付商户平台升级API证书,升级后才可成功调用本接口。服务商商户号和秘钥apiv3。
2.获取平台证书序列号及明文证书pem文件信息
public class WxGetcertFicatesUtils {
public final static String URL = “https://api.mch.weixin.qq.com/risk/getcertficates”;
/**
- https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=19_11
- @param config 服务商信息
- @return
- @throws Exception
*/
public static String getcertficates(WXPayConfig config) throws Exception {
Map<String, String> map = new HashMap<>();
map.put(“mch_id”, config.getMchID());
map.put(“nonce_str”, WXPayUtil.generateNonceStr());
map.put(“sign_type”, “HMAC-SHA256”);
map.put(“sign”, WXPayUtil.generateSignature(map, config.getKey(), WXPayConstants.SignType.HMACSHA256));
WXPay wxpay = new WXPay(config);
String str = wxpay.requestWithoutCert(URL, map, config.getHttpConnectTimeoutMs(),
config.getHttpReadTimeoutMs());
Map<String, String> certificates = WXPayUtil.xmlToMap(str);
String certificates1 = certificates.get(“certificates”);
String data1 = JSON.parseObject(certificates1).get(“data”).toString();
//去【】
data1=data1.replace("[", “”);
data1=data1.replace("]", “”);
String encrypt_certificate = JSON.parseObject(data1).get(“encrypt_certificate”).toString();
String serial_no = JSON.parseObject(data1).get(“serial_no”).toString();
String nonce = JSON.parseObject(encrypt_certificate).get(“nonce”).toString();
String associated_data = JSON.parseObject(encrypt_certificate).get(“associated_data”).toString();
String ciphertext = JSON.parseObject(encrypt_certificate).get(“ciphertext”).toString();
//*获取证书明文
String s = decryptCertSN(associated_data,nonce,ciphertext, config.getKey());
//重要勿泄露
//System.out.println(“替换apiclient_cert.pem的明文信息为:” + s);
if (certificates.get(“return_code”).equals(“SUCCESS”)) {
JSONArray data = JSON.parseObject(certificates.get(“certificates”)).getJSONArray(“data”);
JSONObject obj = data.getJSONObject(0);
return obj.getString(“serial_no”);
}
return null;
}
public static void main(String[] args) {
try {
MyWxconfig config = new MyWxconfig();
System.out.println(WxGetcertFicatesUtils.getcertficates(config));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3.获取上传图片要的media_id
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import org.apache.commons.codec.binary.Hex;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import XXX.XXX.XXX.XXX.XXX.MyWxconfig;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
public class WxUploadImgageUtils {
private final static Logger log = LoggerFactory.getLogger(WxUploadImgageUtils.class);
private final static String URL = “https://api.mch.weixin.qq.com/secapi/mch/uploadmedia”;
public static String uploadImgage(String img_url, WXPayConfig config) throws Exception {
String media_hash = md5HashCode(returnBitMap(img_url));
Map<String, Object> paramMap = new HashMap<>();
paramMap.put(“mch_id”, config.getMchID());
paramMap.put(“media_hash”, media_hash);
paramMap.put(“sign_type”, “HMAC-SHA256”);
String sign = sha256Sign(paramMap, config.getKey());
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.addTextBody(“mch_id”, config.getMchID(), ContentType.MULTIPART_FORM_DATA);
multipartEntityBuilder.addBinaryBody(“media”, returnBitMap(img_url), ContentType.create(“image/jpg”),
img_url.substring(img_url.lastIndexOf("/") + 1));
multipartEntityBuilder.addTextBody(“media_hash”, media_hash, ContentType.MULTIPART_FORM_DATA);
multipartEntityBuilder.addTextBody(“sign_type”, “HMAC-SHA256”, ContentType.MULTIPART_FORM_DATA);
multipartEntityBuilder.addTextBody(“sign”, sign, ContentType.MULTIPART_FORM_DATA);
KeyStore keyStore = KeyStore.getInstance(“PKCS12”);
keyStore.load(config.getCertStream(), config.getMchID().toCharArray());// 这里写密码…默认是你的MCHID
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, config.getMchID().toCharArray()).build();
@SuppressWarnings(“deprecation”)
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { “TLSv1” }, null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
HttpPost httpPost = new HttpPost(URL);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(10000).setConnectionRequestTimeout(10000)
.setSocketTimeout(10000).build();
httpPost.setConfig(requestConfig);
这里的Content-type要设置为"multipart/form-data",否则返回“参数填写有误,请检查后重试”
httpPost.addHeader(HTTP.CONTENT_TYPE, “multipart/form-data; charset=UTF-8”);
httpPost.addHeader(HTTP.USER_AGENT, “wxpay sdk java v1.0 " + config.getMchID());
httpPost.setEntity(multipartEntityBuilder.build());
CloseableHttpResponse response = httpclient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity(), “UTF-8”);
Map<String, String> map = WXPayUtil.xmlToMap(result);
if (map.get(“result_code”).equals(“SUCCESS”)) {
return map.get(“media_id”);
}
return null;
}
public static String upload(String file_url) {
try {
MyWxconfig config = new MyWxconfig();
String media_hash = md5HashCode(returnBitMap(file_url));
String sign_type = “HMAC-SHA256”;
Map<String, Object> paramMap = new HashMap<>();
paramMap.put(“mch_id”, config.getMchID());
paramMap.put(“media_hash”, media_hash);
paramMap.put(“sign_type”, sign_type);
String sign = sha256Sign(paramMap, config.getKey());
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
multipartEntityBuilder.addTextBody(“mch_id”, config.getMchID(), ContentType.MULTIPART_FORM_DATA);
multipartEntityBuilder.addBinaryBody(“media”, returnBitMap(file_url), ContentType.create(“image/jpg”),
file_url.substring(file_url.lastIndexOf(”/") + 1));
multipartEntityBuilder.addTextBody(“media_hash”, media_hash, ContentType.MULTIPART_FORM_DATA);
multipartEntityBuilder.addTextBody(“sign_type”, sign_type, ContentType.MULTIPART_FORM_DATA);
multipartEntityBuilder.addTextBody(“sign”, sign, ContentType.MULTIPART_FORM_DATA);
KeyStore keyStore = KeyStore.getInstance(“PKCS12”);
keyStore.load(config.getCertStream(), config.getMchID().toCharArray());// 这里写密码…默认是你的MCHID
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, config.getMchID().toCharArray())
.build();
@SuppressWarnings(“deprecation”)
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { “TLSv1” },
null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
HttpPost httpPost = new HttpPost(URL);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(10000)
.setConnectionRequestTimeout(10000).setSocketTimeout(10000).build();
httpPost.setConfig(requestConfig);
这里的Content-type要设置为"multipart/form-data",否则返回“参数填写有误,请检查后重试”
httpPost.addHeader(HTTP.CONTENT_TYPE, “multipart/form-data; charset=UTF-8”);
httpPost.addHeader(HTTP.USER_AGENT, "wxpay sdk java v1.0 " + config.getMchID());
httpPost.setEntity(multipartEntityBuilder.build());
CloseableHttpResponse response = httpclient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity(), “UTF-8”);
return result;
} catch (Exception e) {
log.error(“官方微信–请求失败!{}”, e);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
- 对上次文件进行MD5获取其Hash值
- @param fis
- @return
/
public static String md5HashCode(InputStream fis) {
try {
MessageDigest MD5 = MessageDigest.getInstance(“MD5”);
byte[] buffer = new byte[8192];
int length;
while ((length = fis.read(buffer)) != -1) {
MD5.update(buffer, 0, length);
}
return new String(Hex.encodeHex(MD5.digest()));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/* - 获取HMAC-SHA256签名
- @param paramMap 签名参数(sign不参与签名)
- @param key 签名密钥
- @return HMAC-SHA256签名结果
/
public final static String sha256Sign(Map<String, Object> paramMap, String key) {
try {
String payParam = getSignTemp(paramMap, key);
Mac sha256_HMAC = Mac.getInstance(“HmacSHA256”);
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(“UTF-8”), “HmacSHA256”);
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(payParam.getBytes(“UTF-8”));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
String sign = sb.toString().toUpperCase();
return sign;
} catch (Exception e) {
return null;
}
}
/* - 获取签名参数字符串
- @param paramMap 签名参数(sign字段不参与签名)
- @param payKey 签名密钥
- @return 待签名字符串
*/
private final static String getSignTemp(Map<String, Object> paramMap, String payKey) {
ArrayList keyList = new ArrayList<>(paramMap.keySet());
Collections.sort(keyList);
StringBuilder signParam = new StringBuilder();
for (String key : keyList) {
if (!“sign”.equals(key) && !StringUtils.isEmpty(paramMap.get(key))) {
signParam.append(key).append("=").append(paramMap.get(key)).append("&");
}
}
signParam.delete(signParam.length() - 1, signParam.length());
signParam.append("&key=").append(payKey);
return signParam.toString();
}
public static InputStream returnBitMap(String path) {
URL url = null;
InputStream is = null;
try {
url = new URL(path);
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();// 利用HttpURLConnection对象,我们可以从网络中获取网页数据.
conn.setDoInput(true);
conn.connect();
is = conn.getInputStream(); // 得到网络返回的输入流
} catch (IOException e) {
e.printStackTrace();
}
return is;
}
}
4.敏感数据加密
微信官方文档地址:https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=19_12
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.security.cert.X509Certificate;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class RSAEncrypt {
private static final String CIPHER_PROVIDER = “SunJCE”;
private static final String TRANSFORMATION_PKCS1Paddiing = “RSA/ECB/PKCS1Padding”;
private static final String CHAR_ENCODING = “UTF-8”;// 固定值,无须修改
private static final String PUBLIC_KEY_FILENAME = “apiclient_cert.pem”;// 平台证书路径,开发人员需根据具体路径修改
// 数据加密方法
private static byte[] encryptPkcs1padding(PublicKey publicKey, byte[] data) throws Exception {
Cipher ci = Cipher.getInstance(TRANSFORMATION_PKCS1Paddiing, CIPHER_PROVIDER);
ci.init(Cipher.ENCRYPT_MODE, publicKey);
return ci.doFinal(data);
}
// 加密后的秘文,使用base64编码方法
private static String encodeBase64(byte[] bytes) throws Exception {
return Base64.getEncoder().encodeToString(bytes);
}
// 对敏感内容(入参Content)加密,其中PUBLIC_KEY_FILENAME为存放平台证书的路径,平台证书文件存放明文平台证书内容,且为pem格式的平台证书(平台证书的获取方式参照平台证书及序列号获取接口,通过此接口得到的参数certificates包含了加密的平台证书内容ciphertext,然后根据接口文档中平台证书解密指引,最终得到明文平台证书内容)
public static String rsaEncrypt(String Content) throws Exception {
Resource resource = new ClassPathResource(PUBLIC_KEY_FILENAME);
final byte[] PublicKeyBytes = input2byte(resource.getInputStream());
X509Certificate certificate = X509Certificate.getInstance(PublicKeyBytes);
PublicKey publicKey = certificate.getPublicKey();
return encodeBase64(encryptPkcs1padding(publicKey, Content.getBytes(CHAR_ENCODING)));
}
public static final byte[] input2byte(InputStream inStream) {
ByteArrayOutputStream swapStream = null;
try {
swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while ((rc = inStream.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] in2b = swapStream.toByteArray();
return in2b;
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
swapStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
// 对“hello world进行加密”
public static void main(String[] args) {
try {
System.out.println("after encrypt: " + rsaEncrypt(“hello world”));
} catch (Exception e) {
e.printStackTrace();
}
}
}
5.sign参数放最后
以上条件满足后,可以直接通过WXPay.requestWithCert(…)发送请求。
可能出现:抛出异常java.security.InvalidKeyException: Illegal key size
Illegal key size or default parameters 是指密钥长度受限制
Java运行时环境读到的是受限的policy文件。
policy文件位于${java_home}/jre/lib/security 目录下。
这种限制是因为美国对软件出口的控制。
解决办法:
去除该限制只需下载 Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files,
覆盖上述目录下的对应jar文件(local_policy.jar, US_export_policy.jar)即可。
下载地址:
JDK6 http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html
JDK7 http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
JDK8 http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
下载包的readme.txt 有安装说明,即替换
${jdk_home}/jre/lib/security
${jre_home}/lib/security
目录下的 local_policy.jar 和 US_export_policy.jar 文件。
签约注意
1.银行卡持卡人必须和身份证姓名一致
2.身份证有效期限按官方要求写
3.开户银行注意,若为邮储银行写全名邮政储蓄银行
4.商户简称,不要随意只写水果店,饭店,商店,至少写XX水果店,XX饭店/商店
5.售卖商品/提供服务描述写官方规定的描述
6.扫码签约时系统会验证微信实名信息是否为申请单商户本人,若扫码后报错,请先确认商户微信号是否已通过绑卡认证
7.一个微信只能绑定一次,慎用。
8.签约成功后,再申请只要查询状态成功,后台就会多出一个小微商户号,尽管没用但很烦。
9.签约后要进行支付不需要配置绑定,关注和js支付目录。
10.支付成功后金额T+1天会自动提现到银行卡,手续费与申请签约时的费率有关。
11.Java实现AES加密,抛出异常java.security.InvalidKeyException: Illegal key size
支付宝签约官方文档:https://docs.open.alipay.com/20190114195458793939/quickstart/