WeChatServiceImpl.java 26 KB

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