使用Quickbooks Online API v3的python

时间:2015-06-02 19:39:33

标签: python quickbooks-online

我需要一些帮助来实现访问Quickbooks API的python应用程序。我已经成功编写了几个使用API​​的应用程序,但是一旦我们进入OAuth世界,我就会迷失方向。

无论如何,我在这里找到了quickbooks-python包装器: https://github.com/troolee/quickbooks-python

但是没有工作代码示例如何正确实现。我想,一个更有经验的python程序员可以在没有任何指示的情况下弄清楚如何完成这项工作,但似乎我错过了基础知识。

如果我可以将它连接起来,我可以从那里开始工作......

似乎github上的文档跳了起来,对于一个更有经验的程序员来说,可能会非常有意义。但我只是不跟随...

from quickbooks import *

consumerKey =           "fromApiConsole"
consumerSecret =        "fromApiConsole"
callbackUrl =           "https://quickbooks.api.intuit.com/v3"

qbObject = QuickBooks(
        consumer_key = consumerKey,
        consumer_secret = consumerSecret,
        callback_url = callbackUrl
        )

authorize_url = qbObject.get_authorize_url() # will create a service, and further set up the qbObject.

oauth_token = request.GET['oauth_token']
oauth_verifier = request.GET['oauth_verifier']
realm_id = request.GET['realmId']

session = qbObject.get_access_tokens(oauth_verifier)

# say you want access to the reports

reportType = "ProfitAndLoss"

url = "https://quickbooks.api.intuit.com/v3/company/asdfasdfas/"
url += "reports/%s" % reportType

r = session.request( #This is just a Rauth request
    "POST",
    url,
    header_auth = True,
    realm = realm_id,
    params={"format":"json"}
    )

qb = QuickBooks(
    consumer_key = consumerKey,
    consumer_secret = consumerSecret,
    access_token = qbtoken.access_token, # the stored token
    access_token_secret = qbtoken.access_token_secret, # the stored secret
    company_id = qbtoken.realm_id #the stored realm_id
    )

qbText = str(qb.query_objects(business_object, params, query_tail))

print qbText

我很确定我是:

  1. 导入错误的模块/类
  2. 错过了巨大的代码片段和#34;粘合在一起"在github上找到的样本
  3. 这里没有使用django,我知道上面的请求类是在django中,但我真的很想在不使用django的情况下将其作为python脚本工作
  4. 未从初始authorize_url函数获取令牌/标识符/ realmId。它打印在屏幕上,但我不确定如何抓住它......
  5. 这里的最终目标实际上只是连接并从Quickbooks Online获得P& L声明。如果我可以做到这一点,我相信我可以从API获得剩余的所需内容。我并不需要更改任何内容,我只是希望将报告中的数据包含在一些仪表板中。

    *更新*

    好吧,我想出了如何连接它,但我不确定如何获取报告。

    答案就是这个,它位于以前的API页面上:

    Accessing the API
    Once you've gotten a hold of your QuickBooks access tokens, you can create a QB object:
    
    qb = QuickBooks(consumer_key = QB_OAUTH_CONSUMER_KEY, 
            consumer_secret = QB_OAUTH_CONSUMER_SECRET,
            access_token = QB_ACCESS_TOKEN, 
            access_token_secret = QB_ACCESS_TOKEN_SECRET,
            company_id = QB_REALM_ID
            )
    

    仍在尝试获取基本报告......

2 个答案:

答案 0 :(得分:3)

好的,这就是如何使这项工作。我专注于报告,所以这里是如何使用Python从Quickbooks Online API获取报告的:

1)转到https://github.com/finoptimal-dev/quickbooks-python并下载代码

2)确保你安装了rauth。如果您使用的是AWS / EC2,只需:

sudo yum install rauth

3)编辑quickbooks2.py文件并将以下内容添加到END:

qb = QuickBooks(consumer_key = QB_OAUTH_CONSUMER_KEY, 
        consumer_secret = QB_OAUTH_CONSUMER_SECRET,
        access_token = QB_ACCESS_TOKEN, 
        access_token_secret = QB_ACCESS_TOKEN_SECRET,
        company_id = QB_REALM_ID
        )

