先附上官方文档:微信支付-开发者文档

一.微信商家券回调参数样式

1.微信回调的参数分两部分,这里两部分参数都要用的!

A.Body

{ "id":"8b33f79f-8869-5ae5-b41b-3c0b59f957d0","create_time":"2019-12-12T16:54:38+08:00","resource_type":"encrypt-resource","event_type":"COUPON.SEND","summary":"商家券领券通知","resource":{"original_type":"coupon","algorithm":"AEAD_AES_256_GCM","ciphertext":"xxx","associated_data":"coupon","nonce":"j9g1wAzF9Xn1"}
} 

B.请求头

这里只取用的请求头

Wechatpay-Nonce: c5ac7061fccab6bc5ac7061fccab6bc
Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvCtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/O==
Wechatpay-Timestamp: 1555555555
Wechatpay-Serial: 5157F09EFDC096DE155157F09EFDC096DE15EB11

2.接受回调参数,建议采用注解,不同公司封装不同的不一定接收到参数

@RequestBody String body,
@RequestHeader("Wechatpay-Serial") String wechatpaySerial,
@RequestHeader("Wechatpay-Signature") String wechatpaySignature,
@RequestHeader("Wechatpay-Timestamp") String wechatpayTimestamp,
@RequestHeader("Wechatpay-Nonce") String wechatpayNonce)

官方文档用的是CloseableHttpResponse response接收参数的,body= response.getBody

二.上代码,采用的是实时刷新在线证书临时缓存证书的方式,不需要读取本地证书

1.接收参数

@ApiOperation("微信商家券领券事件回调通知API")@RequestMapping(value="/callback")Map<String, String> stockCouponsReceiveCallback(@RequestBody String params,@RequestHeader("Wechatpay-Serial") String wechatpaySerial,@RequestHeader("Wechatpay-Signature") String wechatpaySignature,@RequestHeader("Wechatpay-Timestamp") String wechatpayTimestamp,@RequestHeader("Wechatpay-Nonce") String wechatpayNonce);

