QBO API 在同一步骤中连接和授权

QBO API Connect and Authorize in the same step

我正在使用 QBO API SDK (IppDotNetSdkQuickBooksApiV3),但不知道如何让用户连接到 QuickBooks 并授权我的应用程序。它目前分两 (2) 个步骤进行:

  1. 用户点击 "Connect To Intuit" 并被发送到 Intuit 进行登录
  2. 他们被重定向回我的应用程序,然后必须再次连接他们的实际文件

我显然遗漏了什么,但不知道是什么。我正在使用应用程序内置的 ipp:connecttointuit 功能,所以我不知道如何针对我正在寻找的结果对其进行自定义。

我的应用程序可以通过上述两个步骤运行,但是我无法使用上面详述的过程将我的应用程序列在 apps.com 网站中。他们 (apps.com) 希望用户使用他们的 QBO 凭据登录,授权应用程序,然后自动将用户重定向回我的网站,应用程序正常运行。他们不想要重复授权(我不能怪他们)。

完全卡住了。我是一个不错的程序员,但没有使用 OpenId 或 OAuth 的经验。


protected void Page_Load(object sender, EventArgs e)
        {
            var openIdRelyingParty = new OpenIdRelyingParty();
            var openid_identifier = ConfigurationManager.AppSettings["openid_identifier"];
            var returnUrl = "~/OpenID/Connect";
            var response = openIdRelyingParty.GetResponse();
            if (response == null)
            {
                // Stage 2: user submitting Identifier
                Identifier id;
                if (Identifier.TryParse(openid_identifier, out id))
                {
                    IAuthenticationRequest request = openIdRelyingParty.CreateRequest(openid_identifier);
                    FetchRequest fetch = new FetchRequest();
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email));
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.FullName));
                    fetch.Attributes.Add(new AttributeRequest("http://axschema.org/intuit/realmId"));
                    request.AddExtension(fetch);
                    request.RedirectToProvider();
                }
            }
            else
            {
                if (response.FriendlyIdentifierForDisplay == null)
                {
                    Response.Redirect("~/OpenID/Connect");
                }

                // Stage 3: OpenID Provider sending assertion response
                //Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
                FetchResponse fetch = response.GetExtension<FetchResponse>();
                if (fetch != null)
                {
                    var openIdEmail = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
                    var openIdFullName = fetch.GetAttributeValue(WellKnownAttributes.Name.FullName);
                    var openIdRealmId = fetch.GetAttributeValue("http://axschema.org/intuit/realmId");

                    string userName = Membership.GetUserNameByEmail(openIdEmail);

                    //If username is null---------------------------------------------------
                    if (userName == null)
                    {

                        //DG added this---------------------------
                        String NewPassword = Membership.GeneratePassword(6, 1);
                        Membership.CreateUser(openIdEmail, NewPassword, openIdEmail);
                        //DG added this----------------------------

                        //Membership.CreateUser(openIdEmail, Guid.NewGuid().ToString(), openIdEmail);

                        FormsAuthentication.SetAuthCookie(openIdEmail, true);
                        //if (Request.QueryString["Subscribe"] != null)
                        //{
                        String csname = "DirectConnectScript";
                        Type cstype = this.GetType();
                        ClientScriptManager csm = Page.ClientScript;

                        // Check to see if the startup script is already registered.
                        if (!csm.IsStartupScriptRegistered(cstype, csname))
                        {
                            StringBuilder cstext = new StringBuilder();
                            cstext.AppendLine("<script>");
                            cstext.AppendLine("$(document).ready(function () {");
                            cstext.AppendLine("intuit.ipp.anywhere.directConnectToIntuit();");
                            cstext.AppendLine("});");
                            cstext.AppendLine("</script>");
                            csm.RegisterStartupScript(cstype, csname, cstext.ToString());
                            //}
                        }

                    }
                    else if (Request.QueryString["Disconnect"] != null)
                    {
                        RestHelper.clearProfile(RestProfile.GetRestProfile());
                        Response.Redirect("~/ManageConnection");
                    }

                    //If username is not null---------------------------------------------------
                    else if (userName != null)
                    {
                        FormsAuthentication.SetAuthCookie(userName, true);

                        if (!string.IsNullOrEmpty(returnUrl))
                        {
                            Response.Redirect("~/ManageConnection");
                        }
                    }
                }

            }
        }

