"""
QBO Excel Sync - OAuth Callback Server
This is a simple Flask server that handles OAuth callbacks from QuickBooks.
Host this on your own server (e.g., https://yourcompany.com/oauth/callback)
Usage:
1. Deploy this to your server
2. Set up SSL (required for QuickBooks production)
3. Register the callback URL in your QuickBooks app settings
4. Update the desktop app's redirect_uri to match
Environment Variables:
- PORT: Server port (default: 5000)
- SECRET_KEY: Flask secret key for sessions
"""
import os
import logging
from datetime import datetime
from flask import Flask, request, render_template_string, jsonify
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production')
# HTML template for displaying the authorization code
CALLBACK_TEMPLATE = """
QBO Excel Sync - Authorization
{% if error %}
Authorization Failed
Error: {{ error }}
{% if error_description %}
Details: {{ error_description }}
{% endif %}
{% else %}
Authorization Successful!
Copy the information below and paste it into the QBO Excel Sync app.
Company ID (Realm ID): {{ realm_id }}
Authorization Code
{{ code }}
Copy Authorization Code
Next Steps:
Go back to the QBO Excel Sync desktop application
Click "Enter Code Manually" button
Paste the Authorization Code and Company ID
Click Connect
{% endif %}
"""
@app.route('/')
def index():
"""Health check endpoint."""
return jsonify({
'status': 'ok',
'service': 'QBO Excel Sync OAuth Callback Server',
'timestamp': datetime.now().isoformat()
})
@app.route('/oauth/callback')
def oauth_callback():
"""
Handle OAuth callback from QuickBooks.
QuickBooks redirects here with:
- code: Authorization code (on success)
- realmId: Company ID
- state: State parameter we sent
- error: Error code (on failure)
- error_description: Error details (on failure)
"""
# Get parameters
code = request.args.get('code')
realm_id = request.args.get('realmId')
state = request.args.get('state')
error = request.args.get('error')
error_description = request.args.get('error_description')
# Log the callback
logger.info(f"OAuth callback received - realm_id: {realm_id}, state: {state}, error: {error}")
if error:
logger.error(f"OAuth error: {error} - {error_description}")
return render_template_string(
CALLBACK_TEMPLATE,
error=error,
error_description=error_description,
year=datetime.now().year
)
if not code:
logger.error("No authorization code received")
return render_template_string(
CALLBACK_TEMPLATE,
error="No authorization code received",
error_description="The authorization server did not return a code.",
year=datetime.now().year
)
# Success - display the code to the user
logger.info(f"Authorization successful for realm {realm_id}")
return render_template_string(
CALLBACK_TEMPLATE,
code=code,
realm_id=realm_id,
state=state,
error=None,
year=datetime.now().year
)
@app.route('/oauth/callback/json')
def oauth_callback_json():
"""
JSON endpoint for programmatic access.
Can be used if you want to implement automatic code relay.
"""
code = request.args.get('code')
realm_id = request.args.get('realmId')
state = request.args.get('state')
error = request.args.get('error')
error_description = request.args.get('error_description')
if error:
return jsonify({
'success': False,
'error': error,
'error_description': error_description
}), 400
return jsonify({
'success': True,
'code': code,
'realm_id': realm_id,
'state': state
})
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
debug = os.environ.get('DEBUG', 'false').lower() == 'true'
print(f"""
╔═══════════════════════════════════════════════════════════════╗
║ QBO Excel Sync - OAuth Callback Server ║
╠═══════════════════════════════════════════════════════════════╣
║ Server running on port {port} ║
║ Callback URL: http://localhost:{port}/oauth/callback ║
║ ║
║ For production, deploy with HTTPS (required by QuickBooks) ║
║ Example: https://yourcompany.com/oauth/callback ║
╚═══════════════════════════════════════════════════════════════╝
""")
app.run(host='0.0.0.0', port=port, debug=debug)