"""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)