我同情你,我自己花了一些时间才搞定。

这是我的 MVC 版本。希望对你有帮助。

它以 QuickBooks/Index 开头。使用 QB 弹出窗口获取我的应用程序的权限,然后从那里开始。作为奖励,如果用户已经登录到 QB,如果他们在过去授予权限,他们将自动登录到该应用程序。这是因为我将加密的令牌保存在数据库中。 (忽略大部分 Session 内容,我只是从未将其从我创建代码的示例中删除)。

不管怎样,这里有很多 post 的代码。 如果您需要任何说明,请随时在评论中提问

我假设你有这样的东西接收 OAuthResponse 并重定向回 /QuickBooks/Index(在你的情况下,你在问题中 posted 的页面)

public class OauthResponseController : Controller
{
    /// <summary>
    /// OAuthVerifyer, RealmId, DataSource
    /// </summary>
    private String _oauthVerifyer, _realmid, _dataSource;

    /// <summary>
    /// Action Results for Index, OAuthToken, OAuthVerifyer and RealmID is recieved as part of Response
    /// and are stored inside Session object for future references
    /// NOTE: Session storage is only used for demonstration purpose only.
    /// </summary>
    /// <returns>View Result.</returns>
    public ViewResult Index()
    {
        if (Request.QueryString.HasKeys())
        {
            // This value is used to Get Access Token.
            _oauthVerifyer = Request.QueryString.GetValues("oauth_verifier").FirstOrDefault().ToString();
            if (_oauthVerifyer.Length == 1)
            {
                _oauthVerifyer = Request.QueryString["oauth_verifier"].ToString();
            }
            _realmid = Request.QueryString.GetValues("realmId").FirstOrDefault().ToString();
            if (_realmid.Length == 1)
            {
                _realmid = Request.QueryString["realmId"].ToString();
            }
            Session["Realm"] = _realmid;

            //If dataSource is QBO call QuickBooks Online Services, else call QuickBooks Desktop Services
            _dataSource = Request.QueryString.GetValues("dataSource").FirstOrDefault().ToString();
            if (_dataSource.Length == 1)
            {
                _dataSource = Request.QueryString["dataSource"].ToString();
            }
            Session["DataSource"] = _dataSource;

            getAccessToken();


            //Production applications should securely store the Access Token.
            //In this template, encrypted Oauth access token is persisted in OauthAccessTokenStorage.xml
            OauthAccessTokenStorageHelper.StoreOauthAccessToken();

            // This value is used to redirect to Default.aspx from Cleanup page when user clicks on ConnectToInuit widget.
            Session["RedirectToDefault"] = true;
        }
        else
        {
            Response.Write("No oauth token was received");
        }

        return View(); // This will redirect to /QuickBooks/OpenIdIndex which is almost the same as the code that you have posted
    }

    /// <summary>
    /// Gets the OAuth Token
    /// </summary>
    private void getAccessToken()
    {
        IOAuthSession clientSession = CreateSession();
        try
        {
            IToken accessToken = clientSession.ExchangeRequestTokenForAccessToken((IToken)Session["requestToken"], _oauthVerifyer);
            Session["AccessToken"] = accessToken.Token;

            // Add flag to session which tells that accessToken is in session
            Session["Flag"] = true;

            // Remove the Invalid Access token since we got the new access token
            Session.Remove("InvalidAccessToken");
            Session["AccessTokenSecret"] = accessToken.TokenSecret;
        }
        catch (Exception ex)
        {
            //Handle Exception if token is rejected or exchange of Request Token for Access Token failed.
            throw ex;
        }

    }

