WeChatServiceImpl.java 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. package com.cyksj.service.wechat.impl;
  2. import cn.hutool.core.date.DateTime;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.util.CharsetUtil;
  5. import cn.hutool.core.util.RandomUtil;
  6. import cn.hutool.core.util.StrUtil;
  7. import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
  8. import cn.hutool.json.JSONObject;
  9. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  10. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  11. import com.cyksj.common.constant.Constant;
  12. import com.cyksj.common.exception.BusinessRuntimeException;
  13. import com.cyksj.common.util.*;
  14. import com.cyksj.config.BaseWeChatConfig;
  15. import com.cyksj.config.WeChatConfig;
  16. import com.cyksj.config.WeChatFactory;
  17. import com.cyksj.config.YhCertMiniConfig;
  18. import com.cyksj.enums.WeChatAuthRedirectUrl;
  19. import com.cyksj.mapper.WxAppMapper;
  20. import com.cyksj.mapper.channel.ShopConfigMapper;
  21. import com.cyksj.model.dto.*;
  22. import com.cyksj.model.entity.ShopConfig;
  23. import com.cyksj.model.entity.SysConfig;
  24. import com.cyksj.model.entity.WxApp;
  25. import com.cyksj.model.response.WxGzhQrCodeTicketRep;
  26. import com.cyksj.model.wechat.MiniURLLink;
  27. import com.cyksj.redis.RedisService;
  28. import com.cyksj.service.sys.SysConfigService;
  29. import com.cyksj.service.wechat.WeChatService;
  30. import com.cyksj.service.wechat.WxMpAccountOps;
  31. import lombok.RequiredArgsConstructor;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.apache.commons.lang3.StringUtils;
  34. import org.apache.logging.log4j.util.Strings;
  35. import org.springframework.stereotype.Service;
  36. import javax.crypto.Mac;
  37. import javax.crypto.spec.SecretKeySpec;
  38. import javax.net.ssl.KeyManagerFactory;
  39. import java.io.File;
  40. import java.io.FileInputStream;
  41. import java.io.InputStream;
  42. import java.math.BigDecimal;
  43. import java.net.URLEncoder;
  44. import java.net.http.HttpRequest;
  45. import java.net.http.HttpResponse;
  46. import java.security.KeyStore;
  47. import java.util.*;
  48. /**
  49. * @author chan
  50. * @description
  51. * @ClassName WeChatServiceImpl
  52. * @date 2020-08-05 11:00 上午
  53. */
  54. @Slf4j
  55. @Service
  56. @RequiredArgsConstructor
  57. public class WeChatServiceImpl implements WeChatService {
  58. private final WeChatConfig weChatConfig;
  59. private final WxMpAccountOps wxMpAccountOps;
  60. private final WxAppMapper wxAppMapper;
  61. private final ShopConfigMapper shopConfigMapper;
  62. private final RedisService redisService;
  63. private final SysConfigService sysConfigService;
  64. private final YhCertMiniConfig certMiniConfig;
  65. @Override
  66. public String createUrl(String state, String redirectUrl) {
  67. return createUrl(state,redirectUrl,weChatConfig.getAppid(), WeChatAuthRedirectUrl.BASE.getScope());
  68. }
  69. @Override
  70. public String createUrl(String state, String redirectUrl,String appId,String scope) {
  71. return "https://open.weixin.qq.com/connect/oauth2/authorize" +
  72. "?appid=" + appId +
  73. "&redirect_uri=" + URLEncoder.encode(redirectUrl, IoKit.Charsets.UTF_8.getCharset()) +
  74. "&response_type=code" +
  75. "&scope=" + scope +
  76. "&state=" + state + "#wechat_redirect";
  77. }
  78. @Override
  79. public AuthToken getAuthToken(String code) throws Exception {
  80. return getAuthToken(code, weChatConfig.getAppid());
  81. }
  82. public String getSecret(String appId){
  83. WxApp wxApp = wxAppMapper.selectOne(new QueryWrapper<WxApp>().lambda().eq(WxApp::getAppId, appId));
  84. if (Optional.ofNullable(wxApp).isPresent()) {
  85. return wxApp.getAppSecret();
  86. }else {
  87. return Strings.EMPTY;
  88. }
  89. }
  90. @Override
  91. public AuthToken getAuthToken(String code,String appId) throws Exception {
  92. String secret = getSecret(appId);
  93. if(StringUtils.isBlank(secret)){
  94. throw BusinessRuntimeException.getInstance("授权公众号不存在!");
  95. }
  96. String url = "https://api.weixin.qq.com/sns/oauth2/access_token" +
  97. "?appid=" + appId +
  98. "&secret=" + secret +
  99. "&code=" + code +
  100. "&grant_type=authorization_code";
  101. HttpResponse<byte[]> res =
  102. J11HttpC.custom()
  103. .ofGet()
  104. .url(url)
  105. .send(HttpResponse.BodyHandlers.ofByteArray());
  106. if (200 != res.statusCode()) {
  107. throw BusinessRuntimeException.getInstance("请求user_token失败: " + IoKit.toString(res.body()));
  108. }
  109. return Jsons.parseObject(res.body(), AuthToken.class);
  110. }
  111. @Override
  112. public GzhOAuth2UserInfo getUserInfo(String accessToken, String openid) throws Exception {
  113. String url = "https://api.weixin.qq.com/sns/userinfo" +
  114. "?access_token=" + accessToken +
  115. "&openid=" + openid +
  116. "&lang=zh_CN";
  117. HttpResponse<byte[]> res =
  118. J11HttpC.custom()
  119. .ofGet()
  120. .url(url)
  121. .send(HttpResponse.BodyHandlers.ofByteArray());
  122. if (200 != res.statusCode()) {
  123. throw BusinessRuntimeException.getInstance("请求userinfo失败: " + IoKit.toString(res.body()));
  124. }
  125. return Jsons.parseObject(res.body(), GzhOAuth2UserInfo.class);
  126. }
  127. @Override
  128. public GzhUnionidUserinfo getUserInfo2(String accessToken, String openid) throws Exception {
  129. String url = "https://api.weixin.qq.com/cgi-bin/user/info" +
  130. "?access_token=" + accessToken +
  131. "&openid=" + openid +
  132. "&lang=zh_CN";
  133. HttpResponse<byte[]> res =
  134. J11HttpC.custom()
  135. .ofGet()
  136. .url(url)
  137. .send(HttpResponse.BodyHandlers.ofByteArray());
  138. if (200 != res.statusCode()) {
  139. throw BusinessRuntimeException.getInstance("请求userinfo失败: " + IoKit.toString(res.body()));
  140. }
  141. GzhUnionidUserinfo info = Jsons.parseObject(res.body(), GzhUnionidUserinfo.class);
  142. if (null == info.getSubscribe() || info.getSubscribe() == 0) {
  143. return null;
  144. }
  145. return info;
  146. }
  147. @Override
  148. public String getJsTicket() throws Exception {
  149. return getJsTicket(null);
  150. }
  151. @Override
  152. public String getJsTicket(String appId) throws Exception {
  153. appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid());
  154. String jsTicket = wxMpAccountOps.jsTicket(appId);
  155. if (StringUtils.isBlank(jsTicket)) {
  156. throw BusinessRuntimeException.getInstance("获取用户信息失败");
  157. }
  158. return jsTicket;
  159. }
  160. @Override
  161. public String getAccessToken() throws Exception {
  162. return getAccessToken(null);
  163. }
  164. @Override
  165. public String getAccessToken(String appId) throws Exception {
  166. appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid());
  167. String accessToken = wxMpAccountOps.accessToken(appId);
  168. log.info("------------------accessToken:{} \n",accessToken);
  169. if (StringUtils.isBlank(accessToken)) {
  170. throw BusinessRuntimeException.getInstance("获取用户信息失败:");
  171. }
  172. return accessToken;
  173. }
  174. @Override
  175. public H5JsPayParams getJsPayParams(String appid, String shopId, String openid, Integer totalFee, String outTradeNo, String notifyUrl, String attach, String body, String tradeType, Date expireTime) throws Exception {
  176. log.info("------------------- 调用微信h5支付 开始 -------------------");
  177. ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, shopId).last(" limit 1"));
  178. // 拼接统一下单实体类
  179. UnifiedOrder order = new UnifiedOrder()
  180. .setAppid(appid)
  181. .setAttach(attach)
  182. .setBody(body)
  183. .setMchId(shopConfig.getAppId())
  184. .setNonceStr("chan.123")
  185. .setNotifyUrl(notifyUrl)
  186. .setOutTradeNo(outTradeNo)
  187. .setSpbillCreateIp("127.0.0.1")
  188. .setTotalFee(totalFee)
  189. .setTradeType(tradeType);
  190. if (expireTime != null) {
  191. //5分钟过期
  192. order.setTimeExpire(DateUtil.format(DateUtil.offsetMinute(expireTime, 5), "yyyyMMddHHmmss"));
  193. }
  194. if (!"NATIVE".equals(tradeType)) {
  195. order.setOpenid(openid);
  196. }
  197. String sign = Codec.DoDigest.custom()
  198. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  199. .setStringData(order.getPrintln(tradeType, shopConfig.getSecret()))
  200. .toHexString()
  201. .toUpperCase();
  202. order.setSign(sign);
  203. // xml
  204. String xml = Xmls.toXml(order);
  205. log.info("调用微信h5支付, 请求参数: \n{}", xml);
  206. HttpResponse<byte[]> res =
  207. J11HttpC.custom()
  208. .ofPost()
  209. .url("https://api.mch.weixin.qq.com/pay/unifiedorder")
  210. .cacheURI()
  211. .headers(J11HttpC.ReqType.raw_xml)
  212. .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset()))
  213. .send(HttpResponse.BodyHandlers.ofByteArray());
  214. if (200 != res.statusCode()) {
  215. throw BusinessRuntimeException.getInstance("请求wx_prepay失败: " + IoKit.toString(res.body()));
  216. }
  217. Map<String, String> wxResult = Xmls.toMap(res.body());
  218. log.info("微信返回值 xml转map: \n{}", wxResult);
  219. if (!StringUtils.equals(wxResult.get("return_code"), "SUCCESS") || !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  220. throw BusinessRuntimeException.getInstance("调用微信h5支付失败. 微信返回: " + wxResult);
  221. }
  222. if (StringUtils.equals(wxResult.get("return_code"), "SUCCESS") && !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  223. throw BusinessRuntimeException.getInstance("订单异常, 原因: " + wxResult.get("err_code") + "[" + wxResult.get("err_code_des") + "]");
  224. }
  225. if (StringUtils.isBlank(wxResult.get("prepay_id"))) {
  226. throw BusinessRuntimeException.getInstance("无法获取prepay_id.");
  227. }
  228. // 校验签名
  229. boolean f = this.checkSignWithMd5(wxResult,shopConfig.getSecret());
  230. if (!f) {
  231. throw BusinessRuntimeException.getInstance("调用微信h5支付, 微信返回值的签名验证失败.");
  232. }
  233. H5JsPayParams params = new H5PayParams();
  234. params.setAppId(appid);
  235. params.setNonceStr(order.getNonceStr());
  236. params.setSignType("MD5");
  237. params.setTimestamp(System.currentTimeMillis());
  238. params.setPrepayId("prepay_id=" + wxResult.get("prepay_id"));
  239. sign = Codec.DoDigest.custom()
  240. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  241. .setStringData(params.println(shopConfig.getSecret()))
  242. .toHexString()
  243. .toUpperCase();
  244. params.setPaySign(sign);
  245. //二维码链接
  246. params.setCodeUrl(wxResult.get("code_url"));
  247. log.info("------------------- 调用微信h5支付 结束 -------------------");
  248. return params;
  249. }
  250. @Override
  251. public boolean checkSignWithMd5(Map<String, String> map) throws Exception {
  252. return checkSignWithMd5(map,weChatConfig.getShopKey());
  253. }
  254. @Override
  255. public boolean checkSignWithMd5(Map<String, String> map,String shopKey) throws Exception {
  256. // 将map的key按ASCii升序排列
  257. String[] keys = map.keySet().toArray(new String[0]);
  258. Arrays.sort(keys);
  259. // 将参数有序排列
  260. StringBuilder str = new StringBuilder();
  261. for (int i = 0; i < keys.length; i++) {
  262. if (StringUtils.equals(keys[i], "sign")) {
  263. continue;
  264. }
  265. if (StringUtils.isBlank(map.get(keys[i]))) {
  266. continue;
  267. }
  268. str.append(keys[i]).append("=").append(map.get(keys[i])).append("&");
  269. }
  270. str.append("key=").append(shopKey);
  271. log.info("签名验证, 排列好的有序字符串: {}", str.toString());
  272. String sign = Codec.DoDigest.custom().setAlgorithm(Codec.DoDigest.Algorithm.MD5).setStringData(str.toString()).toHexString().toUpperCase();
  273. return StringUtils.equals(map.get("sign"), sign);
  274. }
  275. @Override
  276. public H5PayParams getH5PayParams(String appid, Integer totalFee, String outTradeNo, String notifyUrl, String attach, String ip,String body) throws Exception {
  277. log.info("------------------- 调用微信h5支付 开始 -------------------");
  278. BaseWeChatConfig weChatConfigStrategy = WeChatFactory.getWeChatConfigStrategy(appid);
  279. // 拼接统一下单实体类
  280. UnifiedOrder order = new UnifiedOrder();
  281. order.setAppid(appid);
  282. order.setAttach(attach);
  283. order.setBody(body);
  284. order.setMchId(weChatConfigStrategy.getShopid());
  285. order.setNonceStr("chan123");
  286. order.setNotifyUrl(notifyUrl);
  287. order.setOutTradeNo(outTradeNo);
  288. order.setSpbillCreateIp(ip);
  289. order.setTotalFee(totalFee);
  290. order.setTradeType("MWEB");
  291. String sceneInfo= "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"" + weChatConfigStrategy.getDomain() +"\",\"wap_name\": \"善行\"}}";
  292. order.setSceneInfo(sceneInfo);
  293. // order.setSignType("MD5");
  294. String sign = Codec.DoDigest.custom()
  295. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  296. .setStringData(order.H5Println(weChatConfigStrategy.getShopKey()))
  297. .toHexString()
  298. .toUpperCase();
  299. order.setSign(sign);
  300. // xml
  301. String xml = Xmls.toXml(order);
  302. log.info("调用微信h5支付, 请求参数: \n{}", xml);
  303. HttpResponse<byte[]> res =
  304. J11HttpC.custom()
  305. .ofPost()
  306. .url("https://api.mch.weixin.qq.com/pay/unifiedorder")
  307. .cacheURI()
  308. .headers(J11HttpC.ReqType.raw_xml)
  309. .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset()))
  310. .send(HttpResponse.BodyHandlers.ofByteArray());
  311. if (200 != res.statusCode()) {
  312. throw BusinessRuntimeException.getInstance("请求wx_prepay失败: " + IoKit.toString(res.body()));
  313. }
  314. Map<String, String> wxResult = Xmls.toMap(res.body());
  315. log.info("微信返回值 xml转map: \n{}", wxResult);
  316. if (!StringUtils.equals(wxResult.get("return_code"), "SUCCESS") || !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  317. throw BusinessRuntimeException.getInstance("调用微信h5支付失败. 微信返回: " + wxResult);
  318. }
  319. if (StringUtils.equals(wxResult.get("return_code"), "SUCCESS") && !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) {
  320. throw BusinessRuntimeException.getInstance("订单异常, 原因: " + wxResult.get("err_code") + "[" + wxResult.get("err_code_des") + "]");
  321. }
  322. if (StringUtils.isBlank(wxResult.get("prepay_id"))) {
  323. throw BusinessRuntimeException.getInstance("无法获取prepay_id.");
  324. }
  325. // 校验签名
  326. boolean f = this.checkSignWithMd5(wxResult,weChatConfigStrategy.getShopKey());
  327. if (!f) {
  328. throw BusinessRuntimeException.getInstance("调用微信h5支付, 微信返回值的签名验证失败.");
  329. }
  330. H5PayParams params = new H5PayParams();
  331. params.setAppId(appid);
  332. params.setNonceStr(order.getNonceStr());
  333. params.setSignType("MD5");
  334. params.setTimestamp(System.currentTimeMillis());
  335. params.setPrepayId("prepay_id=" + wxResult.get("prepay_id"));
  336. params.setMWebUrl(wxResult.get("mweb_url"));
  337. sign = Codec.DoDigest.custom()
  338. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  339. .setStringData(params.println(weChatConfigStrategy.getShopKey()))
  340. .toHexString()
  341. .toUpperCase();
  342. params.setPaySign(sign);
  343. log.info("------------------- 调用微信h5支付 结束 -------------------");
  344. return params;
  345. }
  346. @Override
  347. public void sendTemplateMessage(String accessToken, String json) throws Exception{
  348. String url = "https://api.weixin.qq.com/cgi-bin/message/template/send" +
  349. "?access_token=" + accessToken;
  350. HttpResponse<String> send = J11HttpC.custom()
  351. .ofPost()
  352. .url(url)
  353. .headers(J11HttpC.ReqType.raw_json)
  354. .body(HttpRequest.BodyPublishers.ofString(json))
  355. .send(HttpResponse.BodyHandlers.ofString());
  356. log.info("发送模板消息结束,返回值:{}", send != null ? send.body() : null);
  357. }
  358. @Override
  359. public void refundWxOrder(String appId,String shopId, String transactionId, String outRefundNo, BigDecimal totalFee, BigDecimal refundMoney, String notifyUrl) throws Exception {
  360. ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, shopId).last(" limit 1"));
  361. WxRefundOrder wxRefundOrder = new WxRefundOrder();
  362. wxRefundOrder.setAppid(appId);
  363. //商户号
  364. wxRefundOrder.setMchId(shopConfig.getAppId());
  365. wxRefundOrder.setOutRefundNo(outRefundNo);
  366. wxRefundOrder.setNonceStr("chan123");
  367. wxRefundOrder.setTotalFee(totalFee.intValue());
  368. //退款金额
  369. wxRefundOrder.setRefundFee(refundMoney.intValue());
  370. wxRefundOrder.setTransactionId(transactionId);
  371. // wxRefundOrder.setRefundDesc("订单退款");
  372. wxRefundOrder.setNotifyUrl(notifyUrl);
  373. String sign = Codec.DoDigest.custom()
  374. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  375. .setStringData(wxRefundOrder.println(shopConfig.getSecret()))
  376. .toHexString()
  377. .toUpperCase();
  378. wxRefundOrder.setSign(sign);
  379. String xml = Xmls.toXml(wxRefundOrder);
  380. KeyStore keyStore = KeyStore.getInstance("PKCS12");
  381. File file = new File("cert/" + shopId + ".p12");
  382. InputStream inputStream = new FileInputStream(file);
  383. keyStore.load(inputStream, shopId.toCharArray());
  384. // 初始化密钥库
  385. KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  386. factory.init(keyStore, shopId.toCharArray());
  387. cn.hutool.http.HttpRequest request = cn.hutool.http.HttpRequest.post("https://api.mch.weixin.qq.com/secapi/pay/refund")
  388. // 自定义返回编码
  389. .charset(CharsetUtil.CHARSET_UTF_8)
  390. // 禁用缓存
  391. .disableCache()
  392. .setSSLSocketFactory(SSLSocketFactoryBuilder.create()
  393. .setKeyManagers(factory.getKeyManagers())
  394. .setProtocol(SSLSocketFactoryBuilder.TLSv1).build());
  395. cn.hutool.http.HttpResponse res = request.body(xml).execute();
  396. if (200 != res.getStatus()) {
  397. throw BusinessRuntimeException.getInstance("请求wx_refund失败: " + res.body());
  398. }
  399. Map<String, String> wxRefund = Xmls.toMap(res.body());
  400. log.info("微信返回值 xml 转 map: \n{}", wxRefund);
  401. if (!StringUtil.equals(wxRefund.get("return_code"), "SUCCESS") || !StringUtil.equals(wxRefund.get("result_code"), "SUCCESS")) {
  402. throw BusinessRuntimeException.getInstance("调用微信申请退款失败. 微信返回: " + wxRefund);
  403. }
  404. if (StringUtil.equals(wxRefund.get("return_code"), "SUCCESS") && !StringUtil.equals(wxRefund.get("result_code"), "SUCCESS")) {
  405. throw BusinessRuntimeException.getInstance("订单异常: " + wxRefund.get("err_code") + "[" + wxRefund.get("err_code_des") + "]");
  406. }
  407. // 校验签名
  408. boolean f = this.checkSignWithMd5(wxRefund, shopConfig.getSecret());
  409. if (!f) {
  410. throw BusinessRuntimeException.getInstance("调用微信h5申请退款, 微信返回值的签名验证失败.");
  411. }
  412. log.info("------------------- 调用微信申请退款 结束 -------------------");
  413. }
  414. @Override
  415. public WxGzhQrCodeTicketRep getWxGzhQrCode(Long sharedId, Integer dsType, Long popularizeId, String loginPhone, String loginEmail, String dsCode) throws Exception {
  416. SysConfig sysConfig = sysConfigService.getOne(Wrappers.lambdaQuery(SysConfig.class)
  417. .eq(SysConfig::getSysKey, Constant.QR_CODE_WX_APP_ID).last("limit 1"));
  418. String appid = null;
  419. if (sysConfig != null) {
  420. String sysValue = sysConfig.getSysValue();
  421. if (StrUtil.isNotBlank(sysValue)) {
  422. appid = sysValue;
  423. }
  424. }
  425. String gzhQrCodeUrl = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", this.getAccessToken(appid));
  426. //scene_str:场景值ID(字符串形式的ID),字符串类型,长度限制为1到64
  427. String scene_str = RandomUtil.randomString(16);
  428. int expireTime = 60;
  429. if (StrUtil.isNotBlank(loginPhone) || StrUtil.isNotBlank(loginEmail)) {
  430. expireTime = 25;
  431. }
  432. DateTime dateTime = DateUtil.offsetSecond(DateTime.now(), expireTime);
  433. String body = String.format("{\"expire_seconds\": %s, \"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}",
  434. expireTime,
  435. "QR_STR_SCENE",
  436. scene_str);
  437. HttpResponse<String> response = J11HttpC.custom()
  438. .url(gzhQrCodeUrl)
  439. .ofPost()
  440. .body(HttpRequest.BodyPublishers.ofString(body))
  441. .send(HttpResponse.BodyHandlers.ofString());
  442. if (200 != response.statusCode()) {
  443. throw BusinessRuntimeException.getInstance("获取微信登录二维码失败: " + response.body());
  444. }
  445. if (sharedId != null || popularizeId != null || StrUtil.isNotBlank(loginPhone) || StrUtil.isNotBlank(dsCode)) {
  446. GzhBodyDto gzhBodyDto = new GzhBodyDto()
  447. .setSharedId(sharedId)
  448. .setPopularizeId(popularizeId)
  449. .setDsType(dsType)
  450. .setLoginPhone(loginPhone)
  451. .setLoginEmail(loginEmail)
  452. .setDsCode(dsCode);
  453. String state = Codec.DoBase64.custom().setData(Jsons.toJson(gzhBodyDto)).encodeAsText();
  454. redisService.set(RedisService.key.WX_GZH_QRCODE_SHARED_LOGIN.getNameFormat(scene_str), state, RedisService.key.WX_GZH_QRCODE_SHARED_LOGIN.getTimeout());
  455. }
  456. JSONObject re = Jsons.parseObject(response.body(), JSONObject.class);
  457. String ticket = re.getStr("ticket");
  458. WxGzhQrCodeTicketRep wxGzhQrCodeTicketRep = new WxGzhQrCodeTicketRep();
  459. wxGzhQrCodeTicketRep.setQrCodeUrl(String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket));
  460. wxGzhQrCodeTicketRep.setSceneStr(scene_str);
  461. wxGzhQrCodeTicketRep.setExpireTime(dateTime.getTime());
  462. return wxGzhQrCodeTicketRep;
  463. }
  464. @Override
  465. public String getNotExpiredWxGzhQrCode(String sceneKey) throws Exception {
  466. String gzhQrCodeUrl = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", this.getAccessToken());
  467. String body = String.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}",
  468. "QR_LIMIT_STR_SCENE",
  469. sceneKey);
  470. HttpResponse<String> response = J11HttpC.custom()
  471. .url(gzhQrCodeUrl)
  472. .ofPost()
  473. .body(HttpRequest.BodyPublishers.ofString(body))
  474. .send(HttpResponse.BodyHandlers.ofString());
  475. if (200 != response.statusCode()) {
  476. throw BusinessRuntimeException.getInstance("获取永久渠道二维码失败: " + response.body());
  477. }
  478. JSONObject re = Jsons.parseObject(response.body(), JSONObject.class);
  479. String ticket = re.getStr("ticket");
  480. String url = String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket);
  481. return url;
  482. }
  483. @Override
  484. public Map<String, String> getWxOrderDetail(String shopId, String orderNo) throws Exception {
  485. String url = "https://api.mch.weixin.qq.com/pay/orderquery";
  486. // 拼接统一下单实体类
  487. Map<String, String> map = new TreeMap<>();
  488. map.put("appid", weChatConfig.getAppid());
  489. map.put("mch_id", shopId);
  490. map.put("out_trade_no", orderNo);
  491. map.put("nonce_str", "d1234");
  492. String sign = getMd5Sign(map);
  493. map.put("sign", sign);
  494. String xml = Xmls.toXml(map);
  495. HttpResponse<String> send = J11HttpC.custom()
  496. .ofPost()
  497. .url(url)
  498. .headers(J11HttpC.ReqType.raw_xml)
  499. .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset()))
  500. .send(HttpResponse.BodyHandlers.ofString());
  501. Map<String, String> orderSearch = Xmls.toMap(send.body());
  502. log.info("微信返回值 xml转map: \n{}", orderSearch);
  503. if (!StringUtils.equals(orderSearch.get("result_code"), "SUCCESS")) {
  504. log.error("调用微信h5查询订单详情接口失败. 微信返回: " + orderSearch);
  505. return null;
  506. }
  507. return orderSearch;
  508. }
  509. @Override
  510. public String createWxAuthLoginUrl(String state, String appId, String redirectUri) {
  511. String authLoginUrl = String.format("https://open.weixin.qq.com/connect/qrconnect?" +
  512. "appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect", appId, redirectUri, state);
  513. return authLoginUrl;
  514. }
  515. @Override
  516. public String getMiniURLLink(String accessToken, MiniURLLink miniURLLink) throws Exception{
  517. String url = String.format("https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s", accessToken);
  518. HttpResponse<String> send = J11HttpC.custom()
  519. .url(url)
  520. .ofPost()
  521. .body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(miniURLLink)))
  522. .send(HttpResponse.BodyHandlers.ofString());
  523. if (send.statusCode() != 200) {
  524. throw BusinessRuntimeException.getInstance("获取小程序链接错误");
  525. }
  526. JSONObject body = Jsons.parseObject(send.body(), JSONObject.class);
  527. if (body.getInt("errcode") != 0) {
  528. throw BusinessRuntimeException.getInstance(body.getStr("errmsg"));
  529. }
  530. return body.getStr("url_link");
  531. }
  532. /**
  533. * sha256_HMAC加密
  534. * @param message 消息
  535. * @param secret 秘钥
  536. * @return 加密后字符串
  537. */
  538. public static String sha256_HMAC(String message,String secret) {
  539. String hash = "";
  540. try {
  541. Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
  542. SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(IoKit.Charsets.UTF_8.getCharset()), "HmacSHA256");
  543. sha256_HMAC.init(secret_key);
  544. byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
  545. hash = byteArrayToHexString(bytes);
  546. } catch (Exception e) {
  547. System.out.println("Error HmacSHA256 ===========" + e.getMessage());
  548. }
  549. return hash;
  550. }
  551. /**
  552. * 将加密后的字节数组转换成字符串
  553. *
  554. * @param b 字节数组
  555. * @return 字符串
  556. */
  557. public static String byteArrayToHexString(byte[] b) {
  558. StringBuilder hs = new StringBuilder();
  559. String stmp;
  560. for (int n = 0; b!=null && n < b.length; n++) {
  561. stmp = Integer.toHexString(b[n] & 0XFF);
  562. if (stmp.length() == 1) {
  563. hs.append('0');
  564. }
  565. hs.append(stmp);
  566. }
  567. return hs.toString().toLowerCase();
  568. }
  569. public String getMd5Sign(Map<String, String> map) throws Exception {
  570. StringBuilder sb = new StringBuilder();
  571. map.forEach((k, v) -> {
  572. sb.append(k).append("=").append(v).append("&");
  573. });
  574. sb.append("key").append("=").append(shopConfigMapper.getByAppId(map.get("mch_id")).getSecret());
  575. String sign = Codec.DoDigest.custom()
  576. .setAlgorithm(Codec.DoDigest.Algorithm.MD5)
  577. .setStringData(sb.toString())
  578. .toHexString()
  579. .toUpperCase();
  580. return sign;
  581. }
  582. @Override
  583. public MiniProgramSession code2Session(String appId,String code) throws Exception {
  584. appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid());
  585. return wxMpAccountOps.miniProgramCode2Session(appId,code);
  586. }
  587. @Override
  588. public void getMiniStudentIdentity(String openId, String code) throws Exception {
  589. String url = "https://api.weixin.qq.com/intp/quickcheckstudentidentity?access_token=" + getAccessToken(certMiniConfig.getAppId());
  590. JSONObject params = new JSONObject();
  591. params.putOpt("openid", openId);
  592. params.putOpt("wx_studentcheck_code", code);
  593. HttpResponse<String> response = J11HttpC.custom().url(url).ofPost().body(HttpRequest.BodyPublishers.ofString(params.toString())).send(HttpResponse.BodyHandlers.ofString());
  594. JSONObject re = Jsons.parseObject(response.body(), JSONObject.class);
  595. if (re.getInt("errcode") != 0) {
  596. log.info("获取学生信息失败:{}", re.getStr("errmsg"));
  597. throw BusinessRuntimeException.getInstance("获取认证信息失败");
  598. }
  599. Integer bindStatus = Optional.ofNullable(re.getInt("bind_status")).orElse(1);
  600. //绑定状态:
  601. //1-未绑定
  602. //2-审核中
  603. //3-已绑定
  604. if (bindStatus == 1) {
  605. throw BusinessRuntimeException.getInstance("大学生身份未绑定");
  606. }
  607. if (bindStatus == 2) {
  608. throw BusinessRuntimeException.getInstance("大学生身份审核中");
  609. }
  610. }
  611. }