带筛选器的QuickBooks联机查询每次都返回401

带筛选器的QuickBooks联机查询每次都返回401,quickbooks,intuit-partner-platform,quickbooks-online,Quickbooks,Intuit Partner Platform,Quickbooks Online,我已经成功地使用POST和内容类型application/xml创建了对象 我还成功地使用Content-Type application/x-www-form-urlencoded进行了查询,其中包含一个空白的请求体,该请求体根据我指定的URI返回所有对象类型 我也可以在请求体中使用类似PageNum=1&ResultsPerPage=1的内容,我已经知道如何将其合并到签名中,以便获得有效的响应 然而,无论我如何格式化它,当我尝试使用过滤器时,除了401响应之外,我无法得到任何东西(一些基本的

我已经成功地使用POST和内容类型application/xml创建了对象

我还成功地使用Content-Type application/x-www-form-urlencoded进行了查询,其中包含一个空白的请求体,该请求体根据我指定的URI返回所有对象类型

我也可以在请求体中使用类似PageNum=1&ResultsPerPage=1的内容,我已经知道如何将其合并到签名中,以便获得有效的响应

然而,无论我如何格式化它,当我尝试使用过滤器时,除了401响应之外,我无法得到任何东西(一些基本的东西,比如filter=FAMILYNAME:EQUALS:Doe)。我已经阅读了OAuth Core 1.0修订版A规范中关于如何使用[RFC3986]百分比编码转义所有参数名和值的内容。然而,我觉得我错过了一个步骤或格式不正确。我在Intuit的论坛上搜索时发现了不一致的信息,即正确的格式到底是什么

在此方面的任何帮助都将不胜感激。我已经为此挣扎了一周了

尝试使用筛选器时得到的响应是: HTTP状态401-消息=验证OAuth的异常;错误代码=003200;状态代码=401

----更新----

当我尝试将过滤器与新的IPP开发工具IPP API Explorer一起使用时,我看到了相同的错误。我正在使用IDSV2QBOAPI浏览器。我能够使用该工具检索所有帖子,回复显示我的所有客户,但当我尝试使用过滤器时,我得到: 服务器错误 401-未经授权:由于凭据无效,访问被拒绝。 您没有使用您提供的凭据查看此目录或页面的权限

有什么想法吗?如果我从API资源管理器工具中得到相同的错误,它会让我认为问题完全是另外一回事

----最后更新----

我终于成功地使用了过滤器,我相信我已经找到了我的问题所在。我一直怀疑我是否能够使用“PageNum=1&ResultsPerPage=1”这样的分页方式来进行查询,但却无法获得“Filter=FAMILYNAME:EQUALS:Doe”这样的查询。我怀疑过滤器格式中的空白有问题。之前让我无法追踪的是,我无法让过滤器在IDS V2 QBO API资源管理器中工作。这让我怀疑还有别的事情发生。我决定忽略API资源管理器,把重点放在为什么我可以让它以一种方式工作,而不能以另一种方式工作


我相信我的问题归结为签名中对过滤器值的不正确编码。这就解释了我遇到的401个无效签名错误

规范化后,“过滤器=名称:等于:Doe”变为“过滤器=名称%20%3ADEE等于%20%3ADoe”

应给出“筛选器%3DName%2520%253aQuals%2520%253ADoe”的百分比编码


本质上,你必须对空白和冒号进行“双重”编码,而不是等号。我尝试了许多编码的排列方式,但我相信我的错误是我不是“双重”编码,或者当我是双重编码时,我包含了“=”符号。无论哪种方式都会破坏你的签名。谢谢大家的意见。

在API资源管理器中,您是否会收到具有相同请求的401

另外,您是在使用静态基本URL还是在运行时检索它


如果您使用的是静态基本URL,请尝试切换到运行时基本URL,以查看是否仍然出现错误。

peterl在此处回答了我的一个问题,也可能回答了您的问题。我一直试图把过滤器放在身体里,而它们本应该放在头上的。下面是peterl为特定客户获取所有未付发票(未清余额大于0.00)的代码示例

public List getqbounpaidvoices(数据服务数据服务,int起始页,int结果每页,IdType客户ID)
{
StringBuilder requestXML=新建StringBuilder();
StringBuilder responseXML=新的StringBuilder();
var requestBody=String.Format(“PageNum={0}&ResultsPerPage={1}&Filter=OpenBalance:GreaterThan:0.00:AND:CustomerId:EQUALS:{2}”,起始页,ResultsPerPage,CustomerId.Value);
HttpWebRequest HttpWebRequest=WebRequest.Create(dataServices.ServiceContext.BaseUrl+“invoices/v2/”+dataServices.ServiceContext.RealmId)作为HttpWebRequest;
httpWebRequest.Method=“POST”;
httpWebRequest.ContentType=“应用程序/x-www-form-urlencoded”;
添加(“授权”,GetDevDefinedOAuthHeader(httpWebRequest,requestBody));
Append(requestBody);
UTF8Encoding=新的UTF8Encoding();
byte[]content=encoding.GetBytes(requestXML.ToString());
使用(var stream=httpWebRequest.GetRequestStream())
{
stream.Write(content,0,content.Length);
}
HttpWebResponse HttpWebResponse=httpWebRequest.GetResponse()作为HttpWebResponse;
使用(Stream data=httpWebResponse.GetResponseStream())
{
Intuit.Ipp.Data.Qbo.SearchResults SearchResults=(Intuit.Ipp.Data.Qbo.SearchResults)dataServices.ServiceContext.Serializer.Deserialize(新的StreamReader(Data.ReadToEnd());
return((Intuit.Ipp.Data.Qbo.Invoices)searchResults.CdmCollections).Invoice.ToList();
}
}
受保护的字符串GetDevDefinedOAuthHeader(HttpWebRequest webRequest,字符串requestBody)
{
OAuthConsumerContext consumerContext=新的OAuthConsumerContext
{
ConsumerKey=ConsumerKey,
ConsumerCret=ConsumerCret,
SignatureMethod=SignatureMethod.HmacSha1,
UseHeaderForOAuthParameters=true
};
consumerContext.UseHeaderForOAuthParameters=true;
//未使用URI-我们已经有Oauth令牌
OAuthSession oSession=新的OAuthSession(consumerContext)https://www.example.com",
"https://www.example.com",
“https://
public List<Intuit.Ipp.Data.Qbo.Invoice> GetQboUnpaidInvoices(DataServices dataServices, int startPage, int resultsPerPage,  IdType CustomerId)
{
    StringBuilder requestXML = new StringBuilder();
    StringBuilder responseXML = new StringBuilder();

    var requestBody = String.Format("PageNum={0}&ResultsPerPage={1}&Filter=OpenBalance :GreaterThan: 0.00 :AND: CustomerId :EQUALS: {2}", startPage, resultsPerPage, CustomerId.Value);

    HttpWebRequest httpWebRequest = WebRequest.Create(dataServices.ServiceContext.BaseUrl + "invoices/v2/" + dataServices.ServiceContext.RealmId) as HttpWebRequest;
    httpWebRequest.Method = "POST";
    httpWebRequest.ContentType = "application/x-www-form-urlencoded";
    httpWebRequest.Headers.Add("Authorization", GetDevDefinedOAuthHeader(httpWebRequest, requestBody));
    requestXML.Append(requestBody);
    UTF8Encoding encoding = new UTF8Encoding();
    byte[] content = encoding.GetBytes(requestXML.ToString());
    using (var stream = httpWebRequest.GetRequestStream())
    {
        stream.Write(content, 0, content.Length);
    }
    HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
    using (Stream data = httpWebResponse.GetResponseStream())
    {
        Intuit.Ipp.Data.Qbo.SearchResults searchResults = (Intuit.Ipp.Data.Qbo.SearchResults)dataServices.ServiceContext.Serializer.Deserialize<Intuit.Ipp.Data.Qbo.SearchResults>(new StreamReader(data).ReadToEnd());
        return ((Intuit.Ipp.Data.Qbo.Invoices)searchResults.CdmCollections).Invoice.ToList();
    }

}

protected string GetDevDefinedOAuthHeader(HttpWebRequest webRequest, string requestBody)
{

    OAuthConsumerContext consumerContext = new OAuthConsumerContext
    {
        ConsumerKey = consumerKey,
        ConsumerSecret = consumerSecret,
        SignatureMethod = SignatureMethod.HmacSha1,
        UseHeaderForOAuthParameters = true

    };

    consumerContext.UseHeaderForOAuthParameters = true;

    //URIs not used - we already have Oauth tokens
    OAuthSession oSession = new OAuthSession(consumerContext, "https://www.example.com",
                            "https://www.example.com",
                            "https://www.example.com");


    oSession.AccessToken = new TokenBase
    {
        Token = accessToken,
        ConsumerKey = consumerKey,
        TokenSecret = accessTokenSecret
    };

    IConsumerRequest consumerRequest = oSession.Request();
    consumerRequest = ConsumerRequestExtensions.ForMethod(consumerRequest, webRequest.Method);
    consumerRequest = ConsumerRequestExtensions.ForUri(consumerRequest, webRequest.RequestUri);
    if (webRequest.Headers.Count > 0)
    {
        ConsumerRequestExtensions.AlterContext(consumerRequest, context => context.Headers = webRequest.Headers);
        if (webRequest.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded")
        {
            Dictionary<string, string> formParameters = new Dictionary<string, string>();
            foreach (string formParameter in requestBody.Split('&'))
            {
                formParameters.Add(formParameter.Split('=')[0], formParameter.Split('=')[1]);
            }
            consumerRequest = consumerRequest.WithFormParameters(formParameters);
        }
    }

    consumerRequest = consumerRequest.SignWithToken();
    return consumerRequest.Context.GenerateOAuthParametersForHeader();
}