    /// <summary>
    /// Creates User Session
    /// </summary>
    /// <returns>OAuth Session.</returns>
    private IOAuthSession CreateSession()
    {
        OAuthConsumerContext consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = ConfigurationManager.AppSettings["consumerKey"].ToString(),
            ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"].ToString(),
            SignatureMethod = SignatureMethod.HmacSha1
        };

        return new OAuthSession(consumerContext,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlRequestToken,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl,
                                         Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlAccessToken);
    }
}

这是我的对应页面。

public class QuickBooksController : Controller
{
    private readonly IQueryChannel _queryChannel;
    private readonly ICommandChannel _commandChannel;
    public QuickBooksController(IQueryChannel queryChannel, ICommandChannel commandChannel)
    {
        _queryChannel = queryChannel;
        _commandChannel = commandChannel;
    }
    /// <summary>
    /// OpenId Relying Party
    /// </summary>
    private static OpenIdRelyingParty openid = new OpenIdRelyingParty();

    public ActionResult Index(string returnurl)
    {
        QuickBooksAuthStore.Load();
        if (QuickBooksContext.AccessToken != null)
        {
            if (!new Customers().CheckConnection())
            {
                QuickBooksContext.FriendlyName = null;
                OauthAccessTokenStorageHelper.RemoveInvalidOauthAccessToken(QuickBooksContext.FriendlyEmail);
                QuickBooksContext.AccessToken = null;
            }
        }
        if (returnurl != null || QuickBooksContext.QuickReturnUrl != null)
        {
            if (returnurl != null)
            {
               QuickBooksContext.QuickReturnUrl = returnurl; 
            }
            if (QuickBooksContext.AccessToken != null)
            {
                var connected = new Customers().CheckConnection();
                if (connected)
                {
                    returnurl = QuickBooksContext.QuickReturnUrl;
                    QuickBooksContext.QuickReturnUrl = null;
                    return Redirect(returnurl);
                }
            }  
        }
        return View();
    }

