微信支付分创建支付分订单+签名+验签
签名工具类:
package app.action.signUtil; import app.action.wx.Sign; import com.alibaba.druid.util.StringUtils; import okhttp3.HttpUrl; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Random; /** * 微信支付分签名工具类 */ public class SignUtil { private static final Log LOGGER = LogFactory.getLog(Sign.class); //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 = create_nonce_str(); 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); 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 filename 私钥文件路径 (required) * @return 私钥对象 */ public static PrivateKey getPrivateKey(String filename) throws IOException { String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8"); LOGGER.info("File content:" + content); try { String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s+", ""); LOGGER.info("privateKey:" + privateKey); 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) { LOGGER.info("异常:" + e); throw new RuntimeException("无效的密钥格式"); } } /** * 获取32位随机数 * @return */ public static String create_nonce_str() { StringBuilder sb = new StringBuilder(); Random rand = new Random(); for (int i = 0; i < 32; i++) { sb.append(STR_ARR[rand.nextInt(STR_ARR.length)]); } return sb.toString(); } private static String[] STR_ARR = new String[] { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }; }
创建支付分订单:
package app.action.signUtil; import cn.hutool.http.Header; import cn.hutool.http.HttpRequest; /** * 签名并创建支付分订单 */ public class CreateOrderInfoAction { public static void main(String[] args) throws Exception { // 微信支付分验签+创建支付分订单 // 版本号:V1.3(版本时间---版本更新时间:2020.03.05) String reqdata = "{ \"out_order_no\": \"456123JKHDFE125476962\"," + // 随机数 " \"appid\": \"wx74654qweqwe46545\"," + // 公众账号ID,(这个一定要确保正确,不然能坑死) " \"service_id\": \"0000000000000000251514515151512\"," + " \"service_introduction\": \"小米手机\"," + " \"time_range\": {" + " \"start_time\": \"OnAccept\"" + " }," + " \"post_payments\": [" + " {" + " \"name\": \"支付分换手机\"," + " \"amount\":10," + " \"description\": \"小米手机\"," + " \"count\": 1" + " }" + "]," + " \"risk_fund\": {" + " \"name\": \"ESTIMATE_ORDER_COST\"," + " \"amount\": 10" + " }," + " \"notify_url\": \"https://test.com\"," + // 设置的微信处理完回调你的地址 " \"openid\": \"aqweqweqweqweqweqweqweUY\"," + // 用户标识,每个用户是唯一的 " \"need_user_confirm\": false}"; // 我这商户是免确认模式,顾设置为false // 创建支付分订单API的请求URL:https://api.mch.weixin.qq.com/v3/payscore/serviceorder String URL = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder"; // 商户号(具体以自己的为准) String WX_MCHID = "123456789"; // 商户API证书序列号serial_no(具体以自己的为准) String WX_SERIAL_NO = "16ASDASDASDASD561564651321ASDASDASD"; // 签名私钥路径(最好不要有中文) String keyPath = "F:\\wxpert\\apiclient_key.pem"; // 下面的post方法是Hutool工具类里的一个方法 /* 引入以下jar包 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.3</version> </dependency> */ String data = HttpRequest.post(URL) .header(Header.CONTENT_TYPE, "application/json") .header(Header.ACCEPT, "application/json") .header("Authorization", "WECHATPAY2-SHA256-RSA2048" + " " + SignUtil.getToken("POST", URL, reqdata, WX_MCHID, WX_SERIAL_NO, keyPath))//头信息,多个头信息多次调用此方法即可 .body(reqdata) .execute().body(); System.out.println("data = " + data); } }
微信支付分验签公钥获取:
package app.action.signUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpEntity; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.*; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import static app.action.wx.Sign.getToken; /** * 获取公钥的方式,并把获取的公钥保存在F:\key * F:\key(自己设置的地址) */ public class PublicKeyUtil { private static final Log LOGGER = LogFactory.getLog(PublicKeyUtil.class); public static void main(String[] args) throws Exception { getCertByAPI("123456789","https://api.mch.weixin.qq.com/v3/certificates",2,null,"16ASDASDASDASD561564651321ASDASDASD","F:\\wxpert\\apiclient_key.pem"); } 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>(); // qweqweqweqweqweqweqweqwApi3为自己的apiv3秘药 AesUtil aesUtil = new AesUtil(("qweqweqweqweqweqweqweqwApi3").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()); plainCertificateList.add(bo); } return plainCertificateList; } //证书保存地址 private static List<X509Certificate> saveCertificate(List<PlainCertificateItem> cert) throws IOException { List<X509Certificate> x509Certs = new ArrayList<X509Certificate>(); // 公钥保存在F:\key文件夹下名字为publicKey.pem File file = new File("F:\\key"); file.mkdirs(); for (PlainCertificateItem item : cert) { ByteArrayInputStream inputStream = new ByteArrayInputStream(item.getPlainCertificate().getBytes(StandardCharsets.UTF_8)); X509Certificate x509Cert = PemUtil.loadCertificate(inputStream); x509Certs.add(x509Cert); String outputAbsoluteFilename = file.getAbsolutePath() + File.separator + "publicKey.pem"; try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputAbsoluteFilename), StandardCharsets.UTF_8))) { writer.write(item.getPlainCertificate()); } LOGGER.info("输出证书文件目录:"+outputAbsoluteFilename); } return x509Certs; } }
微信支付分验签工具类:
package app.action.signUtil; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.PublicKey; import java.security.Signature; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Base64; import java.util.HashMap; import java.util.Map; import static app.action.signUtil.SignUtil.getToken; /** * 微信支付分验签工具类 */ public class CheckSignUtil { private static String CHARSET_ENCODING = "UTF-8"; private static String ALGORITHM = "SHA256withRSA"; public static void main(String[] args) throws Exception { getCheckSign("123456789","https://api.mch.weixin.qq.com/v3/certificates",2,null,"16ASDASDASDASD561564651321ASDASDASD","F:\\wxpert\\apiclient_key.pem"); } public static String getCheckSign(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); try { CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(httpGet); HttpEntity httpEntity = response.getEntity(); result = EntityUtils.toString(httpEntity, "UTF-8"); Header[] allHeaders = response.getAllHeaders(); Map<String, String> headers = new HashMap<String, String>(); for(int i=0;i<allHeaders.length;i++){ String key = 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"); String srcData = Timestamp+"\n" + Nonce+"\n" + result+"\n"; // publicKeyPath 为自己公钥保存的地址如:F:\key\publicKey.pem String publicKeyPath = "F:\\key\\publicKey.pem"; boolean verify = verify(srcData, Signature, publicKeyPath); System.out.println("验签结果 = " + verify); }catch (Exception e){ e.printStackTrace(); } return null; } /** * 验签 * @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)); return sign.verify(Base64.getDecoder().decode(signedData)); } catch (Exception 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; } }
以下是需要的工具及实体类:
package app.action.signUtil; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Base64; public class AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; private final byte[] aesKey; public AesUtil(byte[] key) { if (key.length != KEY_LENGTH_BYTE) { throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); } this.aesKey = key; } public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { 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); } } }
package app.action.signUtil; //平台证书item public class CertificateItem { //加密的平台证书序列号 private String serial_no; //加密的平台证书序列号 private String effective_time; //证书弃用时间 private String expire_time; //证书加密信息 private EncryptedCertificateItem encrypt_certificate; public String getSerial_no() { return serial_no; } public void setSerial_no(String serial_no) { this.serial_no = serial_no; } public String getEffective_time() { return effective_time; } public void setEffective_time(String effective_time) { this.effective_time = effective_time; } public String getExpire_time() { return expire_time; } public void setExpire_time(String expire_time) { this.expire_time = expire_time; } public EncryptedCertificateItem getEncrypt_certificate() { return encrypt_certificate; } public void setEncrypt_certificate(EncryptedCertificateItem encrypt_certificate) { this.encrypt_certificate = encrypt_certificate; } }
package app.action.signUtil; public class EncryptedCertificateItem { //加密的平台证书序列号 private String algorithm; //加密的平台证书序列号 private String nonce; //证书弃用时间 private String associated_data; //证书弃用时间 private String ciphertext; public String getAlgorithm() { return algorithm; } public void setAlgorithm(String algorithm) { this.algorithm = algorithm; } public String getNonce() { return nonce; } public void setNonce(String nonce) { this.nonce = nonce; } public String getAssociated_data() { return associated_data; } public void setAssociated_data(String associated_data) { this.associated_data = associated_data; } public String getCiphertext() { return ciphertext; } public void setCiphertext(String ciphertext) { this.ciphertext = ciphertext; } }
package app.action.signUtil; //证书明文item public class PlainCertificateItem { private String serialNo; private String effectiveTime; private String expireTime; private String plainCertificate; public String getSerialNo() { return serialNo; } public void setSerialNo(String serialNo) { this.serialNo = serialNo; } public String getEffectiveTime() { return effectiveTime; } public void setEffectiveTime(String effectiveTime) { this.effectiveTime = effectiveTime; } public String getExpireTime() { return expireTime; } public void setExpireTime(String expireTime) { this.expireTime = expireTime; } public String getPlainCertificate() { return plainCertificate; } public void setPlainCertificate(String plainCertificate) { this.plainCertificate = plainCertificate; } }