Files
handoff-pro/lib/qbo.py

55 lines
2.1 KiB
Python
Raw Normal View History

"""QuickBooks Online API helper. Tokens from http://192.168.86.11:18801."""
import json
import time
import urllib.request
TOKEN_URL = "http://192.168.86.11:18801/kellow-tokens.json"
REFRESH_URL = "http://192.168.86.11:18803/qbo/refresh?tenant=kellow"
QBO_BASE = "https://quickbooks.api.intuit.com/v3/company"
def get_token():
"""Fetch valid QBO access token + realm_id, auto-refresh if expired."""
with urllib.request.urlopen(TOKEN_URL, timeout=10) as resp:
tokens = json.loads(resp.read())
if time.time() >= tokens.get("expires_at_epoch", 0):
req = urllib.request.Request(REFRESH_URL, method="POST")
urllib.request.urlopen(req, timeout=15)
with urllib.request.urlopen(TOKEN_URL, timeout=10) as resp:
tokens = json.loads(resp.read())
return tokens["access_token"], tokens["realm_id"]
def qbo_request(method, endpoint, body=None):
"""Make authenticated QBO API request. Returns parsed JSON."""
access_token, realm_id = get_token()
url = f"{QBO_BASE}/{realm_id}/{endpoint}"
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(url, data=data, method=method, headers={
"Authorization": f"Bearer {access_token}",
"Accept": "application/json",
"Content-Type": "application/json",
})
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
def create_invoice(customer_id, line_items, due_date=None):
"""Create invoice in QBO. line_items: [{description, amount, qty}]"""
lines = []
for i, item in enumerate(line_items, 1):
lines.append({
"LineNum": i,
"Amount": item["amount"],
"DetailType": "SalesItemLineDetail",
"Description": item.get("description", ""),
"SalesItemLineDetail": {
"Qty": item.get("qty", 1),
"UnitPrice": item["amount"] / item.get("qty", 1),
}
})
invoice = {
"CustomerRef": {"value": customer_id},
"Line": lines,
}
if due_date:
invoice["DueDate"] = due_date
return qbo_request("POST", "invoice", invoice)