有没有使用 OAuth2 访问 Quickbooks API 的简单方法?

Is there a simple way to access the Quickbooks API using OAuth2?

我只需要使用 API 和 C# 访问我的 Quickbooks Online (QBO) 公司中的数据。但现在他们已经转向 OAuth2,它看起来非常复杂。例如,它建议我需要一个重定向 URI 来接收授权代码,但我正在编写一个简单的控制台应用程序来将数据导出到 QBO,并且不想托管 URI 端点来执行此操作。

了解如何获取和管理以下内容也令人困惑:

一定有一种简单的方法可以做到这一点。例如,对于 Stripe,您只需管理一个 API 键。

经过一些研究,我发现这可以通过一种简单的方式完成。您只需要保留一份刷新令牌的副本(可能在 read/write 文件中)。当您想要访问 API 时,只需调用 OAuth2Client.RefreshTokenAsync() 即可获取访问令牌。可以使用 OAuth2 游乐场找到所需的所有其他项目。

然后,访问令牌最多可与 API 一起使用一小时。您还可以取回更新后的刷新令牌。如果发生这种情况,请将其存储起来以备将来使用。刷新令牌最多可以使用 100 天,然后您必须使用从刷新令牌操作返回的较新版本。

以下是如何使用 C# 中的 API 的较长版本:

  1. 创建一个应用程序,但不要在 QBO 应用程序商店中发布它。为此,请登录 developer.intuit.com 使用您的 QBO 帐户。转到 'My Apps' 然后创建一个应用程序(例如称为“MyQBOApiApp”)。这只需要做一次。保留默认重定向 URL 设置为 OAuth2 操场,因为这是唯一需要的重定向 URL。

  2. 从 'Production Keys' 部分获取生产 客户端 ID客户端密码 101=] 应用程序的选项卡。 (记录这些以供在您的 C# 程序中使用,因为它们不会更改)

  3. 转到 https://developer.intuit.com/v2/ui#/playground

  4. 的 OAuth 2.0 游乐场
  5. 在步骤 1 'Get Authorization Code' select 下拉框列表中的 MyQBOApiApp(Production)

  6. 在Select范围列表select'Accounting'如果你只需要read/write数据到你的QBO公司

  7. 点击'Get authorization code'

  8. 将您的 QBO 公司连接到 MyQBOApiApp 应用程序

  9. 在 playground 页面的第 2 步 'Get OAuth 2.0 token from auth code' 中单击 'Get tokens'。这将为您 API 访问贵公司获得一个刷新令牌。

  10. 跳至 playground 页面上的步骤 4 'Refresh access token'。访问令牌只能使用 59 分钟 所以只保留 'Refresh Token' 因为它可以使用 100 天来获取新的访问令牌和刷新令牌。将其存储在您的 C# 程序可以读取和写入的位置(例如文件或数据库)

  11. realmID 可从步骤 3 获得。'Make API calls'。 (记录下来以便在您的 C# 程序中使用,因为它不会改变)

  12. 将 IppDotNetSdkForQuickBooksApiV3 NuGet 包添加到您的 C# 程序中。提供对 API.

  13. 的轻松访问
  14. 确保您使用的是 .Net Framework 4.6.1 或更高版本,因为 QBO 需要 TLS 1.2 连接

  15. 遗憾的是,.Net 控制台应用程序默认不使用 TLS 1.2。因此,在 C# 程序启动的某处添加这行代码:

    // Have to explicitly set TLS 1.2 as QBO APIs require it
    System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
    

在访问 API 之前,您需要这样的代码来获取访问令牌:

public static string GetAccessToken()
{
    var oauth2Client = new OAuth2Client(CLIENTID_FROM_STEP_2, 
            CLIENT_SECRET_FROM_STEP_2,
            // Redirect not used but matches entry for app
            "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl",
            "production"); // environment is “sandbox” or “production”

    var previousRefreshToken = ReadRefreshTokenFromWhereItIsStored();
    var tokenResp = oauth2Client.RefreshTokenAsync(previousRefreshToken );
    tokenResp.Wait();
    var data = tokenResp.Result;

    if ( !String.IsNullOrEmpty(data.Error) || String.IsNullOrEmpty(data.RefreshToken) || 
          String.IsNullOrEmpty(data.AccessToken))
    {
        throw new Exception("Refresh token failed - " + data.Error);
    }

    // If we've got a new refresh_token store it in the file
    if (previousRefreshToken != data.RefreshToken)
    {
        Console.WriteLine("Writing new refresh token : " + data.RefreshToken);
        WriteNewRefreshTokenToWhereItIsStored(data.RefreshToken)

    }
    return data.AccessToken;
}

您需要编写函数 ReadRefreshTokenFromWhereItIsStored()WriteNewRefreshTokenToWhereItIsStored() 以从持久存储加载和保存刷新令牌.

QBO 中的所有 API 访问都从服务上下文开始。您可以使用如下代码创建一个:

static public ServiceContext GetServiceContext()
{
    var accessToken = GetAccessToken(); // Code from above
    var oauthValidator = new OAuth2RequestValidator(accessToken);

    ServiceContext qboContext = new ServiceContext(REALMID_PROD_FROM_STEP10,
            IntuitServicesType.QBO, oauthValidator);

    return qboContext;
}

要访问数据,您可以像这样创建数据服务:

var service = new DataService(GetServiceContext());
[HttpPost("qb/refresh-token")]
        public async Task<ActionResult> GetRefressToken()
        {
            JObject result = new JObject();
            //Request Oauth2 tokens
            var tokenClient = new OAuth2Client(
                "your client id",
                "secret",
                "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl",
                "sandbox"); // environment is “sandbox” or “production”

                    
            var previousRefreshToken = "your refresh token id ";Get it from Playgroud for first time**strong text**//ReadRefreshTokenFromWhereItIsStored();
            var tokenResp = await tokenClient.RefreshTokenAsync(previousRefreshToken);
           

            if (!String.IsNullOrEmpty(tokenResp.Error) || String.IsNullOrEmpty(tokenResp.RefreshToken) ||
                  String.IsNullOrEmpty(tokenResp.AccessToken))
            {
                throw new Exception("Refresh token failed - " + tokenResp.Error);
            }

            // If we've got a new refresh_token store it in the file
            if (previousRefreshToken != tokenResp.RefreshToken)
            {
                Console.WriteLine("Writing new refresh token : " + tokenResp.RefreshToken);
                //WriteNewRefreshTokenToWhereItIsStored(data.RefreshToken);
             
            }
            return Ok(tokenResp.AccessToken);
            //return new Tuple<string, string>(tokenResp.RefreshToken, tokenResp.AccessToken);
        }