import requests
import json
from parsons import Table
[docs]class BillCom(object):
"""
`Args:`
user_name: str
The Bill.com username
password: str
The Bill.com password
org_id: str
The Bill.com organization id
dev_key: str
The Bill.com dev key
api_url:
The Bill.com end point url
"""
def __init__(self, user_name, password, org_id, dev_key, api_url):
self.headers = {"Content-Type": "application/x-www-form-urlencoded"}
params = {
"userName": user_name,
"password": password,
"orgId": org_id,
"devKey": dev_key,
}
response = requests.post(url="%sLogin.json" % api_url, data=params, headers=self.headers)
self.dev_key = dev_key
self.api_url = api_url
self.session_id = response.json()["response_data"]["sessionId"]
def _get_payload(self, data):
"""
`Args:`
data: dict
A dictionary containing the payload to be sent in the request.
The dev_key and sessionId should not be included as they are
dealt with separately.
`Returns:`
A dictionary of the payload to be sent in the request with the
dev_key and sessionId added.
"""
return {
"devKey": self.dev_key,
"sessionId": self.session_id,
"data": json.dumps(data),
}
def _post_request(self, data, action, object_name):
"""
`Args:`
data: dict
A dictionary containing the payload to be sent in the request.
The dev_key and sessionId should not be included as they are
dealt with separately.
action: str
The action to be taken on the given object.
Possible values are "List", "Read", "Create", and "Send".
object_name: str
The id of the object. Possible values are "User", "Customer", and "Invoice".
`Returns:`
A dictionary containing the JSON response from the post request.
"""
if action == "Read":
url = "%sCrud/%s/%s.json" % (self.api_url, action, object_name)
elif action == "Create":
data["obj"]["entity"] = object_name
url = "%sCrud/%s/%s.json" % (self.api_url, action, object_name)
elif action == "Send":
url = "%s%s%s.json" % (self.api_url, action, object_name)
else:
url = "%s%s/%s.json" % (self.api_url, action, object_name)
payload = self._get_payload(data)
response = requests.post(url=url, data=payload, headers=self.headers)
return response.json()
def _get_request_response(self, data, action, object_name, field="response_data"):
"""
`Args:`
data: dict
A dictionary containing the payload to be sent in the request.
The dev_key and sessionId should not be included as they are
dealt with separately.
action: str
The action to be taken on the given object.
Possible values are "List", "Read", "Create", and "Send".
object_name: str
The id of the object. Possible values are "User", "Customer", and "Invoice".
field: str
The JSON field where the response data is stored. Defaults to "response_data".
`Returns:`
A dictionary containing the choosen field from the JSON response from the post request.
"""
r = self._post_request(data, action, object_name)[field]
if action == "List":
return self._paginate_list(r, data, object_name)
return r
def _paginate_list(self, response, data, object_name, field="response_data"):
"""
Internal method to paginate through and concatenate results of lists larger than max
`Args:`
response: list of dicts
Data from an initial list call
data: dict
Start, max, and kwargs from initial list call
object_name: str
Name of the object being listed
"""
r_table = Table(response)
max_ct = data["max"]
while len(response) == max_ct:
data["start"] += max_ct
response = self._post_request(data, "List", object_name)[field]
r_table.concat(Table(response))
return r_table
[docs] def get_user_list(self, start_user=0, max_user=999, **kwargs):
"""
`Args:`
start_user: int
The index of first user to return. Starts from 0 (not 1).
max_user: str
The index of the max user to return
**kwargs:
Any other fields to pass
`Returns:`
A Parsons Table of user information for every user from start_user to max_user.
"""
data = {"start": start_user, "max": max_user, **kwargs}
return self._get_request_response(data, "List", "User")
[docs] def get_customer_list(self, start_customer=0, max_customer=999, **kwargs):
"""
`Args:`
start_customer: int
The index of first customer to return. Starts from 1 (not 0).
max_customer: str
The index of the max customer to return
**kwargs:
Any other fields to pass
`Returns:`
A Parsons Table of customer information for every user from start_customer
to max_customer.
"""
data = {"start": start_customer, "max": max_customer, **kwargs}
return self._get_request_response(data, "List", "Customer")
[docs] def get_invoice_list(self, start_invoice=0, max_invoice=999, **kwargs):
"""
`Args:`
start_invoice: int
The index of first customer to return. Starts from 1 (not 0).
max_invoice: str
The index of the max customer to return
**kwargs:
Any other fields to pass
`Returns:`
A list of dictionaries of invoice information for every invoice from start_invoice
to max_invoice.
"""
data = {"start": start_invoice, "max": max_invoice, **kwargs}
return self._get_request_response(data, "List", "Invoice")
[docs] def read_customer(self, customer_id):
"""
`Args:`
customer_id: str
The id of the customer to query
`Returns:`
A dictionary of the customer's information.
"""
data = {"id": customer_id}
return self._get_request_response(data, "Read", "Customer")
[docs] def read_invoice(self, invoice_id):
"""
`Args:`
invoice_id: str
The id of the invoice to query
`Returns:`
A dictionary of the invoice information.
"""
data = {"id": invoice_id}
return self._get_request_response(data, "Read", "Invoice")
[docs] def check_customer(self, customer1, customer2):
"""
`Args:`
customer1: dict
A dictionary of data on customer1
customer2: dict
A dictionary of data on customer2
`Returns:`
True if either
1. customer1 and customer2 have the same id
OR
2. customer1 has no id and customer1 customer2 have the same email address
False otherwise
"""
if "id" in customer1.keys():
if customer1["id"] == customer2["id"]:
return True
if "id" not in customer1.keys() and customer2["email"]:
if customer1["email"].lower() == customer2["email"].lower():
return True
return False
[docs] def get_or_create_customer(self, customer_name, customer_email, **kwargs):
"""
`Args:`
customer_name: str
The name of the customer
customer_email: str
The customer's email
`Keyword Args:`
**kwargs:
Any other fields to store about the customer.
`Returns:`
A dictionary of the customer's information including an id.
If the customer already exists, this function will not
create a new id and instead use the existing id.
"""
customer = {"name": customer_name, "email": customer_email, **kwargs}
# check if customer already exists
customer_list = self.get_customer_list()
for existing_customer in customer_list:
if self.check_customer(customer, existing_customer):
return existing_customer
# customer doesn't exist, create
data = {"obj": customer}
return self._get_request_response(data, "Create", "Customer")
[docs] def create_invoice(
self, customer_id, invoice_number, invoice_date, due_date, invoice_line_items, **kwargs
):
"""
`Args:`
customer_id: str
The customer's id
invoice_number: str
The invoice number. Every invoice must have a distinct
invoice number.
invoice_date: str
The invoice date. This can be the date the invoice was
generated of any other relevant date.
due_date: str
The date on which the invoice is due.
invoice_line_items: list
A list of dicts, one for each line item in the invoice.
The only required field is "quantity".
**kwargs:
Any other invoice details to pass.
`Returns:`
A dictionary of the invoice's information including an id.
"""
for invoice_line_item in invoice_line_items:
if "entity" not in invoice_line_item:
invoice_line_item["entity"] = "InvoiceLineItem"
data = {
"obj": {
"customerId": customer_id,
"invoiceNumber": invoice_number,
"invoiceDate": invoice_date,
"dueDate": due_date,
"invoiceLineItems": invoice_line_items,
**kwargs,
}
}
return self._get_request_response(data, "Create", "Invoice")
[docs] def send_invoice(
self, invoice_id, from_user_id, to_email_addresses, message_subject, message_body, **kwargs
):
"""
`Args:`
invoice_id: str
The id of the invoice to send
from_user_id: str
The id of the Bill.com user from whom to send the email
to_email_addresses:
The customer's email address
message_subject:
The subject of the email to send to the customer
message_body:
The body of the email to send to the customer
**kwargs:
Any other details for sending the invoice
`Returns:`
A dictionary of the sent invoice.
"""
data = {
"invoiceId": invoice_id,
"headers": {
"fromUserId": from_user_id,
"toEmailAddresses": to_email_addresses,
"subject": message_subject,
**kwargs,
},
"content": {"body": message_body},
}
return self._get_request_response(data, "Send", "Invoice")