diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6ccb3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.env +.flaskenv +*.pyc +*.pyo +env/ +env* +dist/ +.cache/ +.pytest_cache/ diff --git a/README.md b/README.md index 2f8c451..9ab7409 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -[![Sample Banner](views/Sample.png)][ss1] +[![Rate your Sample](views/Ratesample.png)][ss1][![Yes](views/Thumbup.png)][ss2][![No](views/Thumbdown.png)][ss3] # Data import from Excel to QBO #### Sample App in Python that implements Connect to Quickbooks button and imports customer data from Excel to QBO company. This sample app is meant to provide working example of how to make API calls to Quickbooks. Specifically, this sample application demonstrates the following: -- Implementing OAuth to connect an application to a customer's QuickBooks Online company for both OAuth1 and OAuth2. +- OAuth2 sample app for a QuickBooks Online company. - Creating a QB customer that are added from Excel file using Customer API. - Gets company data using CompanyInfo API @@ -16,13 +16,11 @@ Please note that while these examples work, features not called out above are no 2. A [developer.intuit.com](https://developer.intuit.com/) account 3. An app on [developer.intuit.com](https://developer.intuit.com/) and the associated app keys: - Client Id and Client Secret for OAuth2 apps; Configure the RedirectUri[http://localhost:5000/callback] in your app's Keys tab on the Intuit developer account, only Accounting scope needed - - Consumer key and Consumer secret for OAuth1 apps -4. This sample app uses several libraries listed in [requirements.txt](requirements.txt) which need to be installed including flask, flask_oauth, openpyxl, requests_oauthlib +4. This sample app uses several libraries listed in [requirements.txt](requirements.txt) which need to be installed including flask, openpyxl, requests_oauthlib ## First Time Instructions 1. Clone the GitHub repo to your computer -2. Install libraries mentioned above in Requirements 4. -3. Fill in your [config.py](config.py) file values by copying over from the keys section for your app +2. Fill in your [config.py](config.py) file values by copying over from the keys section for your app ## Running the code 1. cd to the project directory @@ -43,4 +41,6 @@ Please note that while these examples work, features not called out above are no #### Note: For other OAuth2 services like Refresh token, Revoke token, etc, refer to [this](https://github.com/IntuitDeveloper/OAuth2PythonSampleApp) app -[ss1]: https://help.developer.intuit.com/s/samplefeedback?cid=9010&repoName=SampleApp-QuickBooksV3API-Python +[ss1]: # +[ss2]: https://customersurveys.intuit.com/jfe/form/SV_9LWgJBcyy3NAwHc?check=Yes&checkpoint=SampleApp-QuickBooksV3API-Python&pageUrl=github +[ss3]: https://customersurveys.intuit.com/jfe/form/SV_9LWgJBcyy3NAwHc?check=No&checkpoint=SampleApp-QuickBooksV3API-Python&pageUrl=github diff --git a/app.py b/app.py index d77a8b7..aab00bf 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ from flask import Flask, request, redirect, url_for, session, g, flash, render_template -from flask_oauth import OAuth +# from flask_oauth import OAuth import requests import urllib from werkzeug.exceptions import BadRequest @@ -16,18 +16,6 @@ app.debug = DEBUG app.secret_key = SECRET_KEY -if config.AUTH_TYPE == 'OAuth1': - oauth = OAuth() - - qbo = oauth.remote_app('qbo', - base_url=config.OAUTH1_BASE, - request_token_url=config.REQUEST_TOKEN_URL, - access_token_url=config.ACCESS_TOKEN_URL, - authorize_url=config.AUTHORIZE_URL, - consumer_key=config.CONSUMER_KEY, - consumer_secret=config.CONSUMER_SECRET - ) - @app.route('/') def index(): """Index route""" @@ -43,11 +31,8 @@ def index(): def update_table(): """Update Excel file after customer is added in QBO""" customer_id = request.form['id'] - - if config.AUTH_TYPE == 'OAuth1': - request_context = context.RequestContextOAuth1(session['realm_id'], session['access_token'], session['access_secret']) - else: - request_context = context.RequestContext(session['realm_id'], session['access_token'], session['refresh_token']) + + request_context = context.RequestContext(session['realm_id'], session['access_token'], session['refresh_token']) for customer in customer_list: if customer['Id'] == customer_id: @@ -73,10 +58,7 @@ def update_table(): @app.route('/company-info') def company_info(): """Gets CompanyInfo of the connected QBO account""" - if config.AUTH_TYPE == 'OAuth1': - request_context = context.RequestContextOAuth1(session['realm_id'], session['access_token'], session['access_secret']) - else: - request_context = context.RequestContext(session['realm_id'], session['access_token'], session['refresh_token']) + request_context = context.RequestContext(session['realm_id'], session['access_token'], session['refresh_token']) response = get_companyInfo(request_context) if (response.status_code == 200): @@ -94,23 +76,18 @@ def company_info(): title='QB Customer Leads', ) - @app.route('/auth') def auth(): """Initiates the Authorization flow after getting the right config value""" - if config.AUTH_TYPE == "OAuth1": - return qbo.authorize(callback=url_for('oauth_authorized')) - else: - # OAuth2 initiate authorization flow - params = { - 'scope': 'com.intuit.quickbooks.accounting', - 'redirect_uri': config.REDIRECT_URI, - 'response_type': 'code', - 'client_id': config.CLIENT_ID, - 'state': csrf_token() - } - url = OAuth2Helper.get_discovery_doc()['authorization_endpoint'] + '?' + urllib.parse.urlencode(params) - return redirect(url) + params = { + 'scope': 'com.intuit.quickbooks.accounting', + 'redirect_uri': config.REDIRECT_URI, + 'response_type': 'code', + 'client_id': config.CLIENT_ID, + 'state': csrf_token() + } + url = OAuth2Helper.get_discovery_doc()['authorization_endpoint'] + '?' + urllib.parse.urlencode(params) + return redirect(url) @app.route('/reset-session') def reset_session(): @@ -147,30 +124,6 @@ def callback(): return redirect(url_for('index')) -if config.AUTH_TYPE == 'OAuth1': - @app.route('/oauth-authorized') - @qbo.authorized_handler - def oauth_authorized(resp): - """Handles callback for OAuth1 only""" - realm_id = str(request.args.get('realmId')) - next_url = url_for('index') - if resp is None: - flash(u'You denied the request to sign in.') - return redirect(next_url) - - session['is_authorized'] = True - session['access_token'] = resp['oauth_token'] - session['realm_id'] = realm_id - session['access_secret'] = resp['oauth_token_secret'] - - return redirect(url_for('index')) - -if config.AUTH_TYPE == 'OAuth1': - @qbo.tokengetter - def get_qbo_token(token=None): - """Get OAuth1 QBO token""" - return session.get('qbo_token') - def csrf_token(): token = session.get('csrfToken', None) if token is None: diff --git a/config.py b/config.py index 7a81d57..698f365 100644 --- a/config.py +++ b/config.py @@ -1,26 +1,11 @@ DEBUG = False SQLALCHEMY_ECHO = False -# Specify which OAuth your app uses; default is OAuth2 -# Change this flag to 1 for OAUth1 apps -AUTH_TYPE = 'OAuth2' -# AUTH_TYPE = 'OAuth1' - -# OAuth2 +# OAuth2 credentials CLIENT_ID= 'EnterClientIDHere' CLIENT_SECRET = 'EnterClientSecretHere' REDIRECT_URI = 'http://localhost:5000/callback' -# OAuth1 -CONSUMER_KEY = 'EnterConsumerKeyHere' -CONSUMER_SECRET = 'EnterConsumerSecretHere' - -# OAuth1 Base URLs -OAUTH1_BASE = 'https://oauth.intuit.com' -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' - # Choose environment; default is sandbox ENVIRONMENT = 'Sandbox' # ENVIRONMENT = 'Production' diff --git a/requirements.txt b/requirements.txt index 73855e6..3073da3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ requests==2.13.0 Flask==0.12 -Flask_OAuth==0.13 +# Flask_OAuth==0.12 Werkzeug==0.11.15 openpyxl==2.4.4 requests_oauthlib==0.8.0 diff --git a/views/Ratesample.png b/views/Ratesample.png new file mode 100644 index 0000000..17fb7fd Binary files /dev/null and b/views/Ratesample.png differ diff --git a/views/Sample.png b/views/Sample.png deleted file mode 100644 index 46a5c53..0000000 Binary files a/views/Sample.png and /dev/null differ diff --git a/views/Thumbdown.png b/views/Thumbdown.png new file mode 100644 index 0000000..726971a Binary files /dev/null and b/views/Thumbdown.png differ diff --git a/views/Thumbup.png b/views/Thumbup.png new file mode 100644 index 0000000..b42beb6 Binary files /dev/null and b/views/Thumbup.png differ