55 lines
2.1 KiB
Python
55 lines
2.1 KiB
Python
|
|
"""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)
|