From 20d7eafff28704d6b28509d58012c7c47f888b3f Mon Sep 17 00:00:00 2001 From: zhangye <654600784@qq.com> Date: Thu, 28 Apr 2022 15:20:25 +0800 Subject: [PATCH] =?UTF-8?q?20220428=E6=96=B0=E5=A2=9E=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E6=88=90=E5=8A=9F=E5=9B=9E=E8=B0=83=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E4=BF=AE=E6=94=B9accessToken=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ccsens/util/bean/wx/po/WxGetIp.java | 16 ++ .../bean/dto/NoticeResourceDecode.java | 145 ++++++++++++++++++ .../bean/dto/WxNativePayNotice.java | 43 ++++++ .../wechatutil/bean/vo/NativePayNoticeVo.java | 22 +++ .../payutil/wxnative/NativeUtils.java | 133 ++++++---------- .../ccsens/wechatutil/util/WxConstant.java | 4 +- .../wechatutil/wxcommon/WxCommonUtil.java | 45 +++++- 7 files changed, 311 insertions(+), 97 deletions(-) create mode 100644 util/src/main/java/com/ccsens/util/bean/wx/po/WxGetIp.java create mode 100644 wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/NoticeResourceDecode.java create mode 100644 wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/WxNativePayNotice.java create mode 100644 wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/NativePayNoticeVo.java diff --git a/util/src/main/java/com/ccsens/util/bean/wx/po/WxGetIp.java b/util/src/main/java/com/ccsens/util/bean/wx/po/WxGetIp.java new file mode 100644 index 0000000..289b167 --- /dev/null +++ b/util/src/main/java/com/ccsens/util/bean/wx/po/WxGetIp.java @@ -0,0 +1,16 @@ +package com.ccsens.util.bean.wx.po; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * @author __zHangSan + */ +@Data +public class WxGetIp extends WxBaseEntity{ + @JsonProperty("ipList") + private List ip_list; +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/NoticeResourceDecode.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/NoticeResourceDecode.java new file mode 100644 index 0000000..99a977c --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/NoticeResourceDecode.java @@ -0,0 +1,145 @@ +package com.ccsens.wechatutil.bean.dto; + +import lombok.Data; + +import java.util.List; +/** + * @author 逗 + */ +@Data +public class NoticeResourceDecode { + + public enum TRADE_STATE { + /*** 支付成功 */ + SUCCESS("SUCCESS","支付成功"), + /*** 转入退款 */ + REFUND("REFUND","转入退款"), + /*** 未支付 */ + NOTPAY("NOTPAY","未支付"), + /*** 已关闭 */ + CLOSED("CLOSED","已关闭"), + /*** 已撤销 */ + REVOKED("REVOKED","已撤销"), + /*** 用户支付中 */ + USERPAYING("USERPAYING","用户支付中"), + /*** 支付失败 */ + PAYERROR("PAYERROR","支付失败"); + + public String value; + public String phase; + TRADE_STATE(String value,String thePhase){ + this.value =value; + this.phase = thePhase; + } + } + + /*** 微信支付订单号 微信支付系统生成的订单号 */ + private String transaction_id; + /*** 订单金额 */ + private AmountBean amount; + /*** 应用ID */ + private String mchid; + /*** + * 交易状态,枚举值: + * SUCCESS:支付成功 + * REFUND:转入退款 + * NOTPAY:未支付 + * CLOSED:已关闭 + * REVOKED:已撤销(付款码支付) + * USERPAYING:用户支付中(付款码支付) + * PAYERROR:支付失败(其他原因,如银行返回失败) + */ + private String trade_state; + /*** 银行类型,采用字符串类型的银行标识 */ + private String bank_type; + /*** 支付完成时间 yyyy-MM-DDTHH:mm:ss+TIMEZONE*/ + private String success_time; + /*** 支付者 */ + private PayerBean payer; + /*** 商户订单号 商户系统内部订单号 */ + private String out_trade_no; + /*** 应用ID */ + private String appid; + /*** 交易状态描述*/ + private String trade_state_desc; + /*** + * 交易类型,枚举值: + * JSAPI:公众号支付 + * NATIVE:扫码支付 + * APP:APP支付 + * MICROPAY:付款码支付 + * MWEB:H5支付 + * FACEPAY:刷脸支付 + */ + private String trade_type; + /*** 附加数据 */ + private String attach; + /*** 场景信息 */ + private SceneInfoBean scene_info; + /*** 优惠功能 */ + private List promotion_detail; + + @Data + public static class AmountBean { + /*** 用户支付金额,单位为分 */ + private int payer_total; + /*** 订单总金额,单位为分 */ + private int total; + /*** 货币类型 */ + private String currency; + /*** 用户支付币种 */ + private String payer_currency; + } + + @Data + public static class PayerBean { + /*** 用户标识 */ + private String openid; + } + + @Data + public static class SceneInfoBean { + /*** 终端设备号(门店号或收银设备ID) */ + private String device_id; + } + + @Data + public static class PromotionDetailBean { + /*** 优惠券面额 */ + private int amount; + /*** 微信出资 */ + private int wechatpay_contribute; + /*** 券ID */ + private String coupon_id; + /*** 优惠范围 */ + private String scope; + /*** 商户出资 */ + private int merchant_contribute; + /*** 优惠名称 */ + private String name; + /*** 优惠类型 */ + private String type; + /*** 其他出资 */ + private int other_contribute; + /*** 优惠币种 */ + private String currency; + /*** 活动ID */ + private String stock_id; + /*** 单品列表 */ + private List goods_detail; + + @Data + public static class GoodsDetailBean { + /*** 商品备注 */ + private String goods_remark; + /*** 商品数量 */ + private int quantity; + /*** 商品优惠金额 */ + private int discount_amount; + /*** 商品编码 */ + private String goods_id; + /*** 商品单价 */ + private int unit_price; + } + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/WxNativePayNotice.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/WxNativePayNotice.java new file mode 100644 index 0000000..1917218 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/dto/WxNativePayNotice.java @@ -0,0 +1,43 @@ +package com.ccsens.wechatutil.bean.dto; + +import lombok.Data; + +/** + * @author 逗 + */ +@Data +public class WxNativePayNotice { + + /**"消息id"*/ + private String id; + /**"通知创建的时间,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE"*/ + private String create_time; + /**"通知的类型 SUCCESS"*/ + private String event_type; + /**"通知的资源数据类型 encrypt-resource"*/ + private String resource_type; + /**"回调摘要"*/ + private String summary; + /**"微信支付回调消息"*/ + private NoticeResource resource; + + @Data + public static class NoticeResource { + //"对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM ") + private String algorithm; + //"Base64编码后的开启/停用结果数据密文") + private String ciphertext; + //"附加数据") + private String associated_data; + //"原始回调类型 为transaction") + private String original_type; + //"加密使用的随机串") + private String nonce; + } + + @Data + public static class NoticeResourceDecode { + + } + +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/NativePayNoticeVo.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/NativePayNoticeVo.java new file mode 100644 index 0000000..c064697 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/NativePayNoticeVo.java @@ -0,0 +1,22 @@ +package com.ccsens.wechatutil.bean.vo; + +import lombok.Data; + +/** + * @author 逗 + */ +@Data +public class NativePayNoticeVo { + /**"code"*/ + private String code; + /**消息*/ + private String message; + + public NativePayNoticeVo() { + } + + public NativePayNoticeVo(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java b/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java index e226c46..96e050b 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java @@ -11,6 +11,10 @@ import com.google.zxing.common.BitMatrix; import okhttp3.HttpUrl; import org.apache.commons.lang3.RandomStringUtils; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; @@ -33,81 +37,11 @@ import java.util.UUID; public class NativeUtils { static String schema = "WECHATPAY2-SHA256-RSA2048"; - -// /** -// * 填写你的商户号 -// */ -// static String MCH_ID = "1624859575"; - -// /** -// * 填写证书序列号 -// */ -// static String SERIAL_NO = "71433832548290D95806FAF490878BB9B2677D4E"; - -// /** -// * 填写应用APP ID -// */ -// static String APP_ID = "wxcb60fcfeaddeb3e3"; -// -// /** -// * apiclient_key.pem文件地址 如C:\Users\Administrator\Desktop\wechat\apiclient_key.pem -// */ -// static String PATH_PEM = "C:\\Users\\dou\\Desktop\\wxpay\\apiclient_key.pem"; - /** * 请求地址 */ static String NATIVE_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/native"; -// -// /** -// * 填写你的返回地址 需要HTTPS返回地址 -// */ -// static String RETURN_ADDRESS = "https://test.tall.wiki/gateway/defaultwbs/debug"; -// public static void main(String[] args) { -// try { -// int money = 100; -// String description = "测试支付"; -// HttpUrl httpurl = HttpUrl.parse(NATIVE_URL); -// //签名表头信息 Authorization -// String orderId = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); -// String body = OrderData(orderId, money, description); -// String authorization = schema + " " + getToken("POST", httpurl, body, orderId); -// //下单调用的接口,JSON格式 -// String codeUrl = nativePostBody(NATIVE_URL, body, authorization); -// System.out.println(codeUrl); -// -// JSONObject jsonObject = JSONObject.parseObject(codeUrl); -// String code_url = jsonObject.getString("code_url"); -// if(StrUtil.isNotBlank(code_url)){ -// String s = generateQRCode(code_url); -// System.out.println(s); -// } -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } - -// static String OrderData(String orderId, int money, String description) { -// // 应用ID appid -// // 直连商户号 mchid -// // 商品描述 description -// //商户订单号 out_trade_no -// //通知地址 notify_url -// //订单金额 amount对象: 总金额 total 货币类型 currency -// JSONObject jsonObject = new JSONObject(); -// jsonObject.put("mchid", MCH_ID); -// jsonObject.put("out_trade_no", orderId); -// jsonObject.put("appid", APP_ID); -// jsonObject.put("description", description); -// jsonObject.put("notify_url", RETURN_ADDRESS); -// Map map = new HashMap<>(); -// map.put("total", money); -// map.put("currency", "CNY"); -// jsonObject.put("amount", map); -// return String.valueOf(jsonObject); -// -// } /** * 获取签名信息所有值 @@ -240,26 +174,51 @@ public class NativeUtils { return results; } - public static String generateQRCode(String code_url) throws WriterException, IOException { - //生成二维码 - //设置二维码的尺寸 - int width = 200; - int hight = 200; - //创建map - Map hints = new HashMap<>(); - hints.put(EncodeHintType.CHARACTER_SET,"UTF-8"); +// public static void main(String[] args) throws Exception { +// String apiV3 = "Bb507Ek1mr95aAvvv9OMp2i9eWxZSv1G"; +// String nonce = "bexg7pfYD50D"; +// String ciphertext="i01zjaHjODHIkNkIXal6lKrfxrYtp2IZFe2NeUL/tU7si3p/G52XXqxTRQLuM03D1MTImmzLXocxVOgjNmAT3wb5/2RB6ty0HT2RJzGDnAkR0OxMQseUyiOUNRW3skJtj2/somJ/O1yHI2uktJLd1hitfu2TqoFe18ueBZdFYFHqnSKoayc3qTCKFsNLQ/gArvM0ODG3qCa2x9PypmcpDEx2U0KKdNmn/kLqDjIE6RdW9RpAhG+5wqOw3717sD5uvIg66PvUp7MdGMDiDi1SdcUqRm8x7Fl43uSIq9bAcsJxW4IT877tzkDHNjSY37iOFr9vuFpjMwLnhkZfClCfivh2R9ShrmFVlR1sXikr7rYQQGn0coSQZJ09UVd0UuwSxd5SNiLISkB/MzuLfvFSe2YTFvM/MR4iJy+JLQIEBZc7ZNhv4qrcRUOnjQlWO7G9+iUpFrviItzC7eN1KTkyOBs6QPI28uUDOt/OV548i+0Pf6xmD+rOS+6vvl1WR9F4LFUvCWr3w5uMmejyCRJmWoQY12boiwT+FxsdF0NJnoSheiY+bBZfcDJ/udpo2PBHuryJ"; +// String associated_data = "transaction"; +// +// byte[] key = apiV3.getBytes("UTF-8"); +// WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key); +// String decryptToString = aesUtil.decryptToString(associated_data.getBytes("UTF-8"),nonce.getBytes("UTF-8"),ciphertext); +// System.out.println(decryptToString); +// } + + /** + * 解密微信ApiV3消息 + */ + public static class WxAPIV3AesUtil { + + static final int KEY_LENGTH_BYTE = 32; + static final int TAG_LENGTH_BIT = 128; + private final byte[] aesKey; - //创建矩阵对象 调用谷歌提供的 - BitMatrix bitMatrix = new MultiFormatWriter().encode(code_url, BarcodeFormat.QR_CODE, width, hight, hints); + public WxAPIV3AesUtil(byte[] key) { + if (key.length != KEY_LENGTH_BYTE) { + throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); + } + this.aesKey = key; + } - //创建二维码生成路径 - String filePath = "C:\\Users\\dou\\Desktop\\wxpay\\"; - String fileName = RandomStringUtils.randomAlphanumeric(10)+ ".jpg"; + public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) + throws GeneralSecurityException, IOException { + try { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - Path path = FileSystems.getDefault().getPath(filePath,fileName); + SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); + GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); - //将创建的矩阵转换成图片 - MatrixToImageWriter.writeToPath(bitMatrix,"jpg",path); - return filePath + fileName; + 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); + } + } } } diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java b/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java index 767e804..cc85c23 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java @@ -16,8 +16,8 @@ public class WxConstant { /*** 默认小程序 */ public static final String ANYRING = "anyring"; - - + /*** 获取微信服务器IP地址 */ + public static final String URL_GET_WX_IP = "https://api.weixin.qq.com/cgi-bin/get_api_domain_ip?access_token=%1$s"; /*** 公众号获取accessToken */ public static final String URL_GET_OAUTH2_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%1$s&secret=%2$s&code=%3$s&grant_type=authorization_code"; /*** 公众号通过模板发送订阅消息 */ diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java b/wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java index be18896..12d7385 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java @@ -1,5 +1,6 @@ package com.ccsens.wechatutil.wxcommon; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import com.ccsens.util.JacksonUtil; @@ -7,6 +8,7 @@ import com.ccsens.util.RedisUtil; import com.ccsens.util.WebConstant; import com.ccsens.util.bean.wx.po.WxAccessToken; import com.ccsens.util.bean.wx.po.WxApiTicket; +import com.ccsens.util.bean.wx.po.WxGetIp; import com.ccsens.util.exception.BaseException; import com.ccsens.wechatutil.util.WxCodeError; import com.ccsens.wechatutil.util.WxConstant; @@ -40,7 +42,20 @@ public class WxCommonUtil { public static String getAccessToken(String appId, String secret) throws BaseException { log.info("获取accessToken,appid:{}", appId.substring(appId.length() - 4)); Object obj = util.redisUtil.get(WxConstant.ACCESS_TOKEN + appId); - if (obj == null || StrUtil.isBlank((String) obj)) { + //使用redis内的token获取微信的服务器id,验证是否正确 + boolean flag = false; + if (obj != null && StrUtil.isNotBlank((String) obj)) { + WxGetIp wxGetIp = null; + try { + wxGetIp = getWxIp((String) obj); + }catch (Exception e){ + log.error("accessToken不正确,需要重新获取"); + } + if(ObjectUtil.isNotNull(wxGetIp)){ + flag = true; + } + } + if (!flag) { WxAccessToken wxAccessToken; String url = String.format(WxConstant.URL_GET_ACCESS_TOKEN, appId, secret); String response = HttpRequest.get(url).execute().body(); @@ -67,14 +82,33 @@ public class WxCommonUtil { return (String) obj; } + /** + * 获取微信服务器IP地址 + */ + public static WxGetIp getWxIp(String accessToken) throws BaseException { + log.info("获取微信服务器IP地址"); + WxGetIp wxGetIp; + String url = String.format(WxConstant.URL_GET_WX_IP, accessToken); + String response = HttpRequest.get(url).execute().body(); + log.info("获取微信服务器IP地址返回: {}", response); + try { + if (StrUtil.isEmpty(response) || null == (wxGetIp = JacksonUtil.jsonToBean(response, WxGetIp.class))) { + return null; + } + } catch (IOException e) { + return null; + } + if (null != wxGetIp.getErrcode()) { + return null; + } + return wxGetIp; + } /** * 通过accessToken获取临时票据ticket */ public static String getTicketByAccessToken(String accessToken) throws BaseException { log.info("获取ticket"); -// Object obj = util.redisUtil.get(WxConstant.ACCESS_TOKEN + appId); -// if (obj == null || StrUtil.isBlank((String) obj)) { WxApiTicket wxApiTicket; String url = String.format(WxConstant.URL_GET_API_TICKET, accessToken); String response = HttpRequest.get(url).execute().body(); @@ -93,11 +127,6 @@ public class WxCommonUtil { if (StrUtil.isEmpty(wxApiTicket.getTicket())) { throw new BaseException(WxCodeError.API_TICKET_ERROR); } -// util.redisUtil.set(WebConstant.Wx.ACCESS_TOKEN + appId, wxAccessToken.getAccessToken(), WebConstant.Wx.EXPIRE_TIME); -// log.info("存储access_token:{}", wxAccessToken.getAccessToken()); -// return wxAccessToken.getAccessToken(); -// } -// log.info("读取reids的token:{}", obj); return wxApiTicket.getTicket(); }