微信支付V3版

  • 1.引入依赖
  • 2.创建时间工具类 DateTimeZoneUtil.class
  • 3.解密工具类 AesUtil.class
  • 4.对外暴露方法
      • 公共参数
    • 4-1.支付下单 V3PayGet()
    • 4-2.微信调起支付参数 WxTuneUp()
    • 4-3.处理微信异步回调 notify()
  • 5. 同类类方法
    • 5-1.生成组装请求头 getToken()
    • 5-2.生成签名 sign()
    • 5-3.组装签名加载 buildMessage()
    • 5-4.获取私钥 getPrivateKey()
    • 5-5.构造签名串 buildSignMessage()
    • 5-6.v3支付异步通知验证签名 verifyNotify()
    • 5-7.处理返回对象 readData()
  • 6.接口调用实例
    • 6-1支付请求
    • 6-2.异步回调
  • 文件秘钥截图:
    • 当前添加了微信v2版本的相关支付,合单支付、分账,详情请参考源码--->
    • 源码地址

1.引入依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.5.RELEASE</version><relativePath/></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.6.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib</artifactId><version>2.4</version><classifier>jdk15</classifier></dependency><!-- huTool 工具包 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-crypto</artifactId><version>5.5.0</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-http</artifactId><version>5.5.0</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-json</artifactId><version>5.5.0</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.12</version><scope>compile</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><dependency><groupId>com.github.xkzhangsan</groupId><artifactId>xk-time</artifactId><version>2.1.2</version></dependency></dependencies>

2.创建时间工具类 DateTimeZoneUtil.class

import cn.hutool.core.util.StrUtil;
import com.xkzhangsan.time.converter.DateTimeConverterUtil;
import com.xkzhangsan.time.formatter.DateTimeFormatterUtil;import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.Date;/*** 〈一句话功能简述〉<br>* 〈时间转换工具类〉** @author Gym* @create 2020/12/18* @since 1.0.0*/
public class DateTimeZoneUtil implements Serializable {/*** 时间转 TimeZone** @param time* @return* @throws Exception*/public static String dateToTimeZone(long time) throws Exception {return dateToTimeZone(new Date(time));}/*** 时间转 TimeZone** @param date* @return* @throws Exception*/public static String dateToTimeZone(Date date) throws Exception {String time;if (date == null) {throw new Exception("date is not null");}ZonedDateTime zonedDateTime = DateTimeConverterUtil.toZonedDateTime(date);time = DateTimeFormatterUtil.format(zonedDateTime, DateTimeFormatterUtil.YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);return time;}/*** TimeZone 时间转标准时间** @param str* @return* @throws Exception*/public static String timeZoneDateToStr(String str) throws Exception {String time;if (StrUtil.isBlank(str)) {throw new Exception("str is not null");}ZonedDateTime zonedDateTime = DateTimeFormatterUtil.parseToZonedDateTime(str, DateTimeFormatterUtil.YYYY_MM_DD_T_HH_MM_SS_XXX_FMT);if (zonedDateTime == null) {throw new Exception("str to zonedDateTime fail");}time = zonedDateTime.format(DateTimeFormatterUtil.YYYY_MM_DD_HH_MM_SS_FMT);return time;}
}

3.解密工具类 AesUtil.class


import cn.hutool.core.codec.Base64;import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;/*** 〈一句话功能简述〉<br>* 〈〉** @author Gym* @create 2020/12/18* @since 1.0.0*/
public class AesUtil {static final int KEY_LENGTH_BYTE = 32;static final int TAG_LENGTH_BIT = 128;private final byte[] aesKey;/*** @param key APIv3 密钥*/public AesUtil(byte[] key) {if (key.length != KEY_LENGTH_BYTE) {throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");}this.aesKey = key;}/*** 证书和回调报文解密** @param associatedData associated_data* @param nonce          nonce* @param cipherText     ciphertext* @return {String} 平台证书明文* @throws GeneralSecurityException 异常*/public String decryptToString(byte[] associatedData, byte[] nonce, String cipherText) throws GeneralSecurityException {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.decode(cipherText)), StandardCharsets.UTF_8);} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {throw new IllegalStateException(e);} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {throw new IllegalArgumentException(e);}}
}

4.对外暴露方法

公共参数

 //请求网关 private static final String url_prex = "https://api.mch.weixin.qq.com/";//编码private static final String charset = "UTF-8";

4-1.支付下单 V3PayGet()

