流程
接口文档
证书下载
下载商户证书[证书以及私钥] 详情请参考 微信文档
下载平台证书请参考 微信文档GitHub
代码
工具类
package com.jxyunge.utils.wechat.payapplyment; import okhttp3.HttpUrl; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.UUID; public class WeChatUtil { public static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"; // 商户证书路径 public static final String apiclient_cert = "/apiclient_cert.pem"; // 平台证书路径 public static final String publicKeyPath = "/wechatpay.pem"; // 商户私钥路径 public static final String privageKeyPath = "/apiclient_key.pem"; /** * 获取证书序列号 * * @param certPath 获取商户证书序列号 传递商号证书路径 apiclient_cert * @return * @throws IOException */ public static String getSerialNo(String certPath) throws IOException { X509Certificate certificate = getCertificate(certPath); return certificate.getSerialNumber().toString(16).toUpperCase(); } /** * 获取证书。 * * @param filename 证书文件路径 (required) * @return X509证书 */ public static X509Certificate getCertificate(String filename) throws IOException { InputStream fis = new FileInputStream(filename); try (BufferedInputStream bis = new BufferedInputStream(fis)) { CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(bis); 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); } } /** * 获取签名 * method(请求类型GET、POST url(请求url) * body(请求body,GET请求时body传"",POST请求时body为请求参数的json串) * merchantId(商户号) * certSerialNo(API证书序列号) * keyPath(API证书路径) * * @param method 请求方式 * @param url 请求路径 * @param body 请求参数 * @param merchantId 商户号 * @param certSerialNo API证书序列号 * @return * @throws Exception */ public static String getToken(String method, String url, String body, String merchantId, String certSerialNo) throws Exception { String signStr = ""; HttpUrl httpurl = HttpUrl.parse(url); String nonceStr = UUID.randomUUID().toString().replaceAll("-", ""); long timestamp = System.currentTimeMillis() / 1000; if (StringUtils.isEmpty(body)) { body = ""; } assert httpurl != null; String message = buildMessage(method, httpurl, timestamp, nonceStr, body); String signature = sign(message.getBytes(StandardCharsets.UTF_8), privageKeyPath); signStr = "mchid=\"" + merchantId + "\",nonce_str=\"" + nonceStr + "\",timestamp=\"" + timestamp + "\",serial_no=\"" + certSerialNo + "\",signature=\"" + signature + "\""; 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)), StandardCharsets.UTF_8); try { String privateKey = content.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("无效的密钥格式"); } } public static Cipher getCipher(X509Certificate certificate) { try { Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey()); return cipher; } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e); } catch (InvalidKeyException e) { throw new IllegalArgumentException("无效的证书", e); } } /** * 加密 * * @param message * @param cipher 通过getcipher方法获取 * @return * @throws IllegalBlockSizeException */ public static String rsaEncryptOAEP(String message, Cipher cipher) throws IllegalBlockSizeException { try { if (StringUtils.isBlank(message)) { return null; } byte[] data = message.getBytes(StandardCharsets.UTF_8); byte[] cipherdata = cipher.doFinal(data); return Base64.encodeBase64String(cipherdata); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new IllegalBlockSizeException("加密原串的长度不能超过214字节"); } } }
单元测试
package com.jxyunge.utils.wechat.payapplyment; import com.alibaba.fastjson.JSONObject; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.junit.Test; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import java.io.IOException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; /** * @author lgx * @Description * @create 2020-02-21 21:31:00 */ public class WechartTest { /*** 查询申请单 */ @Test public void test() throws Exception { String url = "https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/applyment_id/微信支付分配的申请单号"; String merchantId = "商户ID"; String serialNo = WeChatUtil.getSerialNo(WeChatUtil.apiclient_cert); String token = WeChatUtil.getToken("GET", url, null, merchantId, serialNo); String authorization = "WECHATPAY2-SHA256-RSA2048 " + token; // 平台证书路径 是必须的 serialNo = WeChatUtil.getSerialNo(WeChatUtil.publicKeyPath); HttpGet httpGet = new HttpGet(url); httpGet.setHeader("Wechatpay-Serial", serialNo); httpGet.setHeader("Accept", "application/json"); httpGet.setHeader("Content-Type", "application/json"); httpGet.setHeader("user-agent", WeChatUtil.DEFAULT_USER_AGENT); httpGet.setHeader("Authorization", authorization); HttpClientBuilder httpClientBuilder = HttpClients.custom(); CloseableHttpClient httpClient = httpClientBuilder.build(); CloseableHttpResponse httpResponse = httpClient.execute(httpGet); HttpEntity httpResponseEntity = httpResponse.getEntity(); String responseEntityStr = EntityUtils.toString(httpResponseEntity); httpResponse.close(); System.out.println(responseEntityStr); } /*** 进件接口 */ @Test public void test2() throws Exception { String url = "https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/"; // TODO String merchantId = "商户ID"; String serialNo = WeChatUtil.getSerialNo(WeChatUtil.apiclient_cert); Map<String,Object> requestMap = new HashMap<>(1); requestMap.put("business_code","业务申请编号"); // 参数自己根据文档进行组装 String requestParams = JSONObject.toJSONString(requestMap); String token = WeChatUtil.getToken("POST", url, requestParams, merchantId, serialNo); String authorization = "WECHATPAY2-SHA256-RSA2048 " + token; // 平台证书路径 是必须的 serialNo = WeChatUtil.getSerialNo(WeChatUtil.publicKeyPath); HttpPost httpPost = new HttpPost(url); httpPost.setHeader("Wechatpay-Serial", serialNo); httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Content-Type", "application/json"); httpPost.setHeader("user-agent", WeChatUtil.DEFAULT_USER_AGENT); httpPost.setHeader("Authorization", authorization); httpPost.setEntity(new StringEntity(requestParams, "UTF-8")); HttpClientBuilder httpClientBuilder = HttpClients.custom(); CloseableHttpClient httpClient = httpClientBuilder.build(); CloseableHttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpResponseEntity = httpResponse.getEntity(); String responseEntityStr = EntityUtils.toString(httpResponseEntity); httpResponse.close(); System.out.println(responseEntityStr); } /** * 加密消息 * * @throws IOException */ @Test public void test1() throws IOException, IllegalBlockSizeException { X509Certificate certificate = WeChatUtil.getCertificate(WeChatUtil.publicKeyPath); Cipher cipher = WeChatUtil.getCipher(certificate); String s = WeChatUtil.rsaEncryptOAEP("xx", cipher); System.out.println(s); } }
运行效果
- 热门文章
- Mysql 8.0+开启远程访问
- Vue3+Ts 组合API调用子组件方法
- JAVA生成微信小程序分享海报
- 基于 Vue 实现魔方矩阵排列效果
- JAVA开发微信特约商户进件/提交申请单
- 检查Office(Word/Excel)文档是否需要密码-通...
- Nginx 跨域配置支持
- 微信/v3/merchant/media/upload 网络图片上...
- 简述分布式CAP理论
- Iterator迭代器设计模式
- 我的标签
- JAVA<7>
- Js<4>
- 设计模式<4>
- TS<2>
- nginx<2>
- 微信服务商<2>
- 微信小程序<1>
- Vue<1>
- Vue3<1>
- IPv6<1>
- Apache POI<1>
- Mysql<1>
- rocketmq<1>
- 分布式数据库<1>
- polygon<1>
- 地图<1>
- CAP<1>
- jQuery<1>
- Git<1>
- curl<1>
- 分布式系统<1>
- 设计<0>
- Redis<0>
- HikariCP<0>
- 数据库连接池<0>
- 多线程<0>
- 友情链接
- 江西云戈信息技术