4)在Quickbooks网站上设置沙盒应用程序:https://developer.intuit.com/v2/ui#/app/startcreate(如果您还没有开发者帐户,则必须创建一个开发者帐户)

5)设置完成后,您可以转到应用程序的“密钥”选项卡,并获取应用程序令牌,OAuth使用者密钥和OAuth使用者密钥。

6)转到https://appcenter.intuit.com/Playground/OAuth/IA的Intuit开发者游乐场,并使用步骤#5中的信息获取访问令牌和访问令牌秘密。

7)将步骤#3中的变量更改为正确的值。对于QB_REALM_ID,这是公司ID。您可以登录https://developer.intuit.com/v2/ui#/sandbox并查找公司ID,在沙箱中获取此信息。

7)在上面步骤#3的代码下面添加以下代码

print qb.get_report('ProfitAndLoss','summarize_column_by=Month&start_date=2014-01-01&end_date=2014-12-31')

我使用上述日期b / c Quickbooks Sandbox公司在2015年没有收入/费用数据,因此您必须在2014年选择日期。

8)重要提示:要与Quickbooks Sandbox一起使用以进行报告,您需要更改get_report()函数以使用base_url_v3而不是硬编码到生产URL。

在get_report()函数中查找如下所示的行:

url = "https://quickbooks.api.intuit.com/v3/company/%s/" % \

并将其更改为:

url = self.base_url_v3 + "/company/%s/" % \

9)现在你可以在顶部将base_url_v3更改为:

base_url_v3 =  "https://sandbox-quickbooks.api.intuit.com/v3"

10)现在你应该能够运行:

python quickbooks2.py

您应该会看到Quickbooks Sandbox公司的一堆JSON数据。

11)您可以在此处探索一下以测试相应的网址:https://developer.intuit.com/apiexplorer?apiname=V3QBO#Reports

12)报告参考在此处:https://developer.intuit.com/docs/0100_accounting/0400_references/reports,它显示了您可以使用的参数。要在资源管理器中测试参数,请在“请求正文”部分中输入它们。

我挣扎了一段时间,终于想通了。希望这有助于其他人。

答案 1 :(得分:0)

我没有太多使用Python的经验,但有人早先与我分享了这段代码oauth。如果你对代码有其他疑问,我将无法回答它们。

注意:以下代码也会调用V2 QBO apis。请不要使用该部分,因为它已被弃用。

看看是否有帮助 -

导入Python

来自rauth导入OAuth1Session,OAuth1Service 将xml.etree.ElementTree导入为ET

导入xmltodict

类QuickBooks():     “”围绕Python的Rauth模块的封装类,用于Quickbooks的API“”“

access_token = ''
access_token_secret = ''
consumer_key = ''
consumer_secret = ''
company_id = 0
callback_url = ''
session = None

base_url_v3 =  "https://quickbooks.api.intuit.com/v3"
base_url_v2 = "https://qbo.intuit.com/qbo1"

request_token_url = "https://oauth.intuit.com/oauth/v1/get_request_token"
access_token_url = "https://oauth.intuit.com/oauth/v1/get_access_token"

authorize_url = "https://appcenter.intuit.com/Connect/Begin"

# Things needed for authentication
qbService = None

request_token = ''
request_token_secret = ''


def __init__(self, **args):

    if 'consumer_key' in args:
        self.consumer_key = args['consumer_key']

    if 'consumer_secret' in args:
        self.consumer_secret = args['consumer_secret']

    if 'access_token' in args:
        self.access_token = args['access_token']

    if 'access_token_secret' in args:
        self.access_token_secret = args['access_token_secret']

    if 'company_id' in args:
        self.company_id = args['company_id']

    if 'callback_url' in args:
        self.callback_url = args['callback_url']