 /*** 微信支付下单** @param url                请求地址(只需传入域名之后的路由地址)* @param jsonStr            请求体 json字符串 此参数与微信官方文档一致* @param mercId             商户ID* @param serial_no          证书序列号* @param privateKeyFilePath 私钥的路径* @return 订单支付的参数* @throws Exception*/public static String V3PayGet(String url, String jsonStr, String mercId, String serial_no, String privateKeyFilePath) throws Exception {String body = "";//创建httpclient对象CloseableHttpClient client = HttpClients.createDefault();//创建post方式请求对象HttpPost httpPost = new HttpPost(url_prex + url);//装填参数StringEntity s = new StringEntity(jsonStr, charset);s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,"application/json"));//设置参数到请求对象中httpPost.setEntity(s);String post = getToken("POST", HttpUrl.parse(url_prex + url), mercId, serial_no, privateKeyFilePath, jsonStr);//设置header信息//指定报文头【Content-type】、【User-Agent】httpPost.setHeader("Content-type", "application/json");httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");httpPost.setHeader("Accept", "application/json");httpPost.setHeader("Authorization","WECHATPAY2-SHA256-RSA2048 " + post);//执行请求操作,并拿到结果(同步阻塞)CloseableHttpResponse response = client.execute(httpPost);//获取结果实体HttpEntity entity = response.getEntity();if (entity != null) {//按指定编码转换结果实体为String类型body = EntityUtils.toString(entity, charset);}EntityUtils.consume(entity);//释放链接response.close();switch (url) {case "v3/pay/transactions/app"://返回APP支付所需的参数return JSONObject.fromObject(body).getString("prepay_id");case "v3/pay/transactions/jsapi"://返回JSAPI支付所需的参数return JSONObject.fromObject(body).getString("prepay_id");case "v3/pay/transactions/native"://返回native的请求地址return JSONObject.fromObject(body).getString("code_url");case "v3/pay/transactions/h5"://返回h5支付的链接return JSONObject.fromObject(body).getString("h5_url");}return null;}

4-2.微信调起支付参数 WxTuneUp()

 /*** 微信调起支付参数* 返回参数如有不理解 请访问微信官方文档* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml** @param prepayId           微信下单返回的prepay_id* @param appId              应用ID(appid)* @param privateKeyFilePath 私钥的地址* @return 当前调起支付所需的参数* @throws Exception*/public static JSONObject WxTuneUp(String prepayId, String appId, String privateKeyFilePath) throws Exception {String time = System.currentTimeMillis() / 1000 + "";String nonceStr = UUID.randomUUID().toString().replace("-", "");String packageStr = "prepay_id=" + prepayId;ArrayList<String> list = new ArrayList<>();list.add(appId);list.add(time);list.add(nonceStr);list.add(packageStr);//加载签名String packageSign = sign(buildSignMessage(list).getBytes(), privateKeyFilePath);JSONObject jsonObject = new JSONObject();jsonObject.put("appid", appId);jsonObject.put("timeStamp", time);jsonObject.put("nonceStr", nonceStr);jsonObject.put("packages", packageStr);jsonObject.put("signType", "RSA");jsonObject.put("paySign", packageSign);return jsonObject;}

4-3.处理微信异步回调 notify()

 /*** 处理微信异步回调** @param request* @param response* @param privateKey 32的秘钥*/public static String notify(HttpServletRequest request, HttpServletResponse response, String privateKey) throws Exception {Map<String, String> map = new HashMap<>(12);String result = readData(request);// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号String plainText = verifyNotify(result, privateKey);if (StrUtil.isNotEmpty(plainText)) {response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "SUCCESS");} else {response.setStatus(500);map.put("code", "ERROR");map.put("message", "签名错误");}response.setHeader("Content-type", ContentType.JSON.toString());response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));response.flushBuffer();String out_trade_no = JSONObject.fromObject(plainText).getString("out_trade_no");return out_trade_no;}

5. 同类类方法

5-1.生成组装请求头 getToken()

/*** 生成组装请求头** @param method             请求方式* @param url                请求地址* @param mercId             商户ID* @param serial_no          证书序列号* @param privateKeyFilePath 私钥路径* @param body               请求体* @return 组装请求的数据* @throws Exception*/static String getToken(String method, HttpUrl url, String mercId, String serial_no, String privateKeyFilePath, String body) throws Exception {String nonceStr = UUID.randomUUID().toString().replace("-", "");long timestamp = System.currentTimeMillis() / 1000;String message = buildMessage(method, url, timestamp, nonceStr, body);String signature = sign(message.getBytes("UTF-8"), privateKeyFilePath);return "mchid=\"" + mercId + "\","+ "nonce_str=\"" + nonceStr + "\","+ "timestamp=\"" + timestamp + "\","+ "serial_no=\"" + serial_no + "\","+ "signature=\"" + signature + "\"";}

5-2.生成签名 sign()

 /*** 生成签名** @param message            请求体* @param privateKeyFilePath 私钥的路径* @return 生成base64位签名信息* @throws Exception*/static String sign(byte[] message, String privateKeyFilePath) throws Exception {Signature sign = Signature.getInstance("SHA256withRSA");sign.initSign(getPrivateKey(privateKeyFilePath));sign.update(message);return Base64.getEncoder().encodeToString(sign.sign());}

5-3.组装签名加载 buildMessage()

     /*** 组装签名加载** @param method    请求方式* @param url       请求地址* @param timestamp 请求时间* @param nonceStr  请求随机字符串* @param body      请求体* @return 组装的字符串*/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";}

5-4.获取私钥 getPrivateKey()

 /*** 获取私钥。** @param filename 私钥文件路径  (required)* @return 私钥对象*/static PrivateKey getPrivateKey(String filename) throws IOException {String content = new String(Files.readAllBytes(Paths.get(filename)), "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.getDecoder().decode(privateKey)));} catch (NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持RSA", e);} catch (InvalidKeySpecException e) {throw new RuntimeException("无效的密钥格式");}}

5-5.构造签名串 buildSignMessage()

 /*** 构造签名串** @param signMessage 待签名的参数* @return 构造后带待签名串*/static String buildSignMessage(ArrayList<String> signMessage) {if (signMessage == null || signMessage.size() <= 0) {return null;}StringBuilder sbf = new StringBuilder();for (String str : signMessage) {sbf.append(str).append("\n");}return sbf.toString();}

5-6.v3支付异步通知验证签名 verifyNotify()

 /*** v3 支付异步通知验证签名** @param body 异步通知密文* @param key  api 密钥* @return 异步通知明文* @throws Exception 异常信息*/static String verifyNotify(String body, String key) throws Exception {// 获取平台证书序列号cn.hutool.json.JSONObject resultObject = JSONUtil.parseObj(body);cn.hutool.json.JSONObject resource = resultObject.getJSONObject("resource");String cipherText = resource.getStr("ciphertext");String nonceStr = resource.getStr("nonce");String associatedData = resource.getStr("associated_data");AesUtil aesUtil = new AesUtil(key.getBytes(StandardCharsets.UTF_8));// 密文解密return aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonceStr.getBytes(StandardCharsets.UTF_8),cipherText);}

5-7.处理返回对象 readData()

 /*** 处理返回对象** @param request* @return*/static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder result = new StringBuilder();br = request.getReader();for (String line; (line = br.readLine()) != null; ) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}}

6.接口调用实例

6-1支付请求

@GetMapping("/wxPay")public Object wxPay() throws Exception {//支付的请求参数信息(此参数与微信支付文档一致,[文档地址](https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml))WxPayRequestBo wxPayRequestBo = new WxPayRequestBo().setAppid(appId).setMchid(mercId).setDescription("商品描述").setOut_trade_no(out_trade_no).setTime_expire(time_expire).setNotify_url(notify_url).setAmount(new Amount().setTotal(1)).setPayer(new Payer().setOpenid(openId)).setScene_info(new SceneInfo().setPayer_client_ip(client_ip));String wxPayRequestJsonStr = JSONUtil.toJsonStr(wxPayRequestBo);//第一步获取prepay_idString prepayId = WxPayV3Util.V3PayGet(url, wxPayRequestJsonStr, mercId, serial_no, privateKeyFilePath);//第二步获取调起支付的参数JSONObject object = JSONObject.fromObject(WxPayV3Util.WxTuneUp(prepayId, appId, privateKeyFilePath));return object;}

6-2.异步回调

 @RequestMapping(value = "/wxnoty", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})public void wxnoty(HttpServletRequest request, HttpServletResponse response) throws Exception {System.err.println("小程序支付异步消息");String out_trade_no = WxPayV3Util.notify(request, response, privateKey);System.out.println(out_trade_no);}

文件秘钥截图:

当前添加了微信v2版本的相关支付,合单支付、分账,详情请参考源码—>

源码地址

微信支付V3版 java相关推荐

  1. 2020微信支付v3版本java对接详细流程

    都0202年,我似乎翻遍了百度,都没找到最新版微信支付v3的对接相关的详细博客,我都纳闷了,只有自己摸索.还有就是竟然还有人用一些v3对接的假代码,来骗积分,我真的服了,感同身受,以下是我对接的过程, ...

  2. 微信公众号之微信支付(V3版)jsp页面jsapi统一下单调启支付(java版)

    web项目 按顺序一步一步来 准备: 申请了微信支付接口的微信公众号一枚 , 备案的域名网站一枚, (如果有没有的这两个东西会耽误你很久,因为微信支付必须是线上,无法本地调试,) 这里说一下 如果有经 ...

  3. java异步调用微信接口_微信支付V3 SDK(Java版,支持同步异步调用)

    我们在开发微信支付时,发现微信官方已经对SDK做了升级,V3版本的SDK从设计上符合RESTful规范. 我们再在开源库中寻找是否有现成de开箱即用.并且支持响应式编程的SDK版本.经过一凡寻找,令我 ...

  4. 小程序微信支付V3版本Java集成

    一.简介 1.关于API v3 相较于之前的微信支付API,主要区别是: 遵循统一的REST的设计风格 使用JSON作为数据交互的格式,不再使用XML 使用基于非对称密钥的SHA256-RSA的数字签 ...

  5. 微信h5支付 php sdk_微信支付V3版,H5,APP微信支付PHP服务端使用方式

    使用步骤: 1.导入下载好的微信V3版本SDK public function __construct() { parent::__construct(); //引入微信支付 Vendor('Wxpa ...

  6. 微信支付V3版开发中遇到的一个问题及原因:缺少prepay_id

    微信 支付与支付宝支付的开发过程中有一个极大的区别, 支付宝可以在内网测试,使用内网地址, 而微信 支付必须使用外网地址,在线测试 测试过程中,我们发现一个错误, unifiedOrder 生成的or ...

  7. springboot集成微信支付V3 SDK

    微信支付开通支付方法在这里可以参考一下:申请开通微信支付教程_个人怎么申请微信支付_郑鹏川的博客-CSDN博客 因为微信支付回调需要一个外网可访问的地址,我本地调试是采用的内网穿透的方式进行调试的. ...

  8. 微信支付V3 小程序支付API Java版

    本文目的:快速接通微信支付V3 无需关注细节,实现支付功能,修改配置即可调用 文章目录 接入准备 微信支付流程整理(小程序版) 一.导入微信支付扩展包 二.微信支付工具类 1.签名工具类 2.微信支付 ...

  9. java微信支付v3系列——6.微信支付查询订单API

    目录 java微信支付v3系列--1.微信支付准备工作 java微信支付v3系列--2.微信支付基本配置 java微信支付v3系列--3.订单创建准备操作 java微信支付v3系列--4.创建订单的封 ...

最新文章

  1. JVM调优:打印所有-XX非标参数命令
  2. 飞鸽传书的这一新的通信方式采用云技术
  3. grafana导出pdf监控日报
  4. pycharm创建一个Hello
  5. php数组是有序,php判断一个数组是否为有序的方法
  6. 36数字在排序数组中出现的次数
  7. myeclipse中文界面改颜色_“颜色识别器”安卓APP功能详细介绍
  8. 带大家一起感受美国两日游
  9. 二端口输入阻抗和输出阻抗
  10. Python批量爬取谷歌原图,2021年最新可用版
  11. 性能分析26-tomcat优化P61
  12. SaaS、PaaS、IaaS是什么意思
  13. Java开发 - SpringCache初体验
  14. 以地理国情调查任务为基础的地图矢量化流程研究
  15. Git教程及常用命令
  16. firefox网页慢加载图片慢或加载不出来
  17. POJ3069-Saruman's Army-贪心水题
  18. service层的作业+mybatis中的重要组件
  19. 为什么浙江初中数学用计算机,计算器对初中数学学习几点看法
  20. String index out of range: -824264796 不明的原因导致驱动程序造成失败,请回报这个例外。

热门文章

  1. 能量英语(一)之激情英语
  2. 云旗OS助手火了!可一站式体验统信UOS
  3. python多窗口显示内容_如何在一个窗口中显示多个页面?
  4. SpringBoot整合Shiro学习(上)
  5. box2d的部分心得
  6. 计算机语言属于人类意识的客观内容,《2008年考研政治800题精解》世界的物质性和人的实践活动(5)...
  7. C++学习 std::tr1::shared_ptr使用的一点体会tr1库介绍
  8. 阿里云科技驱动“数字化转型”,助力中小企业发展“突围”
  9. 《Centos的下载详细步骤》
  10. 迷失在JAVA的咖啡杯中