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<WxApp>().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<byte[]> 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<byte[]> 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<byte[]> 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<byte[]> 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<String, String> 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<String, String> map) throws Exception {
       return checkSignWithMd5(map,weChatConfig.getShopKey());
    }
    @Override
    public boolean checkSignWithMd5(Map<String, String> 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<byte[]> 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<String, String> 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<String> 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<String, String> 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 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"));
        String appid = null;
        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<String> 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<String> 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<String, String> getWxOrderDetail(String shopId, String orderNo) throws Exception {
        String url = "https://api.mch.weixin.qq.com/pay/orderquery";
        // 拼接统一下单实体类
        Map<String, String> 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<String> 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<String, String> orderSearch = Xmls.toMap(send.body());
        log.info("微信返回值 xml转map: \n{}", orderSearch);
        if (!StringUtils.equals(orderSearch.get("result_code"), "SUCCESS")) {
            log.error("调用微信h5查询订单详情接口失败. 微信返回: " + orderSearch);
            return null;
        }
        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<String> 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<String, String> 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<String> 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<byte[]> 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<byte[]> 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();
    }
}