def get_authorize_url(self):
    """Returns the Authorize URL as returned by QB, 
    and specified by OAuth 1.0a.
    :return URI:
    """
    self.qbService = OAuth1Service(
            name = None,
            consumer_key = self.consumer_key,
            consumer_secret = self.consumer_secret,
            request_token_url = self.request_token_url,
            access_token_url = self.access_token_url,
            authorize_url = self.authorize_url,
            base_url = None
        )
    self.request_token, self.request_token_secret = self.qbService.get_request_token(
            params={'oauth_callback':self.callback_url}
        )

    return self.qbService.get_authorize_url(self.request_token)




def get_access_tokens(self, oauth_verifier):
    """Wrapper around get_auth_session, returns session, and sets 
    access_token and access_token_secret on the QB Object.
    :param oauth_verifier: the oauth_verifier as specified by OAuth 1.0a
    """
    session = self.qbService.get_auth_session(
            self.request_token, 
            self.request_token_secret,
            data={'oauth_verifier': oauth_verifier})

    self.access_token = session.access_token
    self.access_token_secret = session.access_token_secret

    return session



def create_session(self):
    if self.consumer_secret and self.consumer_key and self.access_token_secret and self.access_token:
        # print "hi"
        session = OAuth1Session(self.consumer_key,
            self.consumer_secret,
            self.access_token,
            self.access_token_secret,
            )
        # print session
        self.session = session
    else:
        pass
        #TODO: raise an error
    return self.session


def keep_trying(self, r_type, url, header_auth, realm, payload=''):

    if self.session != None:
        session = self.session
    else:
        session = self.create_session()
        self.session = session

    trying = True
    tries = 0
    while trying:
        print url
        tries += 1
        if "v2" in url:
            r = session.request(r_type, url, header_auth, realm, data=payload)

            r_dict = xmltodict.parse(r.text)
            # print "DICT", r_dict
            if "FaultInfo" not in r_dict or tries > 4:
                trying = False
        else:
            # url = "https://qb.sbfinance.intuit.com/v3/company/184010684/query?query=SELECT * FROM JournalEntry"
            # url = "https://quickbooks.api.intuit.com/v3/company/184010684/journalentry/24772"
            # url = "https://quickbooks.api.intuit.com/v3/company/184010684/query?query='SELECT+*+FROM+JournalEntry'"
            # url = "https://qb.sbfinance.intuit.com/v3/company/184010684/query?query=SELECT%20%2A%20FROM%20JournalEntry&"
            print url, r_type
            headers = {'Accept': 'application/json'}
            r = session.request(r_type, url, header_auth, realm, headers = headers)
            # r.headers
            print "\n\n INITIAL TEXT \n\n", r.text


            print "request headers:", r.request.headers
            print "request URL:", r.request.url
            print "response headers:", r.headers

            r_dict = r.text
            if "Fault" not in r_dict or tries > 4:
                trying = False
            r_dict = []

    return r_dict

def fetch_customer(self, pk):

    if pk:
        url = self.base_url_v2 + "/resource/customer/v2/%s/%s" % ( self.company_id, pk)
        r_dict = self.keep_trying("GET", url, True, self.company_id)

        return r_dict['Customer']


def fetch_customers(self, all=False, page_num=0, limit=10):
    if self.session != None:
        session = self.session
    else:
        session = self.create_session()
        self.session = session

    # We use v2 of the API, because what the fuck, v3.
    url = self.base_url_v2
    url += "/resource/customers/v2/%s" % (self.company_id)

    customers = []

    if all:
        counter = 1
        more = True

        while more:
            payload = {
                "ResultsPerPage":30,
                "PageNum":counter,
                }

            trying = True

            # Because the QB API is so iffy, let's try until we get an non-error

            # Rewrite this to use same code as above.
            while trying:
                r = session.request("POST", url, header_auth = True, data = payload, realm = self.company_id)
                root = ET.fromstring(r.text)
                if root[1].tag != "{http://www.intuit.com/sb/cdm/baseexceptionmodel/xsd}ErrorCode":
                    trying = False
                else:
                    print "Failed"

            session.close()
            qb_name = "{http://www.intuit.com/sb/cdm/v2}"

            for child in root:
                # print child.tag, child.text
                if child.tag == "{http://www.intuit.com/sb/cdm/qbo}Count":

                    if int(child.text) < 30:
                        more = False
                        print "Found all customers"

                if child.tag == "{http://www.intuit.com/sb/cdm/qbo}CdmCollections":
                    for customer in child:

                        customers += [xmltodict.parse(ET.tostring(customer))]

            counter += 1

            # more = False
            # print more

    else:

        payload = {
            "ResultsPerPage":str(limit),
            "PageNum":str(page_num),
            }

        r = session.request("POST", url, header_auth = True, data = payload, realm = self.company_id)

        root = ET.fromstring(r.text)

        #TODO: parse for all customers


    return customers

