/// <summary>
        /// code -> oauth.access_token + openid
        /// </summary>
        /// <param name="code">用于换取网页授权access_token。此code只能使用一次,5分钟未被使用自动过期。</param>
        /// <param name="redirectUri"></param>
        /// <returns></returns>
        protected virtual async Task <WeixinOAuthTokenResponse> CustomExchangeCodeAsync(string code, string redirectUri)
        {
            var query = new QueryBuilder()
            {
                { "appid", Options.AppId },
                { "secret", Options.AppSecret },
                { "code", code },
                { "grant_type", "authorization_code" },
                { "redirect_uri", redirectUri }
            };
            var url = Options.TokenEndpoint + query;

            Logger.LogInformation($"Exchanging code via {url}...");
            var response = await Backchannel.GetAsync(url, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                var error = "An error occured while exchanging the code.";
                Logger.LogError($"{error} The remote server returned a {response.StatusCode} response with the following payload: {response.Headers.ToString()} {await response.Content.ReadAsStringAsync()}");
                //throw new HttpRequestException($"{error}");
                return(WeixinOAuthTokenResponse.Failed(new Exception(error)));
            }
            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
            var result  = WeixinOAuthTokenResponse.Success(payload);

            //错误时微信会返回错误JSON数据包,示例如下: { "errcode":40029,"errmsg":"invalid code"}
            if (string.IsNullOrWhiteSpace(result.AccessToken))
            {
                int errorCode    = WeixinOAuthHandlerHelper.GetErrorCode(payload);
                var errorMessage = WeixinOAuthHandlerHelper.GetErrorMessage(payload);
                return(WeixinOAuthTokenResponse.Failed(new Exception($"The remote server returned an error while exchanging the code. {errorCode} {errorMessage}")));
            }
            return(result);
        }
        private async Task <ClaimsIdentity> RetrieveUserInfoAsync(string accessToken, string openId, ClaimsIdentity identity)
        {
            //call userinfo
            var query = new QueryBuilder();

            query.Add("access_token", accessToken);
            query.Add("openid", openId);
            query.Add("lang", Options.LanguageCode);
            var url = Options.UserInformationEndpoint + query;

            Logger.LogInformation($"Retrieving user info via {url}...");
            var response = await Backchannel.GetAsync(url, Context.RequestAborted);

            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError($"An error occurred while retrieving the user profile: the remote server returned a {response.StatusCode} response with the following payload: {response.Headers.ToString()} {await response.Content.ReadAsStringAsync()}");
                throw new HttpRequestException("An error occured while retrieving the user profile.");
            }
            var payload   = JObject.Parse(await response.Content.ReadAsStringAsync());
            int errorCode = WeixinOAuthHandlerHelper.GetErrorCode(payload);

            if (errorCode != 0)
            {
                var errorMessage = WeixinOAuthHandlerHelper.GetErrorMessage(payload);
                Logger.LogError($"The remote server returned an error while retrieving the user profile. {errorCode} {errorMessage}");
                throw new Exception($"The remote server returned an error while retrieving the user profile. {errorCode} {errorMessage}");
            }
            else
            {
                //提取userinfo
                // std:Name
                var nickname = WeixinOAuthHandlerHelper.GetNickName(payload);
                identity.AddOptionalClaim(ClaimTypes.Name, nickname, this.Options.ClaimsIssuer);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.NickName, nickname, Options.ClaimsIssuer);

                var sex = WeixinOAuthHandlerHelper.GetGender(payload);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.Gender, sex, Options.ClaimsIssuer);

                var province = WeixinOAuthHandlerHelper.GetProvince(payload);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.Province, province, Options.ClaimsIssuer);

                var city = WeixinOAuthHandlerHelper.GetCity(payload);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.City, city, Options.ClaimsIssuer);

                var country = WeixinOAuthHandlerHelper.GetCountry(payload);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.Country, country, Options.ClaimsIssuer);

                var headImageUrl = WeixinOAuthHandlerHelper.GetHeadImageUrl(payload);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.HeadImageUrl, headImageUrl, Options.ClaimsIssuer);

                var privileges = WeixinOAuthHandlerHelper.GetPrivileges(payload);
                foreach (string privilege in privileges)
                {
                    identity.AddOptionalClaim(WeixinOAuthClaimTypes.Privilege, privilege, Options.ClaimsIssuer);
                }

                var unionId = WeixinOAuthHandlerHelper.GetUnionId(payload);
                identity.AddOptionalClaim(WeixinOAuthClaimTypes.UnionId, unionId, Options.ClaimsIssuer);
            }

            return(identity);
        }