    public RedirectResult OpenIdIndex()
    {
        var openid_identifier = ConfigurationManager.AppSettings["openid_identifier"] + SessionContext.CurrentUser.MasterCompanyId;
        var response = openid.GetResponse();
        if (response == null)
        {
            // Stage 2: user submitting Identifier
            Identifier id;
            if (Identifier.TryParse(openid_identifier, out id))
            {
                try
                {
                    IAuthenticationRequest request = openid.CreateRequest(openid_identifier);
                    FetchRequest fetch = new FetchRequest();
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Contact.Email));
                    fetch.Attributes.Add(new AttributeRequest(WellKnownAttributes.Name.FullName));
                    request.AddExtension(fetch);
                    request.RedirectToProvider();
                }
                catch (ProtocolException ex)
                {
                    throw ex;
                }
            }
        }
        else
        {
            if (response.FriendlyIdentifierForDisplay == null)
            {
                Response.Redirect("/OpenId");
            }

            // Stage 3: OpenID Provider sending assertion response, storing the response in Session object is only for demonstration purpose
            Session["FriendlyIdentifier"] = response.FriendlyIdentifierForDisplay;
            FetchResponse fetch = response.GetExtension<FetchResponse>();
            if (fetch != null)
            {
                Session["OpenIdResponse"] = "True";
                Session["FriendlyEmail"] = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);// emailAddresses.Count > 0 ? emailAddresses[0] : null;
                Session["FriendlyName"] = fetch.GetAttributeValue(WellKnownAttributes.Name.FullName);//fullNames.Count > 0 ? fullNames[0] : null;

                OauthAccessTokenStorageHelper.GetOauthAccessTokenForUser(Session["FriendlyEmail"].ToString());
                QuickBooksAuthStore.UpdateFriendlyId(Session["FriendlyIdentifier"].ToString(), Session["FriendlyName"].ToString());
            }
        }

        string query = Request.Url.Query;
        if (!string.IsNullOrWhiteSpace(query) && query.ToLower().Contains("disconnect=true"))
        {
            Session["AccessToken"] = "dummyAccessToken";
            Session["AccessTokenSecret"] = "dummyAccessTokenSecret";
            Session["Flag"] = true;
            return Redirect("QuickBooks/CleanupOnDisconnect");
        }
        return Redirect("/QuickBooks/Index");
    }

    /// <summary>
    /// Service response.
    /// </summary>
    private String txtServiceResponse = "";

    /// <summary>
    /// Disconnect Flag.
    /// </summary>
    protected String DisconnectFlg = "";
    public ActionResult Disconnect()
    {
        OAuthConsumerContext consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = ConfigurationManager.AppSettings["consumerKey"].ToString(),
            SignatureMethod = SignatureMethod.HmacSha1,
            ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"].ToString()
        };

        OAuthSession oSession = new OAuthSession(consumerContext, Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlRequestToken,
                              Constants.OauthEndPoints.AuthorizeUrl,
                              Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlAccessToken);

        oSession.ConsumerContext.UseHeaderForOAuthParameters = true;
        if ((Session["AccessToken"] + "").Length > 0)
        {
            Session["FriendlyName"] = null;
            oSession.AccessToken = new TokenBase
            {
                Token = Session["AccessToken"].ToString(),
                ConsumerKey = ConfigurationManager.AppSettings["consumerKey"].ToString(),
                TokenSecret = Session["AccessTokenSecret"].ToString()
            };

            IConsumerRequest conReq = oSession.Request();
            conReq = conReq.Get();
            conReq = conReq.ForUrl(Constants.IaEndPoints.DisconnectUrl);
            try
            {
                conReq = conReq.SignWithToken();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            //Used just see the what header contains
            string header = conReq.Context.GenerateOAuthParametersForHeader();

            //This method will clean up the OAuth Token
            txtServiceResponse = conReq.ReadBody();

            //Reset All the Session Variables
            Session.Remove("oauthToken");

            // Dont remove the access token since this is required for Reconnect btn in the Blue dot menu
            // Session.Remove("accessToken");

            // Add the invalid access token into session for the display of the Disconnect btn
            Session["InvalidAccessToken"] = Session["AccessToken"];

            // Dont Remove flag since we need to display the blue dot menu for Reconnect btn in the Blue dot menu
            // Session.Remove("Flag");

            ViewBag.DisconnectFlg = "User is Disconnected from QuickBooks!";

            //Remove the Oauth access token from the OauthAccessTokenStorage.xml
            OauthAccessTokenStorageHelper.RemoveInvalidOauthAccessToken(Session["FriendlyEmail"].ToString());
        }

        return RedirectToAction("Index", "QuickBooks");
    }

    public ActionResult CleanUpOnDisconnect()
    {
        //perform the clean up here 

        // Redirect to Home when user clicks on ConenctToIntuit widget.
        object value = Session["RedirectToDefault"];
        if (value != null)
        {
            bool isTrue = (bool)value;
            if (isTrue)
            {
                Session.Remove("InvalidAccessToken");
                Session.Remove("RedirectToDefault");
                return Redirect("/QuickBooks/index");
            }
        }

        return RedirectToAction("Index", "QuickBooks");
    }

    private String consumerSecret, consumerKey, oauthLink, RequestToken, TokenSecret, oauth_callback_url;

    public RedirectResult OAuthGrant()
    {
        oauth_callback_url = Request.Url.GetLeftPart(UriPartial.Authority) + ConfigurationManager.AppSettings["oauth_callback_url"];
        consumerKey = ConfigurationManager.AppSettings["consumerKey"];
        consumerSecret = ConfigurationManager.AppSettings["consumerSecret"];
        oauthLink = Constants.OauthEndPoints.IdFedOAuthBaseUrl;
        IToken token = (IToken)Session["requestToken"];
        IOAuthSession session = CreateSession();
        IToken requestToken = session.GetRequestToken();
        Session["requestToken"] = requestToken;
        RequestToken = requestToken.Token;
        TokenSecret = requestToken.TokenSecret;

        oauthLink = Constants.OauthEndPoints.AuthorizeUrl + "?oauth_token=" + RequestToken + "&oauth_callback=" + UriUtility.UrlEncode(oauth_callback_url);
        return Redirect(oauthLink);
    }

    /// <summary>
    /// Gets the Access Token
    /// </summary>
    /// <returns>Returns OAuth Session</returns>
    protected IOAuthSession CreateSession()
    {
        OAuthConsumerContext consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = consumerKey,
            ConsumerSecret = consumerSecret,
            SignatureMethod = SignatureMethod.HmacSha1
        };

        return new OAuthSession(consumerContext,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlRequestToken,
                                        oauthLink,
                                        Constants.OauthEndPoints.IdFedOAuthBaseUrl + Constants.OauthEndPoints.UrlAccessToken);
    }
}