def fetch_sales_term(self, pk):
    if pk:
        url = self.base_url_v2 + "/resource/sales-term/v2/%s/%s" % ( self.company_id, pk)
        r_dict = self.keep_trying("GET", url, True, self.company_id)
        return r_dict


def fetch_invoices(self, **args):
    if "query" in args:
        payload = ""
        if "customer" in args['query']:
            payload = {
                "Filter":"CustomerId :Equals: %s" % (args['query']['customer'])
            }


        # while more:
        url = self.base_url_v2 + "/resource/invoices/v2/%s/" % (self.company_id)
        r_dict = self.keep_trying("POST", url, True, self.company_id, payload)
        invoices = r_dict['qbo:SearchResults']['qbo:CdmCollections']['Invoice']

        return invoices
    elif "pk" in args:
        # TODO: Not tested
        url = self.base_url_v2 + "/resource/invoice/v2/%s/%s" % ( self.company_id, args['pk'])
        r_dict = self.keep_trying("GET", url, True, self.company_id)
        return r_dict
    else:
        url = self.base_url_v2 + "/resource/invoices/v2/%s/" % (self.company_id)
        r_dict = self.keep_trying("POST", url, True, self.company_id, payload)
        return "BLAH"


def fetch_journal_entries(self, **args):
    """ Because of the beautiful way that journal entries are organized
    with QB, you're still going to have to filter these results for the
    actual entity you're interested in. Luckily it only returns the entries
    that are relevant to your search

    :param query: a dictionary that includes 'customer', and the QB id of the
        customer
    """

    if "query" in args:
        payload = {}
        more = True
        counter = 1
        journal_entries = []

        if "customer" in args['query']:

            payload = {
                "Filter":"CustomerId :Equals: %s" % (args['query']['customer'])
            }

            # payload = {
            #     "query":"SELECT * FROM JournalEntry",
            # }

        while more:

            payload["ResultsPerPage"] = 30

            payload["PageNum"] = counter

            # url = self.base_url_v2 + "/resource/journal-entries/v2/%s/" % (self.company_id)
            # url = self.base_url_v3 + "/company/%s/query" % (self.company_id)
            url = "https://qb.sbfinance.intuit.com/v3/company/184010684/query?query=SELECT%20%2A%20FROM%20JournalEntry&"

            r_dict = self.keep_trying("GET", url, True, self.company_id, payload)

            more = False
            # print r_dict['qbo:SearchResults']['qbo:Count']
            counter = counter + 1
            # if int(r_dict['qbo:SearchResults']['qbo:Count']) < 30:
                # more = False

            # journal_entry_set = r_dict['qbo:SearchResults']['qbo:CdmCollections']['JournalEntry']
            # journal_entries += [journal_entry_set]
        return []
        # return r_dict['qbo:SearchResults']['qbo:CdmCollections']['JournalEntry']

    elif "pk" in args:
        # TODO: Not Tested
        url = self.base_url_v2 + "/resource/journal-entry/v2/%s/%s" % ( self.company_id, args['pk'])
        r_dict = self.keep_trying("GET", url, True, self.company_id)
        return r_dict
    else:
        url = self.base_url_v2 + "/resource/journal-entries/v2/%s/" % (self.company_id)
        r_dict = self.keep_trying("POST", url, True, self.company_id)
        print r_dict
        return "BLAH"