2.方法实现

   @Overridepublic Map<String, String> stockCouponsReceiveCallback(String postData,String wechatpaySerial,String wechatpaySignature,String wechatpayTimestamp,String wechatpayNonce) {CallbackParams callbackParams = JSONObject.parseObject(postData, CallbackParams.class);//对响应验签,和应答签名做比较,使用微信平台证书.boolean verify = false;try {if (CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {//如果证书容器为空,调用刷新方法this.refreshCertificate();}X509Certificate certificate = CERTIFICATE_MAP.get(wechatpaySerial);// 构造验签名串final String signatureStr = createSign(wechatpayTimestamp, wechatpayNonce, postData);// 加载SHA256withRSA签名器Signature signer = Signature.getInstance("SHA256withRSA");// 用微信平台公钥对签名器进行初始化signer.initVerify(certificate);// 把我们构造的验签名串更新到签名器中signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));// 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证verify = signer.verify(Base64Utils.decodeFromString(wechatpaySignature));} catch (Exception e) {e.printStackTrace();log.error("微信商家券回调验证签名错误 : " + e.getMessage());throw new BaseException("60170001", "微信商家券回调验证签名错误 : " + e.getMessage());}if(!verify){//签名验证失败return Collections.singletonMap("error", "invalid wechat pay callback");}String eventType = callbackParams.getEvent_type();if (!Objects.equals(eventType, "COUPON.SEND")) {log.error("微信商家券回调官方回调类型出错,不处理");//回调类型错误return Collections.singletonMap("error", "wechat pay event type is not matched");}String data = this.decrypt(callbackParams);StockCouponReceiveConsumeRequest consumeData = JSON.parseObject(data, StockCouponReceiveConsumeRequest.class);//业务处理,写自己的业务代码Map<String, String> map = new HashMap<>();map.put("code", "SUCCESS");map.put("message","成功");return map;}
    /*** 商家券回调-解密参数*/private String decrypt(CallbackParams callbackParams) {CallbackParams.Resource resource = callbackParams.getResource();String associatedData = resource.getAssociated_data();String nonce = resource.getNonce();String ciphertext = resource.getCiphertext();String data = this.decryptResponseBody(associatedData, nonce, ciphertext);Assert.hasText(data, "decryptData is required");return data;}
    /*** 商家券回调-请求时设置签名 组件*/private static String createSign(String timestamp,String nonce,String body) {return timestamp + "\n"+ nonce + "\n"+ body + "\n";}
  /*** 商家券回调-解密响应体*/public String decryptResponseBody(String associatedData, String nonce, String ciphertext) {try {Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//TODO:这里需要V3的KEYSecretKeySpec key = new SecretKeySpec(WEIXIN_APIV3KEY.getBytes(), "AES");GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));cipher.init(Cipher.DECRYPT_MODE, key, spec);cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));byte[] bytes;try {bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));} catch (GeneralSecurityException e) {log.error("微信商家券密文解密错误 : " + e.getMessage());throw new BaseException("60170001", "微信商家券密文解密错误 : " + e.getMessage());}return new String(bytes, StandardCharsets.UTF_8);} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException  e) {log.error("微信商家券密文解密错误 : " + e.getMessage());throw new BaseException("60170001", "微信商家券密文解密错误 : " + e.getMessage());}}
    /*** 微信平台证书容器  key = 序列号  value = 证书对象*/private static final Map<String, X509Certificate> CERTIFICATE_MAP = new ConcurrentHashMap<>(); /*** 商家券回调-调用/v3/certificates接口获取证书列表加入证书容器*/private synchronized void refreshCertificate() throws Exception{String url = "https://api.mch.weixin.qq.com/v3/certificates";// 签名String authorization = HttpUrlUtil.getToken(String.valueOf(HttpMethod.GET), url, "");HttpHeaders headers = new HttpHeaders();headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));headers.setContentType(MediaType.APPLICATION_JSON);headers.add("Authorization", authorization);headers.add("User-Agent", "X-Pay-Service");RequestEntity<?> requestEntity = new RequestEntity<>(headers, HttpMethod.GET,new URI(url));RestOperations restOperations = new RestTemplate();ResponseEntity<ObjectNode> responseEntity = restOperations.exchange(requestEntity, ObjectNode.class);ObjectNode bodyObjectNode = responseEntity.getBody();if (Objects.isNull(bodyObjectNode)) {log.error("微信商家券获取微信v3证书时发生错误,无法获取响应体,不处理");throw new BaseException("60170001", "微信商家券获取微信v3证书时发生错误,无法获取响应体");}ArrayNode certificates = bodyObjectNode.withArray("data");if (certificates.isArray() && certificates.size() > 0) {CERTIFICATE_MAP.clear();CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");certificates.forEach(objectNode -> {JsonNode encryptCertificate = objectNode.get("encrypt_certificate");String associatedData = encryptCertificate.get("associated_data").asText();String nonce = encryptCertificate.get("nonce").asText();String ciphertext = encryptCertificate.get("ciphertext").asText();String publicKey = decryptResponseBody(associatedData, nonce, ciphertext);ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));try {X509Certificate certificate = (X509Certificate)certificateFactory.generateCertificate(inputStream);String responseSerialNo = objectNode.get("serial_no").asText();CERTIFICATE_MAP.put(responseSerialNo, certificate);} catch (CertificateException e) {log.error("微信商家券获取微信v3证书时发生错误 : " + e.getMessage());throw new BaseException("60170001","微信商家券获取微信v3证书时发生错误,原因: " + e.getMessage());}});}}
import lombok.Data;/*** @description: 微信支付回调请求参数*/
@Data
public class CallbackParams {/*** 通知Id*/private String id;/*** 通知创建时间*/private String create_time;/*** 通知类型*/private String event_type;/*** 通知数据类型*/private String resource_type;/*** 回调摘要*/private String summary;/*** 通知数据*/private Resource resource;@Datapublic static class Resource {/*** 对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM。*/private String algorithm;/*** Base64编码后的开启/停用结果数据密文。*/private String ciphertext;/*** 附加数据。*/private String associated_data;/*** 加密使用的随机串。*/private String nonce;/*** 原始回调类型。*/private String original_type;}}

HttpUrlUtil工具类


@Slf4j
public class HttpUrlUtil {/*** 商户私钥*/public static String WEIXIN_PRIVATEKEY = "商户私钥";public static String SCHEMA = "WECHATPAY2-SHA256-RSA2048";//public static String WEIXIN_MCHID= "服务商号";public static String WEIXIN_MCHSERIALNO= "本地证书号";/*** 获取加密串** @param method* @param url* @param body* @return*/public static String getToken(String method, String url, String body) {String nonceStr = RandomDigital.randomNumber(32);long timestamp = System.currentTimeMillis() / 1000;HttpUrl httpUrl = HttpUrl.parse(url);String message = buildMessage(method, httpUrl, timestamp, nonceStr, body);String signature = null;try {signature = sign(message.getBytes(StandardCharsets.UTF_8));} catch (Exception e) {e.printStackTrace();}return SCHEMA + " mchid=\"" + WEIXIN_MCHID + "\"," + "nonce_str=\"" + nonceStr + "\"," + "timestamp=\"" + timestamp + "\"," + "serial_no=\""+ WEIXIN_MCHSERIALNO + "\"," + "signature=\"" + signature + "\"";}/*** 得到签名字符串*/public static String sign(byte[] message) throws Exception {Signature sign = Signature.getInstance("SHA256withRSA");PrivateKey privateKey = getPrivateKey();sign.initSign(privateKey);sign.update(message);return Base64.getEncoder().encodeToString(sign.sign());}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";}/*** 获取私钥。** @return 私钥对象*/public static PrivateKey getPrivateKey() throws IOException {String content = WEIXIN_ISV_PRIVATEKEY;try {//String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\s+", "");KeyFactory kf = KeyFactory.getInstance("RSA");return kf.generatePrivate(new PKCS8EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(privateKey)));} catch (NoSuchAlgorithmException e) {throw new RuntimeException("当前Java环境不支持RSA", e);} catch (InvalidKeySpecException e) {throw new RuntimeException("无效的密钥格式");}}
}

微信商家券V3版本领券回调,签名验证-JAVA相关推荐

  1. 微信小程序:微信商家券的领取

    微信小程序:微信商家券的领取 微信在去年推出了商家券,我前不久才接触,在制作领取程序的时候由于网上相关的资料很少,所以 走了不少弯路. 主要问题 这里可以注意到,微信券的领取需要传递三个参数,坑爹的是 ...

  2. 微信Native支付V3版本

    微信Native支付V3版本 微信支付在开发之前也是需要进行商户接入的 接入文档链接: https://pay.weixin.qq.com/index.php/core/home/login Nati ...

  3. 微信商家券对接wechatpay-apiv3

    源码:https://gitee.com/wsc58888/wx-stock-coupon.git 由于微信商家券是新出来的接口,微信的文档一直都是各种坑. 每次请求需要签名. 注意: post的请求 ...

  4. 微信支付API V3版本JAVA开发指南

    微信支付版本V3的Demo,在官方上下载下来,压根就是不能直接用的东西,你要想学会用,你就得一层一层的看源码,看文档,要求你事无巨细的做一个接入者. 如果接入API需要让人看源码来理解,我觉得是一件让 ...

  5. 微信V3版商家券小程序发券插件签名生成-JAVA

    一.使用范围 用于微信V3版商家券小程序发券插件签名的生成. https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_3_1.shtml 二.注意 ...

  6. 微信支付V3版本回调+验签流程

    本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中的后续之微信支付后的回调. 一.回调验签流程介绍 二.核心流程操作 本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中 ...

  7. PHP实现微信提现V3版本2022-5更新接口:商家转账到零钱

    微信官方又更新了接口... V3版本的微信商家转账到零钱的接口---俗称提现接口 注意事项 一:开通条件:需满足入驻满90天,连续正常交易30天,保持正常健康交易. 二:分为页面发起和api接口操作, ...

  8. 一文带你学会微信V3版本下单支付、退款、关单流程代码实操

    目录 开篇介绍 一.微信支付-Maven依赖加入和代码参数准备 二.商户私钥证书代码读取 三.微信订单支付系列接口URL配置 四.快速验证统一下单接口 五.查询订单支付状态验证 六.关闭订单状态验证 ...

  9. 微信支付v3版本npm包

    wechatpay-node-v3 # 微信支付v3 支持在ts和js中使用 欢迎大家加入一起完善这个api 前言 微信官方在2020-12-25正式开放了v3版本的接口,相比较旧版本v2有了不少改变 ...

最新文章

  1. 手把手教你写ORM(三)
  2. Kubectl 的替代品:kubeman
  3. java 微信多媒体文件_java微信接口之三—上传多媒体文件
  4. python面向对象编程的优点-Python入门之面向对象编程(一)面向对象概念及优点
  5. SpringBoot随笔
  6. 服务器更改IP(公网)地址后,Program Neighborhood客户端无法连接服务器
  7. 安全套接层Secure Sockets Layer,SSL
  8. linux ubuntu安装 mono,在 Ubuntu Server 上安装配置 Mono 生产环境
  9. jdbc下载mysql的驱动 mysql5版本
  10. V.PhyloMaker:维管束植物系统发育树构建实践
  11. 计算机组装与维护选教材,计算机组装与维护校本教材.doc
  12. PHP资源汇总-内容包括模板、框架、数据库、安全等方面的库和工具
  13. html引入阿里在线css文件夹,阿里字体css代码引入方法
  14. OpenCV笔记之六(4)——图像处理之颜色通道拆分、合并及颜色空间
  15. 百度收录自动化提交脚本 - python
  16. 推荐几款优秀的开源博客系统
  17. HDU 5964 平行四边形/Pland 【平面几何】
  18. ContOs的网络配置
  19. 台式机做U盘启动盘----win7旗舰版原装系统重装
  20. 数据同步工具—sqoop 2.x

热门文章

  1. iOS AppStore个人开发者账号申请
  2. Expandable Table Cell
  3. JAVA工厂模式优缺点_简单工厂模式、工厂模式和抽象工厂模式区别及优缺点
  4. yum install安装提示 File /usr/bin/yum, line 30 except KeyboardInterrupt, e:
  5. 深度学习机器学习理论知识:范数、稀疏与过拟合合集(2)有监督模型下的过拟合与正则化加入后缓解过拟合的原理
  6. android keyFrame
  7. 银行计算机sql试题答案,试题(Sql Advance) (120题)带答案.doc
  8. python实现英文句子反转功能。
  9. DirectUI框架GUIFW
  10. 如何使用js(jQuery)加载json?