From 93ed25d20266c7caa15db19b0d9f96674786364d Mon Sep 17 00:00:00 2001 From: zhangye <654600784@qq.com> Date: Tue, 26 Apr 2022 20:23:05 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7=E7=99=BB=E9=99=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/application-util-green.yml | 143 ---------- .../application-util-greenvalley.yml | 66 ----- .../resources/application-util-prodsd.yml | 82 ------ .../ccsens/ptos_tall/service/UserService.java | 24 +- .../ptos_tall/service/WxUserService.java | 16 +- .../java/com/ccsens/util/WebConstant.java | 2 +- .../ccsens/util/bean/wx/po/WxApiTicket.java | 20 ++ .../java/com/ccsens/util/wx/WxGzhUtil.java | 21 +- .../wechatutil/bean/vo/WxNativePay.java | 120 ++++++++ .../payutil/wxnative/NativePay.java | 47 ++++ .../payutil/wxnative/NativeUtils.java | 265 ++++++++++++++++++ .../ccsens/wechatutil/util/WxCodeError.java | 1 + .../ccsens/wechatutil/util/WxConstant.java | 2 + .../wechatutil/wxcommon/WxCommonUtil.java | 36 +++ .../OfficialAccountMessageUtil.java | 2 +- 15 files changed, 535 insertions(+), 312 deletions(-) delete mode 100644 cloudutil/src/main/resources/application-util-green.yml delete mode 100644 cloudutil/src/main/resources/application-util-greenvalley.yml delete mode 100644 cloudutil/src/main/resources/application-util-prodsd.yml create mode 100644 util/src/main/java/com/ccsens/util/bean/wx/po/WxApiTicket.java create mode 100644 wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/WxNativePay.java create mode 100644 wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativePay.java create mode 100644 wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java diff --git a/cloudutil/src/main/resources/application-util-green.yml b/cloudutil/src/main/resources/application-util-green.yml deleted file mode 100644 index b087e99..0000000 --- a/cloudutil/src/main/resources/application-util-green.yml +++ /dev/null @@ -1,143 +0,0 @@ -#<<<<<<< HEAD -##服务端点暴露 -#management: -# endpoints: -# web: -# exposure: -# # 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*' -# include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream -## # 不暴露哪些端点 -## exclude: env,beans,configprops -# endpoint: -# health: -# # 是否展示健康检查详情 -# show-details: always -# health: -# redis: -# enabled: false -##eureka注册 -#eureka: -# client: -# service-url: -# # 指定eureka server通信地址,注意/eureka/小尾巴不能少 -# #defaultZone: http://admin:admin@peer1:8761/eureka/,http://admin:admin@peer2:8762/eureka/ -## defaultZone: http://admin:admin@49.233.89.188:7010/eureka/ -# defaultZone: http://admin:admin@192.168.0.99:7010/eureka/ -# instance: -# # 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server -# prefer-ip-address: true -# metadata-map: -# management: -# context-path: ${server.servlet.context-path:}/actuator -# home-page-url-path: ${server.servlet.context-path:}/ -# status-page-url-path: ${server.servlet.context-path:}/actuator/info -# health-check-url-path: ${server.servlet.context-path:}/actuator/health -#feign: -# client: -# config: -# default: -# connectTime: 5000 -# readTimeout: 5000 -# # NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。 -# # BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。 -# # HEADERS:记录BASIC级别的基础上,记录请求和响应的header。 -# # FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据 -# loggerLevel: basic -# hystrix: -# enabled: true -## sleuth -#logging: -# level: -# root: info -# org.springframework.cloud.sleuth: DEBUG -#spring: -## zipkin: -## base-url: http://49.233.89.188:9411 -## sleuth: -## sampler: -## # 采样率,模式0.1,也就是10%,为了便于观察效果,改为1.0,也就是100%。生产环境建议保持默认。 -## probability: 1.0 -# cloud: -# inetutils: -#======= -#服务端点暴露 -management: - endpoints: - web: - exposure: - # 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*' - include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream -# # 不暴露哪些端点 -# exclude: env,beans,configprops - endpoint: - health: - # 是否展示健康检查详情 - show-details: always - health: - redis: - enabled: false -#eureka注册 -eureka: - client: - service-url: - # 指定eureka server通信地址,注意/eureka/小尾巴不能少 - #defaultZone: http://admin:admin@peer1:8761/eureka/,http://admin:admin@peer2:8762/eureka/ - defaultZone: http://admin:admin@49.232.6.143:7010/eureka/ -# defaultZone: http://admin:admin@192.168.0.99:7010/eureka/ -# defaultZone: http://admin:admin@test.tall.wiki:7010/eureka/ - instance: - # 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server - prefer-ip-address: true - metadata-map: - management: - context-path: ${server.servlet.context-path:}/actuator - home-page-url-path: ${server.servlet.context-path:}/ - status-page-url-path: ${server.servlet.context-path:}/actuator/info - health-check-url-path: ${server.servlet.context-path:}/actuator/health -feign: - client: - config: - default: - connectTime: 5000 - readTimeout: 5000 - # NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。 - # BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。 - # HEADERS:记录BASIC级别的基础上,记录请求和响应的header。 - # FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据 - loggerLevel: basic - hystrix: - enabled: true -hystrix: - threadpool: - default: - coreSize: 200 #并发执行的最大线程数,默认10 - maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1 - queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5 - command: - default: - execution: - timeout: - enabled: true - isolation: - strategy: SEMAPHORE - thread: - timeoutInMilliseconds: 60000 -#ribbon的超时时间 -ribbon: - ReadTimeout: 60000 - ConnectTimeout: 60000 -# sleuth -logging: - level: - root: info - org.springframework.cloud.sleuth: DEBUG -spring: -# zipkin: -# base-url: http://49.233.89.188:9411 -# sleuth: -# sampler: -# # 采样率,模式0.1,也就是10%,为了便于观察效果,改为1.0,也就是100%。生产环境建议保持默认。 -# probability: 1.0 - cloud: - inetutils: - ignored-interfaces: ['VMware.*'] \ No newline at end of file diff --git a/cloudutil/src/main/resources/application-util-greenvalley.yml b/cloudutil/src/main/resources/application-util-greenvalley.yml deleted file mode 100644 index 0551b88..0000000 --- a/cloudutil/src/main/resources/application-util-greenvalley.yml +++ /dev/null @@ -1,66 +0,0 @@ -#服务端点暴露 -management: - endpoints: - web: - exposure: - # 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*' - include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream -# # 不暴露哪些端点 -# exclude: env,beans,configprops - endpoint: - health: - # 是否展示健康检查详情 - show-details: always - health: - redis: - enabled: false -#eureka注册 -eureka: - client: - service-url: - # 指定eureka server通信地址,注意/eureka/小尾巴不能少 - defaultZone: http://admin:admin@82.157.24.76:7010/eureka/ -# defaultZone: http://admin:admin@49.232.6.143:7010/eureka/ - instance: - # 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server - prefer-ip-address: true - metadata-map: - management: - context-path: ${server.servlet.context-path:}/actuator - home-page-url-path: ${server.servlet.context-path:}/ - status-page-url-path: ${server.servlet.context-path:}/actuator/info - health-check-url-path: ${server.servlet.context-path:}/actuator/health -feign: - client: - config: - default: - connectTime: 5000 - readTimeout: 5000 - # NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。 - # BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。 - # HEADERS:记录BASIC级别的基础上,记录请求和响应的header。 - # FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据 - loggerLevel: basic - hystrix: - enabled: true -hystrix: - command: - default: - execution: - isolation: - strategy: SEMAPHORE - thread: - timeoutInMilliseconds: 60000 -#ribbon的超时时间 -ribbon: - ReadTimeout: 60000 - ConnectTimeout: 60000 -# sleuth -logging: - level: - root: info - org.springframework.cloud.sleuth: DEBUG -spring: - cloud: - inetutils: - ignored-interfaces: ['VMware.*'] \ No newline at end of file diff --git a/cloudutil/src/main/resources/application-util-prodsd.yml b/cloudutil/src/main/resources/application-util-prodsd.yml deleted file mode 100644 index 7e21903..0000000 --- a/cloudutil/src/main/resources/application-util-prodsd.yml +++ /dev/null @@ -1,82 +0,0 @@ -#服务端点暴露 -management: - endpoints: - web: - exposure: - # 暴露xxx端点,如需暴露多个,用,分隔;如需暴露所有端点,用'*' - include: auditevents,caches,conditions,flyway,health,heapdump,httptrace,info,integrationgraph,jolokia,logfile,loggers,liquibase,metrics,mappings,prometheus,scheduledtasks,sessions,shutdown,threaddump,hystrix.stream -# # 不暴露哪些端点 - exclude: env,beans,configprops - endpoint: - health: - # 是否展示健康检查详情 - show-details: always - health: - redis: - enabled: false -#eureka注册 -eureka: - client: - service-url: - # 指定eureka server通信地址,注意/eureka/小尾巴不能少 - #defaultZone: http://admin:admin@peer1:8761/eureka/,http://admin:admin@peer2:8762/eureka/ - -# defaultZone: http://admin:admin@81.70.54.64:7010/eureka/ - defaultZone: http://admin:admin@121.36.3.207:7010/eureka/ - - instance: - # 是否注册IP到eureka server,如不指定或设为false,那就回注册主机名到eureka server - prefer-ip-address: true - metadata-map: - management: - context-path: ${server.servlet.context-path:}/actuator - home-page-url-path: ${server.servlet.context-path:}/ - status-page-url-path: ${server.servlet.context-path:}/actuator/info - health-check-url-path: ${server.servlet.context-path:}/actuator/health -feign: - client: - config: - default: - connectTime: 5000 - readTimeout: 5000 - # NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。 - # BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。 - # HEADERS:记录BASIC级别的基础上,记录请求和响应的header。 - # FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据 - loggerLevel: basic - hystrix: - enabled: true -hystrix: - threadpool: - default: - coreSize: 200 #并发执行的最大线程数,默认10 - maxQueueSize: 1000 #BlockingQueue的最大队列数,默认值-1 - queueSizeRejectionThreshold: 800 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5 - command: - default: - execution: - timeout: - enabled: true - isolation: - strategy: SEMAPHORE - thread: - timeoutInMilliseconds: 60000 -#ribbon的超时时间 -ribbon: - ReadTimeout: 60000 - ConnectTimeout: 60000 -# sleuth -logging: - level: - root: info - org.springframework.cloud.sleuth: DEBUG -spring: -# zipkin: -# base-url: http://140.143.228.3:9411 -# sleuth: -# sampler: -# # 采样率,模式0.1,也就是10%,为了便于观察效果,改为1.0,也就是100%。生产环境建议保持默认。 -# probability: 0.1 - cloud: - inetutils: - ignored-interfaces: ['VMware.*'] \ No newline at end of file diff --git a/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/UserService.java b/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/UserService.java index fa0e5d2..5cac618 100644 --- a/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/UserService.java +++ b/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/UserService.java @@ -241,23 +241,35 @@ public class UserService implements IUserService { UserVo.UserSign userSignVo; //获取微信信息并保存 log.info("公众号登陆,{}", code); - WxOauth2UserInfo wxOauth2UserInfo = WxGzhUtil.getOauth2UserInfo(identifyType, code); + WxOauth2UserInfo wxOauth2UserInfo = WxGzhUtil.getOauth2UserInfo(identifyType, code, null, null); log.info("获取用户的微信信息,{}", wxOauth2UserInfo); SysAuth theAuth; if (ObjectUtil.isNull(wxOauth2UserInfo)) { throw new BaseException(CodeEnum.NOT_SELECT_WX); } + if(wxOauth2UserInfo.getUnionId() == null){ + wxOauth2UserInfo.setUnionId(""); + } + SysAuthExample authExample = new SysAuthExample(); - authExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.OAUTH2_Wx.value) - .andIdentifierEqualTo(wxOauth2UserInfo.getOpenId()).andCredentialEqualTo(wxOauth2UserInfo.getUnionId()); + if(wxOauth2UserInfo.getUnionId() == null){ + authExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.OAUTH2_Wx.value) + .andIdentifierEqualTo(wxOauth2UserInfo.getOpenId()); + }else { + authExample.createCriteria().andIdentifyTypeEqualTo((byte) WebConstant.IDENTIFY_TYPE.OAUTH2_Wx.value) + .andIdentifierEqualTo(wxOauth2UserInfo.getOpenId()).andCredentialEqualTo(wxOauth2UserInfo.getUnionId()); + } List authList = sysAuthDao.selectByExample(authExample); if (CollectionUtil.isNotEmpty(authList)) { theAuth = authList.get(0); log.info("该用户已有微信登录的auth信息,{}", theAuth); } else { - SysAuthExample sysAuthExample = new SysAuthExample(); - sysAuthExample.createCriteria().andCredentialEqualTo(wxOauth2UserInfo.getUnionId()); - List sysAuthList = sysAuthDao.selectByExample(sysAuthExample); + List sysAuthList = null; + if(wxOauth2UserInfo.getUnionId() != null){ + SysAuthExample sysAuthExample = new SysAuthExample(); + sysAuthExample.createCriteria().andCredentialEqualTo(wxOauth2UserInfo.getUnionId()); + sysAuthList = sysAuthDao.selectByExample(sysAuthExample); + } if (CollectionUtil.isNotEmpty(sysAuthList)) { //添加认证方式 theAuth = new SysAuth(); diff --git a/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/WxUserService.java b/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/WxUserService.java index af581b8..5bcac05 100644 --- a/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/WxUserService.java +++ b/ptos_tall/src/main/java/com/ccsens/ptos_tall/service/WxUserService.java @@ -125,14 +125,16 @@ public class WxUserService implements IWxUserService { tokenBean.setAvatarUrl(userSign.getAvatarUrl()); log.info("生成token成功"); //根据eventKey更新redis内的用户关注信息 - String eventKey = notice.getEventKey(); - if (notice.getEventKey().startsWith(PtOsConstant.EVENT_KEY_PREFIX)) { - eventKey = eventKey.substring(PtOsConstant.EVENT_KEY_PREFIX.length()); + if(StrUtil.isNotEmpty(notice.getEventKey())){ + String eventKey = notice.getEventKey(); + if (notice.getEventKey().startsWith(PtOsConstant.EVENT_KEY_PREFIX)) { + eventKey = eventKey.substring(PtOsConstant.EVENT_KEY_PREFIX.length()); + } + String key = PtOsConstant.OFFICIAL_EVENT_KEY + eventKey; + long seconds = 60L * 100L; + redisUtil.set(key,tokenBean,seconds); + log.info("存reids--key:{}",key); } - String key = PtOsConstant.OFFICIAL_EVENT_KEY + eventKey; - long seconds = 60L * 100L; - redisUtil.set(key,tokenBean,seconds); - log.info("存reids--key:{}",key); break; case "unsubscribe": //用openId查找用户 diff --git a/util/src/main/java/com/ccsens/util/WebConstant.java b/util/src/main/java/com/ccsens/util/WebConstant.java index a50ba09..035a05c 100644 --- a/util/src/main/java/com/ccsens/util/WebConstant.java +++ b/util/src/main/java/com/ccsens/util/WebConstant.java @@ -361,7 +361,7 @@ public class WebConstant { public enum IDENTIFY_TYPE { Wxmp(0,"微信小程序"), Phone(1,"电话") , Email(2,"Email"), Account(3,"账号") - ,OAUTH2_Wx(4,"微信公众号网页授权登录"),Wx_H5(5,"网页微信登陆") + ,OAUTH2_Wx(8,"微信公众号网页授权登录"),Wx_H5(5,"网页微信登陆") ,OAUTH2_WeiBo(6,"微博"),WxEnterprise(7, "企业微信"), OFFICIAL_ACCOUNT_WX(9,"微信公众号登录"),Wx_H5_TEST(10,"网页微信登陆测试"); // 钉钉是11 diff --git a/util/src/main/java/com/ccsens/util/bean/wx/po/WxApiTicket.java b/util/src/main/java/com/ccsens/util/bean/wx/po/WxApiTicket.java new file mode 100644 index 0000000..32614a8 --- /dev/null +++ b/util/src/main/java/com/ccsens/util/bean/wx/po/WxApiTicket.java @@ -0,0 +1,20 @@ +package com.ccsens.util.bean.wx.po; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @author 逗 + */ +@Data +public class WxApiTicket { + @JsonProperty("errcode") + private Integer errcode; + @JsonProperty("errmsg") + private String errmsg; + @JsonProperty("ticket") + private String ticket; + @JsonProperty("expires_in") + private Long expiresIn; +} diff --git a/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java b/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java index b6dbb52..42c298a 100644 --- a/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java +++ b/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java @@ -1,5 +1,6 @@ package com.ccsens.util.wx; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import com.alibaba.fastjson.JSONException; @@ -77,8 +78,12 @@ public class WxGzhUtil { = "https://api.weixin.qq.com/sns/userinfo?access_token=%1$s&openid=%2$s"; public static final String MESSAGE_TEMPLATE_SEND = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%1$s"; - private static final String APPID = "wx7af1bf1e14facf82"; - private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9"; +// private static final String APPID = "wx7af1bf1e14facf82"; +// private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9"; + // TODO ;临时更换默认appId + private static final String APPID = "wxcb60fcfeaddeb3e3"; + private static final String SECRET = "e6d27d1b2a0a89b16ec61fdb5e628034"; + private static final String APPID_H5_TEST = "wxd1842e073e0e6d91"; private static final String SECRET_H5_TEST = "96d69b79039caf92a2abafa999880cad"; @@ -295,9 +300,9 @@ public class WxGzhUtil { * @param code OAuth2授权码 * @return WxOauth2AccessToken */ - public static WxOauth2AccessToken getOauth2AccessToken(WebConstant.IDENTIFY_TYPE identifyType,String code) throws BaseException { + public static WxOauth2AccessToken getOauth2AccessToken(WebConstant.IDENTIFY_TYPE identifyType,String code,String appId, String secret) throws BaseException { WxOauth2AccessToken wxOauth2AccessToken = null; - String url = String.format(URL_GET_OAUTH2_ACCESS_TOKEN,appId(identifyType),secret(identifyType),code); + String url = String.format(URL_GET_OAUTH2_ACCESS_TOKEN,appId,secret,code); String response = HttpRequest.get(url).execute().body(); log.info("url: {}\nresponse: {}",url,response); try { @@ -355,11 +360,15 @@ public class WxGzhUtil { * @return WxOauth2UserInfo * @throws BaseException 异常 */ - public static WxOauth2UserInfo getOauth2UserInfo(WebConstant.IDENTIFY_TYPE identifyType,String code) throws BaseException { + public static WxOauth2UserInfo getOauth2UserInfo(WebConstant.IDENTIFY_TYPE identifyType,String code,String appId, String secret) throws BaseException { if (StrUtil.isNotBlank(PropUtil.wxH5) && "0".equals(PropUtil.wxH5)){ identifyType = WebConstant.IDENTIFY_TYPE.Wx_H5_TEST; } - WxOauth2AccessToken wxOauth2AccessToken = getOauth2AccessToken(identifyType,code); + if(ObjectUtil.isNull(appId) || ObjectUtil.isNull(secret)){ + appId = appId(identifyType); + secret = secret(identifyType); + } + WxOauth2AccessToken wxOauth2AccessToken = getOauth2AccessToken(identifyType,code,appId,secret); return getOauth2UserInfo(wxOauth2AccessToken.getAccessToken(),wxOauth2AccessToken.getOpenId()); } diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/WxNativePay.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/WxNativePay.java new file mode 100644 index 0000000..610a304 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/vo/WxNativePay.java @@ -0,0 +1,120 @@ +package com.ccsens.wechatutil.bean.vo; + +import lombok.Data; + +import java.util.List; + +/** + * @author 逗 + */ +@Data +public class WxNativePay { + /** + * 应用ID 是 + */ + private String appid; + /** + * 直连商户号 是 + */ + private String mchid; + /** + * 商品描述 是 + */ + private String description; + /** + * 商户订单号 是 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 + */ + private String out_trade_no; + /** + * 交易结束时间 否 + */ + private String time_expire; + /** + * 附加数据 否 + */ + private String attach; + /** + * 通知地址 是 + */ + private String notify_url; + /** + * 订单优惠标记 否 + */ + private String goods_tag; + /** + * 订单金额 是 + */ + private Amount amount; + /** + * 优惠功能 + */ + private Detail detail; + /** + * 场景信息 + */ + private SceneInfo scene_info; + /** + * 结算信息 + */ + private SceneInfo settle_info; + + @Data + public static class Amount{ + //总金额 是 单位为分。 + private int total; + //货币类型 + private String currency; + } + + @Data + public static class Detail{ + //订单原价 单位为分。 + private int cost_price; + //商品小票ID + private String invoice_id; + //单品列表 + private List goods_detail; + } + + @Data + public static class GoodsDetail{ + //商户侧商品编码 是 + private String merchant_goods_id; + //微信支付商品编码 否 + private String wechatpay_goods_id; + //商品名称 + private String goods_name; + //商品数量 是 + private int quantity; + //商品单价 是 + private int unit_price; + } + + @Data + public static class SceneInfo{ + //用户终端IP + private String payer_client_ip; + //商户端设备号 + private String device_id; + //商户门店信息 + private StoreInfo store_info; + } + + @Data + public static class StoreInfo{ + //门店编号 是 + private String id; + //门店名称 + private String name; + //地区编码 + private String area_code; + //详细地址 + private String address; + } + + @Data + public static class SettleInfo{ + //是否指定分账 + private boolean profit_sharing; + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativePay.java b/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativePay.java new file mode 100644 index 0000000..0261d04 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativePay.java @@ -0,0 +1,47 @@ +package com.ccsens.wechatutil.payutil.wxnative; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.ccsens.util.QrCodeUtil; +import com.ccsens.wechatutil.bean.vo.WxNativePay; +import lombok.extern.slf4j.Slf4j; +import okhttp3.HttpUrl; + +/** + * @author 逗 + */ +@Slf4j +public class NativePay { + + /** + * 生成订单,返回收款二维码 + * @param serialNo 证书序列号 + * @param pathPem 秘钥文件地址 + * @param wxNativePay 订单信息 + * @param filePath 二维码存放地址(前缀路径) + * @return 返回二维码访问路径(不包含前缀) + */ + public static String generateOrder( String serialNo, String pathPem, WxNativePay wxNativePay, String filePath){ + String path = ""; + try { + HttpUrl httpurl = HttpUrl.parse(NativeUtils.NATIVE_URL); + String body = String.valueOf(JSON.parseObject(JSON.toJSONString(wxNativePay))); + String authorization = NativeUtils.schema + " " + + NativeUtils.getToken("POST", httpurl, body, wxNativePay.getOut_trade_no(), wxNativePay.getMchid(), serialNo, pathPem); + //下单调用的接口,JSON格式 + String codeUrl = NativeUtils.nativePostBody(NativeUtils.NATIVE_URL, body, authorization); + log.info("调用微信下单返回:{}", codeUrl); + JSONObject jsonObject = JSONObject.parseObject(codeUrl); + String url = jsonObject.getString("code_url"); + if(StrUtil.isNotBlank(url)){ + path = QrCodeUtil.getQrCodeWithUtf8(url,filePath); + }else { + path = String.valueOf(jsonObject); + } + } catch (Exception e) { + e.printStackTrace(); + } + return path; + } +} 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 new file mode 100644 index 0000000..e226c46 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/payutil/wxnative/NativeUtils.java @@ -0,0 +1,265 @@ +package com.ccsens.wechatutil.payutil.wxnative; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import okhttp3.HttpUrl; +import org.apache.commons.lang3.RandomStringUtils; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + + +/** + * @author 逗 + */ +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); +// +// } + + /** + * 获取签名信息所有值 + * + * @param method 请求方法 + * @param url URL地址 + * @param body BOdy参数 + * @return + */ + static String getToken(String method, HttpUrl url, String body, String orderId, String mchId, String serialNo, String pathPem) throws Exception { + //获得系统时间,把毫秒换算成秒 /1000 + long timestamp = System.currentTimeMillis() / 1000; + String message = buildMessage(method, url, timestamp, orderId, body); + String signature = sign(message.getBytes("utf-8"), pathPem); + return "mchid=\"" + mchId + "\"," + + "nonce_str=\"" + orderId + "\"," + + "timestamp=\"" + timestamp + "\"," + + "serial_no=\"" + serialNo + "\"," + + "signature=\"" + signature + "\""; + } + + /** + * 拼接明文数值 + * + * @param method 请求方法 GET or POST + * @param url 网络请求方法地址 取除域名项 + * @param timestamp 时间戳 + * @param nonceStr 随机数 + * @param body GET请求不需要Body参数,POST需要Body + * @return + */ + static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) { + String canonicalUrl = url.encodedPath(); + //get请求自动做了校验,会把空字符串进行识别,注意是空字符串!!!!! + if (url.encodedQuery() != null) { + canonicalUrl += "?" + url.encodedQuery(); + } + //官方的方法自动做了换行的所有动作,注意唤起支付的参数不一样需要更换(这里是统一下单所以直接照搬即可) + return method + "\n" + + canonicalUrl + "\n" + + timestamp + "\n" + + nonceStr + "\n" + + body + "\n"; + } + + /** + * 签名加密 + * + * @param message + * @return + * @throws NoSuchAlgorithmException + * @throws SignatureException + * @throws IOException + * @throws InvalidKeyException + */ + static String sign(byte[] message, String pathPem) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException { + //加密方式 + Signature sign = Signature.getInstance("SHA256withRSA"); + //私钥,通过getPrivateKey来获取,这是个方法可以接调用 ,需要的是_key.pem文件的绝对路径配上文件名 + sign.initSign(getPrivateKey(pathPem)); + sign.update(message); + + return Base64.getEncoder().encodeToString(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"); + 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("无效的密钥格式"); + } + } + + /** + * Post请求带Body参数 + * + * @param actionUrl + * @param params + * @param requestString + * @return + * @throws IOException + */ + public static String nativePostBody(String actionUrl, String params, String requestString) + throws IOException { + String serverURL = actionUrl; + StringBuffer sbf = new StringBuffer(); + String strRead = null; + URL url = new URL(serverURL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + //请求post方式 + connection.setRequestMethod("POST"); + connection.setDoInput(true); + connection.setDoOutput(true); + //header内的的参数在这里set + connection.setRequestProperty("Content-Type", "application/json"); + //Native支付需要的参数表头参数 + connection.setRequestProperty("Authorization", requestString); + connection.connect(); + OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); + //body参数放这里 + writer.write(params); + writer.flush(); + InputStream is = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); + while ((strRead = reader.readLine()) != null) { + sbf.append(strRead); + sbf.append("\r\n"); + } + reader.close(); + connection.disconnect(); + String results = sbf.toString(); + 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"); + + //创建矩阵对象 调用谷歌提供的 + BitMatrix bitMatrix = new MultiFormatWriter().encode(code_url, BarcodeFormat.QR_CODE, width, hight, hints); + + //创建二维码生成路径 + String filePath = "C:\\Users\\dou\\Desktop\\wxpay\\"; + String fileName = RandomStringUtils.randomAlphanumeric(10)+ ".jpg"; + + Path path = FileSystems.getDefault().getPath(filePath,fileName); + + //将创建的矩阵转换成图片 + MatrixToImageWriter.writeToPath(bitMatrix,"jpg",path); + return filePath + fileName; + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxCodeError.java b/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxCodeError.java index 0f2a505..d8322c9 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxCodeError.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxCodeError.java @@ -10,6 +10,7 @@ public class WxCodeError extends CodeError { public static final Code WX_HTTP_ERROR = new Code(1,"微信接口请求失败", true); public static final Code ACCESS_TOKEN_ERROR = new Code(2,"accessToken获取失败", true); public static final Code OPENID_ERROR = new Code(2,"openId获取失败", true); + public static final Code API_TICKET_ERROR = new Code(3,"API_TICKET获取失败", true); } 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 8111685..7a2ff0a 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java @@ -26,6 +26,8 @@ public class WxConstant { public static final String QR_CODE_CREATE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}"; /**根据openId获取用户信息*/ public static final String USER_INFO = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%1$s&openid=%2$s&lang=zh_CN"; + /*** 获取临时票据api_ticket */ + public static final String URL_GET_API_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%1$s&type=jsapi"; /*** 小程序登录路径 */ 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 897f7df..be18896 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/wxcommon/WxCommonUtil.java @@ -6,6 +6,7 @@ import com.ccsens.util.JacksonUtil; 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.exception.BaseException; import com.ccsens.wechatutil.util.WxCodeError; import com.ccsens.wechatutil.util.WxConstant; @@ -65,4 +66,39 @@ public class WxCommonUtil { log.info("读取reids的token:{}", obj); return (String) obj; } + + + /** + * 通过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(); + log.info("getApiTicke: {}", response); + try { + if (StrUtil.isEmpty(response) || null == (wxApiTicket = JacksonUtil.jsonToBean(response, WxApiTicket.class))) { + throw new BaseException(WxCodeError.WX_HTTP_ERROR); + } + } catch (IOException e) { + throw new BaseException(e.getMessage()); + } + + if (null != wxApiTicket.getErrcode() && wxApiTicket.getErrcode() != 0) { + throw new BaseException(wxApiTicket.getErrcode(), wxApiTicket.getErrmsg()); + } + 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(); + } + } diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java b/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java index 9525d65..a14718b 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java @@ -116,7 +116,7 @@ public class OfficialAccountMessageUtil { } catch (IOException e) { throw new BaseException(e.getMessage()); } - if (null != wxBaseEntity.getErrcode()) { + if (null != wxBaseEntity.getErrcode() && wxBaseEntity.getErrcode() != 0) { throw new BaseException(wxBaseEntity.getErrcode(), wxBaseEntity.getErrmsg()); } } From 628bf8f676f12ce072d1c008e425e429c207c3e6 Mon Sep 17 00:00:00 2001 From: zhangye <654600784@qq.com> Date: Wed, 27 Apr 2022 22:50:06 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=92=8C=E6=B6=88=E6=81=AF=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ccsens/util/wx/WxGzhUtil.java | 8 ++-- .../ccsens/wechatutil/util/WxConstant.java | 4 +- .../OfficialAccountMessageUtil.java | 37 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java b/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java index 42c298a..fd6504d 100644 --- a/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java +++ b/util/src/main/java/com/ccsens/util/wx/WxGzhUtil.java @@ -78,11 +78,9 @@ public class WxGzhUtil { = "https://api.weixin.qq.com/sns/userinfo?access_token=%1$s&openid=%2$s"; public static final String MESSAGE_TEMPLATE_SEND = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%1$s"; -// private static final String APPID = "wx7af1bf1e14facf82"; -// private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9"; - // TODO ;临时更换默认appId - private static final String APPID = "wxcb60fcfeaddeb3e3"; - private static final String SECRET = "e6d27d1b2a0a89b16ec61fdb5e628034"; + private static final String APPID = "wx7af1bf1e14facf82"; + private static final String SECRET = "a6613fae11b497639c0224b820aaf6d9"; + private static final String APPID_H5_TEST = "wxd1842e073e0e6d91"; 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 7a2ff0a..767e804 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/util/WxConstant.java @@ -20,8 +20,10 @@ public class WxConstant { /*** 公众号获取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"; - /*** 公众号发送订阅消息 */ + /*** 公众号通过模板发送订阅消息 */ public static final String MESSAGE_TEMPLATE_SEND = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%1$s"; + /*** 公众号发送订阅消息 */ + public static final String OFFICIAL_MESSAGE_SEND = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%1$s"; /** 创建二维码 */ public static final String QR_CODE_CREATE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={}"; /**根据openId获取用户信息*/ diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java b/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java index a14718b..0fee974 100644 --- a/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/wxofficial/OfficialAccountMessageUtil.java @@ -2,10 +2,12 @@ package com.ccsens.wechatutil.wxofficial; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; import com.ccsens.util.*; import com.ccsens.util.exception.BaseException; import com.ccsens.wechatutil.bean.dto.WxTemplateMessage; import com.ccsens.wechatutil.bean.po.WxBaseEntity; +import com.ccsens.wechatutil.bean.vo.wxmini.Custom; import com.ccsens.wechatutil.util.WxCodeError; import com.ccsens.wechatutil.util.WxConstant; import com.ccsens.wechatutil.wxcommon.WxCommonUtil; @@ -120,4 +122,39 @@ public class OfficialAccountMessageUtil { throw new BaseException(wxBaseEntity.getErrcode(), wxBaseEntity.getErrmsg()); } } + + + public static WxBaseEntity send(String openId, Custom.Type type, Object data, String appId, String secret){ + log.info("发送客服消息:{},{},{}", openId, type, data); + if (StrUtil.isEmpty(openId) || type == null || data == null) { + throw new BaseException(CodeError.PARAM_ERROR); + } + String url = String.format(WxConstant.OFFICIAL_MESSAGE_SEND, WxCommonUtil.getAccessToken(appId, secret)); + Custom.Message message = new Custom.Message(); + message.setTouser(openId); + message.setMsgtype(type.getType()); + switch (type) { + case IMAGE: + message.setImage((Custom.Image) data); + break; + case TEXT: + message.setText((Custom.Text) data); + break; + case LINK: + message.setLink((Custom.Link) data); + break; + case MINI_PROGRAM_PAGE: + message.setMiniprogrampage((Custom.MiniProgramPage) data); + break; + default: + throw new BaseException(CodeError.PARAM_ERROR); + } + log.info("发送客服消息路径:{}, 请求:{}", url, message); + String result = RestTemplateUtil.postBody(url, message); + log.info("发送客服消息结果:{}", result); + WxBaseEntity wxBaseEntity = JSONObject.parseObject(result, WxBaseEntity.class); + return wxBaseEntity; + } + + } 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 3/3] =?UTF-8?q?20220428=E6=96=B0=E5=A2=9E=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=94=AF=E4=BB=98=E6=88=90=E5=8A=9F=E5=9B=9E=E8=B0=83?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BF=AE=E6=94=B9accessToken?= =?UTF-8?q?=E8=8E=B7=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(); }