..这里是 QuickBooks/Index

的 cshtml 页面
@using System.Configuration
@using PubManager.Domain.WebSession
@{
    string MenuProxy = Request.Url.GetLeftPart(UriPartial.Authority) + "/" + System.Configuration.ConfigurationManager.AppSettings["menuProxy"];
    string OauthGrant = Request.Url.GetLeftPart(UriPartial.Authority) + "/" + System.Configuration.ConfigurationManager.AppSettings["grantUrl"];

    ViewBag.Title = "MagManager - Intuit Anywhere";
    string FriendlyName = (string)HttpContext.Current.Session["FriendlyName"] + "";
    string FriendlyEmail = (string)HttpContext.Current.Session["FriendlyEmail"];
    string FriendlyIdentifier = (string)HttpContext.Current.Session["FriendlyIdentifier"];

    string realm = (string)Session["Realm"];
    string dataSource = (string)Session["DataSource"];
    string accessToken = (string)Session["AccessToken"] + "";
    string accessTokenSecret = (string)Session["AccessTokenSecret"];
    string invalidAccessToken = (string)Session["InvalidAccessToken"];
}

<h1>Intuit - QuickBooks Online</h1>

<div class="well">
    @if (FriendlyName.Length == 0)
    {
        <script>
            window.location.href = "/QuickBooks/OpenIdIndex";
        </script>
    }
    else
    {
        <div id="IntuitInfo">
            <strong>Open Id Information:</strong><br />
            Welcome: @FriendlyName<br />
            E-mail Address: @FriendlyEmail<br />
            <br />
            @if (accessToken.Length > 0 && invalidAccessToken == null)
            {
                <div id="oAuthinfo">
                    <a onclick="reconcileInvoices()" id="RecInvoices" class="btn btn-primary">Set Invoices Paid</a><br />
                    <br/>
                    <a onclick="recTax()" id="RecTax" class="btn btn-primary">Sync Tax Rates</a><br />
                    <br/>
                    @if (SessionContext.UseAccountCodes)
                    {
                        <a onclick="recProducts()" id="RecProducts" class="btn btn-primary">Sync Products</a><br />
                        <br />
                    }
                    <a href="/QuickBooks/ReconcileCustomers" id="Customers" class="btn btn-primary">Reconcile Customers</a><br/>
                    <br/>
                    <a href="/QuickBooks/Disconnect" id="Disconnect" class="btn btn-primary">
                        Disconnect from
                        QuickBooks
                    </a>
                    <br/><br/>
                    @*<a href="/QuickBooks/Customers" id="QBCustomers" class="btn btn-primary">Get QuickBooks Customer List</a><br/>
        <br/>*@

                    @*<br />
        <a href="/QuickBooks/Invoices" id="QBInvoices" class="btn btn-primary">Get QuickBooks Invoice List</a><br />
        <br />*@
                    <br />
                </div>
            }
            else
            {
                <br />
                    <ipp:connecttointuit></ipp:connecttointuit>
            }
        </div>
    }
</div>
<script type="text/javascript" src="https://js.appcenter.intuit.com/Content/IA/intuit.ipp.anywhere-1.3.5.js"></script>
<script type="text/javascript">
    intuit.ipp.anywhere.setup({
        menuProxy: '@MenuProxy',
        grantUrl: '@OauthGrant'
    });
</script>