package com.cyksj.service.wechat.impl; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; import cn.hutool.json.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.cyksj.common.constant.Constant; import com.cyksj.common.exception.BusinessRuntimeException; import com.cyksj.common.util.*; import com.cyksj.config.BaseWeChatConfig; import com.cyksj.config.WeChatConfig; import com.cyksj.config.WeChatFactory; import com.cyksj.config.YhCertMiniConfig; import com.cyksj.enums.WeChatAuthRedirectUrl; import com.cyksj.mapper.WxAppMapper; import com.cyksj.mapper.channel.ShopConfigMapper; import com.cyksj.model.dto.*; import com.cyksj.model.entity.ShopConfig; import com.cyksj.model.entity.SysConfig; import com.cyksj.model.entity.WxApp; import com.cyksj.model.request.TargetAppletSchemeReq; import com.cyksj.model.response.WxGzhQrCodeTicketRep; import com.cyksj.model.wechat.MiniURLLink; import com.cyksj.redis.RedisService; import com.cyksj.service.sys.SysConfigService; import com.cyksj.service.wechat.WeChatService; import com.cyksj.service.wechat.WxMpAccountOps; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.springframework.stereotype.Service; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.KeyManagerFactory; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.math.BigDecimal; import java.net.URLEncoder; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.security.KeyStore; import java.util.*; /** * @author chan * @description * @ClassName WeChatServiceImpl * @date 2020-08-05 11:00 上午 */ @Slf4j @Service @RequiredArgsConstructor public class WeChatServiceImpl implements WeChatService { private final WeChatConfig weChatConfig; private final WxMpAccountOps wxMpAccountOps; private final WxAppMapper wxAppMapper; private final ShopConfigMapper shopConfigMapper; private final RedisService redisService; private final SysConfigService sysConfigService; private final YhCertMiniConfig certMiniConfig; @Override public String createUrl(String state, String redirectUrl) { return createUrl(state,redirectUrl,weChatConfig.getAppid(), WeChatAuthRedirectUrl.BASE.getScope()); } @Override public String createUrl(String state, String redirectUrl,String appId,String scope) { return "https://open.weixin.qq.com/connect/oauth2/authorize" + "?appid=" + appId + "&redirect_uri=" + URLEncoder.encode(redirectUrl, IoKit.Charsets.UTF_8.getCharset()) + "&response_type=code" + "&scope=" + scope + "&state=" + state + "#wechat_redirect"; } @Override public AuthToken getAuthToken(String code) throws Exception { return getAuthToken(code, weChatConfig.getAppid()); } public String getSecret(String appId){ WxApp wxApp = wxAppMapper.selectOne(new QueryWrapper().lambda().eq(WxApp::getAppId, appId)); if (Optional.ofNullable(wxApp).isPresent()) { return wxApp.getAppSecret(); }else { return Strings.EMPTY; } } @Override public AuthToken getAuthToken(String code,String appId) throws Exception { String secret = getSecret(appId); if(StringUtils.isBlank(secret)){ throw BusinessRuntimeException.getInstance("授权公众号不存在!"); } String url = "https://api.weixin.qq.com/sns/oauth2/access_token" + "?appid=" + appId + "&secret=" + secret + "&code=" + code + "&grant_type=authorization_code"; HttpResponse res = J11HttpC.custom() .ofGet() .url(url) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("请求user_token失败: " + IoKit.toString(res.body())); } return Jsons.parseObject(res.body(), AuthToken.class); } @Override public GzhOAuth2UserInfo getUserInfo(String accessToken, String openid) throws Exception { String url = "https://api.weixin.qq.com/sns/userinfo" + "?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN"; HttpResponse res = J11HttpC.custom() .ofGet() .url(url) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("请求userinfo失败: " + IoKit.toString(res.body())); } return Jsons.parseObject(res.body(), GzhOAuth2UserInfo.class); } @Override public GzhUnionidUserinfo getUserInfo2(String accessToken, String openid) throws Exception { String url = "https://api.weixin.qq.com/cgi-bin/user/info" + "?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN"; HttpResponse res = J11HttpC.custom() .ofGet() .url(url) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("请求userinfo失败: " + IoKit.toString(res.body())); } GzhUnionidUserinfo info = Jsons.parseObject(res.body(), GzhUnionidUserinfo.class); if (null == info.getSubscribe() || info.getSubscribe() == 0) { return null; } return info; } @Override public String getJsTicket() throws Exception { return getJsTicket(null); } @Override public String getJsTicket(String appId) throws Exception { appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid()); String jsTicket = wxMpAccountOps.jsTicket(appId); if (StringUtils.isBlank(jsTicket)) { throw BusinessRuntimeException.getInstance("获取用户信息失败"); } return jsTicket; } @Override public String getAccessToken() throws Exception { return getAccessToken(null); } @Override public String getAccessToken(String appId) throws Exception { appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid()); String accessToken = wxMpAccountOps.accessToken(appId); log.info("------------------accessToken:{} \n",accessToken); if (StringUtils.isBlank(accessToken)) { throw BusinessRuntimeException.getInstance("获取用户信息失败:"); } return accessToken; } @Override 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 { log.info("------------------- 调用微信h5支付 开始 -------------------"); ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, shopId).last(" limit 1")); // 拼接统一下单实体类 UnifiedOrder order = new UnifiedOrder() .setAppid(appid) .setAttach(attach) .setBody(body) .setMchId(shopConfig.getAppId()) .setNonceStr("chan.123") .setNotifyUrl(notifyUrl) .setOutTradeNo(outTradeNo) .setSpbillCreateIp("127.0.0.1") .setTotalFee(totalFee) .setTradeType(tradeType); if (expireTime != null) { //5分钟过期 order.setTimeExpire(DateUtil.format(DateUtil.offsetMinute(expireTime, 5), "yyyyMMddHHmmss")); } if (!"NATIVE".equals(tradeType)) { order.setOpenid(openid); } String sign = Codec.DoDigest.custom() .setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(order.getPrintln(tradeType, shopConfig.getSecret())) .toHexString() .toUpperCase(); order.setSign(sign); // xml String xml = Xmls.toXml(order); log.info("调用微信h5支付, 请求参数: \n{}", xml); HttpResponse res = J11HttpC.custom() .ofPost() .url("https://api.mch.weixin.qq.com/pay/unifiedorder") .cacheURI() .headers(J11HttpC.ReqType.raw_xml) .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset())) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("请求wx_prepay失败: " + IoKit.toString(res.body())); } Map wxResult = Xmls.toMap(res.body()); log.info("微信返回值 xml转map: \n{}", wxResult); if (!StringUtils.equals(wxResult.get("return_code"), "SUCCESS") || !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) { throw BusinessRuntimeException.getInstance("调用微信h5支付失败. 微信返回: " + wxResult); } if (StringUtils.equals(wxResult.get("return_code"), "SUCCESS") && !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) { throw BusinessRuntimeException.getInstance("订单异常, 原因: " + wxResult.get("err_code") + "[" + wxResult.get("err_code_des") + "]"); } if (StringUtils.isBlank(wxResult.get("prepay_id"))) { throw BusinessRuntimeException.getInstance("无法获取prepay_id."); } // 校验签名 boolean f = this.checkSignWithMd5(wxResult,shopConfig.getSecret()); if (!f) { throw BusinessRuntimeException.getInstance("调用微信h5支付, 微信返回值的签名验证失败."); } H5JsPayParams params = new H5PayParams(); params.setAppId(appid); params.setNonceStr(order.getNonceStr()); params.setSignType("MD5"); params.setTimestamp(System.currentTimeMillis()); params.setPrepayId("prepay_id=" + wxResult.get("prepay_id")); sign = Codec.DoDigest.custom() .setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(params.println(shopConfig.getSecret())) .toHexString() .toUpperCase(); params.setPaySign(sign); //二维码链接 params.setCodeUrl(wxResult.get("code_url")); log.info("------------------- 调用微信h5支付 结束 -------------------"); return params; } @Override public boolean checkSignWithMd5(Map map) throws Exception { return checkSignWithMd5(map,weChatConfig.getShopKey()); } @Override public boolean checkSignWithMd5(Map map,String shopKey) throws Exception { // 将map的key按ASCii升序排列 String[] keys = map.keySet().toArray(new String[0]); Arrays.sort(keys); // 将参数有序排列 StringBuilder str = new StringBuilder(); for (int i = 0; i < keys.length; i++) { if (StringUtils.equals(keys[i], "sign")) { continue; } if (StringUtils.isBlank(map.get(keys[i]))) { continue; } str.append(keys[i]).append("=").append(map.get(keys[i])).append("&"); } str.append("key=").append(shopKey); log.info("签名验证, 排列好的有序字符串: {}", str.toString()); String sign = Codec.DoDigest.custom().setAlgorithm(Codec.DoDigest.Algorithm.MD5).setStringData(str.toString()).toHexString().toUpperCase(); return StringUtils.equals(map.get("sign"), sign); } @Override public H5PayParams getH5PayParams(String appid, Integer totalFee, String outTradeNo, String notifyUrl, String attach, String ip,String body) throws Exception { log.info("------------------- 调用微信h5支付 开始 -------------------"); BaseWeChatConfig weChatConfigStrategy = WeChatFactory.getWeChatConfigStrategy(appid); // 拼接统一下单实体类 UnifiedOrder order = new UnifiedOrder(); order.setAppid(appid); order.setAttach(attach); order.setBody(body); order.setMchId(weChatConfigStrategy.getShopid()); order.setNonceStr("chan123"); order.setNotifyUrl(notifyUrl); order.setOutTradeNo(outTradeNo); order.setSpbillCreateIp(ip); order.setTotalFee(totalFee); order.setTradeType("MWEB"); String sceneInfo= "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"" + weChatConfigStrategy.getDomain() +"\",\"wap_name\": \"善行\"}}"; order.setSceneInfo(sceneInfo); // order.setSignType("MD5"); String sign = Codec.DoDigest.custom() .setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(order.H5Println(weChatConfigStrategy.getShopKey())) .toHexString() .toUpperCase(); order.setSign(sign); // xml String xml = Xmls.toXml(order); log.info("调用微信h5支付, 请求参数: \n{}", xml); HttpResponse res = J11HttpC.custom() .ofPost() .url("https://api.mch.weixin.qq.com/pay/unifiedorder") .cacheURI() .headers(J11HttpC.ReqType.raw_xml) .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset())) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("请求wx_prepay失败: " + IoKit.toString(res.body())); } Map wxResult = Xmls.toMap(res.body()); log.info("微信返回值 xml转map: \n{}", wxResult); if (!StringUtils.equals(wxResult.get("return_code"), "SUCCESS") || !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) { throw BusinessRuntimeException.getInstance("调用微信h5支付失败. 微信返回: " + wxResult); } if (StringUtils.equals(wxResult.get("return_code"), "SUCCESS") && !StringUtils.equals(wxResult.get("result_code"), "SUCCESS")) { throw BusinessRuntimeException.getInstance("订单异常, 原因: " + wxResult.get("err_code") + "[" + wxResult.get("err_code_des") + "]"); } if (StringUtils.isBlank(wxResult.get("prepay_id"))) { throw BusinessRuntimeException.getInstance("无法获取prepay_id."); } // 校验签名 boolean f = this.checkSignWithMd5(wxResult,weChatConfigStrategy.getShopKey()); if (!f) { throw BusinessRuntimeException.getInstance("调用微信h5支付, 微信返回值的签名验证失败."); } H5PayParams params = new H5PayParams(); params.setAppId(appid); params.setNonceStr(order.getNonceStr()); params.setSignType("MD5"); params.setTimestamp(System.currentTimeMillis()); params.setPrepayId("prepay_id=" + wxResult.get("prepay_id")); params.setMWebUrl(wxResult.get("mweb_url")); sign = Codec.DoDigest.custom() .setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(params.println(weChatConfigStrategy.getShopKey())) .toHexString() .toUpperCase(); params.setPaySign(sign); log.info("------------------- 调用微信h5支付 结束 -------------------"); return params; } @Override public void sendTemplateMessage(String accessToken, String json) throws Exception{ String url = "https://api.weixin.qq.com/cgi-bin/message/template/send" + "?access_token=" + accessToken; HttpResponse send = J11HttpC.custom() .ofPost() .url(url) .headers(J11HttpC.ReqType.raw_json) .body(HttpRequest.BodyPublishers.ofString(json)) .send(HttpResponse.BodyHandlers.ofString()); log.info("发送模板消息结束,返回值:{}", send != null ? send.body() : null); } @Override public void refundWxOrder(String appId,String shopId, String transactionId, String outRefundNo, BigDecimal totalFee, BigDecimal refundMoney, String notifyUrl) throws Exception { ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class).eq(ShopConfig::getAppId, shopId).last(" limit 1")); WxRefundOrder wxRefundOrder = new WxRefundOrder(); wxRefundOrder.setAppid(appId); //商户号 wxRefundOrder.setMchId(shopConfig.getAppId()); wxRefundOrder.setOutRefundNo(outRefundNo); wxRefundOrder.setNonceStr("chan123"); wxRefundOrder.setTotalFee(totalFee.intValue()); //退款金额 wxRefundOrder.setRefundFee(refundMoney.intValue()); wxRefundOrder.setTransactionId(transactionId); // wxRefundOrder.setRefundDesc("订单退款"); wxRefundOrder.setNotifyUrl(notifyUrl); String sign = Codec.DoDigest.custom() .setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(wxRefundOrder.println(shopConfig.getSecret())) .toHexString() .toUpperCase(); wxRefundOrder.setSign(sign); String xml = Xmls.toXml(wxRefundOrder); KeyStore keyStore = KeyStore.getInstance("PKCS12"); File file = new File("cert/" + shopId + ".p12"); InputStream inputStream = new FileInputStream(file); keyStore.load(inputStream, shopId.toCharArray()); // 初始化密钥库 KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); factory.init(keyStore, shopId.toCharArray()); cn.hutool.http.HttpRequest request = cn.hutool.http.HttpRequest.post("https://api.mch.weixin.qq.com/secapi/pay/refund") // 自定义返回编码 .charset(CharsetUtil.CHARSET_UTF_8) // 禁用缓存 .disableCache() .setSSLSocketFactory(SSLSocketFactoryBuilder.create() .setKeyManagers(factory.getKeyManagers()) .setProtocol(SSLSocketFactoryBuilder.TLSv1).build()); cn.hutool.http.HttpResponse res = request.body(xml).execute(); if (200 != res.getStatus()) { throw BusinessRuntimeException.getInstance("请求wx_refund失败: " + res.body()); } Map wxRefund = Xmls.toMap(res.body()); log.info("微信返回值 xml 转 map: \n{}", wxRefund); if (!StringUtil.equals(wxRefund.get("return_code"), "SUCCESS") || !StringUtil.equals(wxRefund.get("result_code"), "SUCCESS")) { throw BusinessRuntimeException.getInstance("调用微信申请退款失败. 微信返回: " + wxRefund); } if (StringUtil.equals(wxRefund.get("return_code"), "SUCCESS") && !StringUtil.equals(wxRefund.get("result_code"), "SUCCESS")) { throw BusinessRuntimeException.getInstance("订单异常: " + wxRefund.get("err_code") + "[" + wxRefund.get("err_code_des") + "]"); } // 校验签名 boolean f = this.checkSignWithMd5(wxRefund, shopConfig.getSecret()); if (!f) { throw BusinessRuntimeException.getInstance("调用微信h5申请退款, 微信返回值的签名验证失败."); } log.info("------------------- 调用微信申请退款 结束 -------------------"); } @Override public Map getWxRefundDetail(String appId, String shopId, String outRefundNo) throws Exception { ShopConfig shopConfig = shopConfigMapper.selectOne(Wrappers.lambdaQuery(ShopConfig.class) .eq(ShopConfig::getAppId, shopId).last("limit 1")); if (shopConfig == null || StrUtil.isBlank(shopConfig.getSecret())) { throw BusinessRuntimeException.getInstance("微信退款查询商户不存在"); } Map requestData = new TreeMap<>(); requestData.put("appid", appId); requestData.put("mch_id", shopId); requestData.put("out_refund_no", outRefundNo); requestData.put("nonce_str", UUID.randomUUID().toString().replace("-", "")); requestData.put("sign", signWithMd5(requestData, shopConfig.getSecret())); HttpResponse response = J11HttpC.custom() .ofPost() .url("https://api.mch.weixin.qq.com/pay/refundquery") .headers(J11HttpC.ReqType.raw_xml) .body(HttpRequest.BodyPublishers.ofString(Xmls.toXml(requestData), IoKit.Charsets.UTF_8.getCharset())) .send(HttpResponse.BodyHandlers.ofString()); if (response.statusCode() != 200) { throw BusinessRuntimeException.getInstance("微信退款查询失败: " + response.body()); } Map result = Xmls.toMap(response.body()); if (!"SUCCESS".equals(result.get("return_code"))) { throw BusinessRuntimeException.getInstance("微信退款查询通信失败: " + result); } if (StrUtil.isNotBlank(result.get("mch_id")) && !shopId.equals(result.get("mch_id"))) { throw BusinessRuntimeException.getInstance("微信退款查询商户不匹配"); } if (StrUtil.isNotBlank(result.get("appid")) && !appId.equals(result.get("appid"))) { throw BusinessRuntimeException.getInstance("微信退款查询应用不匹配"); } if (!"SUCCESS".equals(result.get("result_code"))) { return result; } if (!checkSignWithMd5(result, shopConfig.getSecret())) { throw BusinessRuntimeException.getInstance("微信退款查询签名校验失败"); } return result; } private String signWithMd5(Map values, String secret) throws Exception { StringBuilder data = new StringBuilder(); values.entrySet().stream() .filter(entry -> !"sign".equals(entry.getKey()) && StrUtil.isNotBlank(entry.getValue())) .sorted(Map.Entry.comparingByKey()) .forEach(entry -> data.append(entry.getKey()).append('=').append(entry.getValue()).append('&')); data.append("key=").append(secret); return Codec.DoDigest.custom().setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(data.toString()).toHexString().toUpperCase(); } @Override public WxGzhQrCodeTicketRep getWxGzhQrCode(GzhBodyDto gzhBodyDto) throws Exception { SysConfig sysConfig = sysConfigService.getOne(Wrappers.lambdaQuery(SysConfig.class) .eq(SysConfig::getSysKey, Constant.QR_CODE_WX_APP_ID).last("limit 1")); //pc公众号扫码 String appid = "wx30f48135b1fd96bb"; if (sysConfig != null) { String sysValue = sysConfig.getSysValue(); if (StrUtil.isNotBlank(sysValue)) { appid = sysValue; } } String loginPhone = gzhBodyDto.getLoginPhone(); String loginEmail = gzhBodyDto.getLoginEmail(); String gzhQrCodeUrl = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", this.getAccessToken(appid)); //scene_str:场景值ID(字符串形式的ID),字符串类型,长度限制为1到64 String scene_str = RandomUtil.randomString(16); int expireTime = 60; if (StrUtil.isNotBlank(loginPhone) || StrUtil.isNotBlank(loginEmail)) { expireTime = 25; } DateTime dateTime = DateUtil.offsetSecond(DateTime.now(), expireTime); String body = String.format("{\"expire_seconds\": %s, \"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}", expireTime, "QR_STR_SCENE", scene_str); HttpResponse response = J11HttpC.custom() .url(gzhQrCodeUrl) .ofPost() .body(HttpRequest.BodyPublishers.ofString(body)) .send(HttpResponse.BodyHandlers.ofString()); if (200 != response.statusCode()) { throw BusinessRuntimeException.getInstance("获取微信登录二维码失败: " + response.body()); } String state = Codec.DoBase64.custom().setData(Jsons.toJson(gzhBodyDto)).encodeAsText(); redisService.set(RedisService.key.WX_GZH_QRCODE_SHARED_LOGIN.getNameFormat(scene_str), state, RedisService.key.WX_GZH_QRCODE_SHARED_LOGIN.getTimeout()); JSONObject re = Jsons.parseObject(response.body(), JSONObject.class); String ticket = re.getStr("ticket"); WxGzhQrCodeTicketRep wxGzhQrCodeTicketRep = new WxGzhQrCodeTicketRep(); wxGzhQrCodeTicketRep.setQrCodeUrl(String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket)); wxGzhQrCodeTicketRep.setSceneStr(scene_str); wxGzhQrCodeTicketRep.setExpireTime(dateTime.getTime()); return wxGzhQrCodeTicketRep; } @Override public String getNotExpiredWxGzhQrCode(String sceneKey) throws Exception { String gzhQrCodeUrl = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", this.getAccessToken()); String body = String.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}", "QR_LIMIT_STR_SCENE", sceneKey); HttpResponse response = J11HttpC.custom() .url(gzhQrCodeUrl) .ofPost() .body(HttpRequest.BodyPublishers.ofString(body)) .send(HttpResponse.BodyHandlers.ofString()); if (200 != response.statusCode()) { throw BusinessRuntimeException.getInstance("获取永久渠道二维码失败: " + response.body()); } JSONObject re = Jsons.parseObject(response.body(), JSONObject.class); String ticket = re.getStr("ticket"); String url = String.format("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s", ticket); return url; } @Override public Map getWxOrderDetail(String shopId, String orderNo) throws Exception { String url = "https://api.mch.weixin.qq.com/pay/orderquery"; // 拼接统一下单实体类 Map map = new TreeMap<>(); map.put("appid", weChatConfig.getAppid()); map.put("mch_id", shopId); map.put("out_trade_no", orderNo); map.put("nonce_str", "d1234"); String sign = getMd5Sign(map); map.put("sign", sign); String xml = Xmls.toXml(map); HttpResponse send = J11HttpC.custom() .ofPost() .url(url) .headers(J11HttpC.ReqType.raw_xml) .body(HttpRequest.BodyPublishers.ofString(xml, IoKit.Charsets.UTF_8.getCharset())) .send(HttpResponse.BodyHandlers.ofString()); Map orderSearch = Xmls.toMap(send.body()); ShopConfig shopConfig = shopConfigMapper.getByAppId(shopId); if (shopConfig == null || !checkSignWithMd5(orderSearch, shopConfig.getSecret())) { throw BusinessRuntimeException.getInstance("微信订单查询签名校验失败"); } log.info("微信返回值 xml转map: \n{}", orderSearch); if (!StringUtils.equals(orderSearch.get("result_code"), "SUCCESS")) { log.error("调用微信h5查询订单详情接口失败. 微信返回: " + orderSearch); // Preserve verified failure codes such as ORDERNOTEXIST for tri-state handling. return orderSearch; } return orderSearch; } @Override public String createWxAuthLoginUrl(String state, String appId, String redirectUri) { String authLoginUrl = String.format("https://open.weixin.qq.com/connect/qrconnect?" + "appid=%s&redirect_uri=%s&response_type=code&fast_login=0&scope=snsapi_login&state=%s#wechat_redirect", appId, redirectUri, state); return authLoginUrl; } @Override public String getMiniURLLink(String accessToken, MiniURLLink miniURLLink) throws Exception{ String url = String.format("https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s", accessToken); HttpResponse send = J11HttpC.custom() .url(url) .ofPost() .body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(miniURLLink))) .send(HttpResponse.BodyHandlers.ofString()); if (send.statusCode() != 200) { throw BusinessRuntimeException.getInstance("获取小程序链接错误"); } JSONObject body = Jsons.parseObject(send.body(), JSONObject.class); if (body.getInt("errcode") != 0) { throw BusinessRuntimeException.getInstance(body.getStr("errmsg")); } return body.getStr("url_link"); } /** * sha256_HMAC加密 * @param message 消息 * @param secret 秘钥 * @return 加密后字符串 */ public static String sha256_HMAC(String message,String secret) { String hash = ""; try { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(IoKit.Charsets.UTF_8.getCharset()), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] bytes = sha256_HMAC.doFinal(message.getBytes()); hash = byteArrayToHexString(bytes); } catch (Exception e) { System.out.println("Error HmacSHA256 ===========" + e.getMessage()); } return hash; } /** * 将加密后的字节数组转换成字符串 * * @param b 字节数组 * @return 字符串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder hs = new StringBuilder(); String stmp; for (int n = 0; b!=null && n < b.length; n++) { stmp = Integer.toHexString(b[n] & 0XFF); if (stmp.length() == 1) { hs.append('0'); } hs.append(stmp); } return hs.toString().toLowerCase(); } public String getMd5Sign(Map map) throws Exception { StringBuilder sb = new StringBuilder(); map.forEach((k, v) -> { sb.append(k).append("=").append(v).append("&"); }); sb.append("key").append("=").append(shopConfigMapper.getByAppId(map.get("mch_id")).getSecret()); String sign = Codec.DoDigest.custom() .setAlgorithm(Codec.DoDigest.Algorithm.MD5) .setStringData(sb.toString()) .toHexString() .toUpperCase(); return sign; } @Override public MiniProgramSession code2Session(String appId,String code) throws Exception { appId = Optional.ofNullable(appId).orElse(weChatConfig.getAppid()); return wxMpAccountOps.miniProgramCode2Session(appId,code); } @Override public void getMiniStudentIdentity(String openId, String code) throws Exception { String url = "https://api.weixin.qq.com/intp/quickcheckstudentidentity?access_token=" + getAccessToken(certMiniConfig.getAppId()); JSONObject params = new JSONObject(); params.putOpt("openid", openId); params.putOpt("wx_studentcheck_code", code); HttpResponse response = J11HttpC.custom().url(url).ofPost().body(HttpRequest.BodyPublishers.ofString(params.toString())).send(HttpResponse.BodyHandlers.ofString()); JSONObject re = Jsons.parseObject(response.body(), JSONObject.class); if (re.getInt("errcode") != 0) { log.info("获取学生信息失败:{}", re.getStr("errmsg")); throw BusinessRuntimeException.getInstance("获取认证信息失败"); } Integer bindStatus = Optional.ofNullable(re.getInt("bind_status")).orElse(1); //绑定状态: //1-未绑定 //2-审核中 //3-已绑定 if (bindStatus == 1) { throw BusinessRuntimeException.getInstance("大学生身份未绑定"); } if (bindStatus == 2) { throw BusinessRuntimeException.getInstance("大学生身份审核中"); } } @Override public String generateSchema(String accessToken, TargetAppletSchemeReq targetAppletSchemeReq) throws Exception { String url = "https://api.weixin.qq.com/wxa/generatescheme?access_token=" + accessToken; HttpResponse res = J11HttpC.custom() .ofPost() .url(url) .body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(targetAppletSchemeReq))) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("获取用户信息失败: " + IoKit.toString(res.body())); } JSONObject jsonObject = Jsons.parseObject(res.body(), JSONObject.class); if (jsonObject.getInt("errcode") != 0) { throw BusinessRuntimeException.getInstance(jsonObject.getStr("errmsg")); } if (StrUtil.isEmpty(jsonObject.getStr("openlink"))) { throw BusinessRuntimeException.getInstance("系统异常.."); } return jsonObject.getStr("openlink"); } @Override public byte[] generateCode(String accessToken, String path) throws Exception { String url = "https://api.weixin.qq.com/wxa/getwxacode?access_token=" + accessToken; JSONObject params = new JSONObject(); params.putOpt("path", path); HttpResponse res = J11HttpC.custom() .ofPost() .url(url) .body(HttpRequest.BodyPublishers.ofString(params.toString())) .send(HttpResponse.BodyHandlers.ofByteArray()); if (200 != res.statusCode()) { throw BusinessRuntimeException.getInstance("获取用户信息失败: " + IoKit.toString(res.body())); } return res.body(); } }