How I took my invoicing workflow from 2 hours a month to 3 seconds.
I was spending 2 long hours a month calculating time, calculating pricing, creating an invoice, sending it out. I hated it. I knew something had to change, and as a developer I knew I could automate this problem.
Couple things you will need to participate in this tutorial
1. Clockify Account
2. Stripe Account
3. Computer with Python installed
I won't go over how to get and install those things but there are many articles online on how to do so.
#1 - Clockify
I have my Clockify account configured with Jira, which is where we track our development tasks. So whenever work is done on a task we track our time directly in Jira to Clockify. This was the major time saver as before I was tracking on my phone and entering it manually to an excel spreadsheet (Yikes).
In order to create the Python function you will need a couple things from Clockify. 1. Workspace Id
2. API Key
Workspace Id
You can find the workspace Id by clicking into your workspace and going to the workspace settings. From there you should be able to see the workspace Id in the URL of the site.
API Key
The API key is on your profile settings at the bottom of the page.
#2 - Stripe
Let's discuss Stripe's beautiful API. I just want to say how great it is for developers. The documentation is superb and they make it super easy to switch to test mode to see what you are building before you go live.
You are going to need a couple things from Stripe to use in our Python code.
1. API Key
2. Customer Id
In the top right corner of the page you can easily toggle between test mode and production. Stripe gives you this nice banner while in test mode so you don't forget. Now from this main dashboard you can easily copy the secret key to use in your Python code but I'd recommend clicking on 'Developers' in the top right corner to bring you to this next page.
Briefly, before we talk about API Keys feel free to check out the Overview tab. This is going to give you some insight on API calls made and also give you error logs. Stripe makes it super easy to debug.
API Key
This API Keys page is what you will want to use. I recommend creating a restricted key with specific permissions so you don't have access to the entire API. On my test mode I simply used the standard key, but on production I created a restricted key so it limits use in case the key was ever leaked for some reason. You can always delete it if needed.
Customer Id
Now that you have your API key lets talk customers!
I prefer to just create my customers manually in the site and then click in to see the details. All you need from this page is the customerId which is on the left panel under details. It should start with cus_ and then a bunch of characters. You can create customers through the API, but I have a lot of repeat business so it is fine for me to create it on the site and get the Id this way.
Great, now that we have our workspaceId and API key from Clockify, and our CustomerId and API key from Stripe we are ready to start writing some Python code.
#3 - Python
Here is where we get to the fun part. Writing some code.
Essentially what this program does is download the time from a specified month from the Clockify API. Once that is downloaded, I format the time and calculate the invoice price with my hourly rate. Once the final price is calculated I create the invoice in Stripe, and finally open up my web browser to the invoices page to review and send out to my client.
Let's dig in to some of the code.
def createInvoice():
dateRangeStart, dateRangeEnd = getDateRanges()
timesheetJson = getTimeSheetForDateRange(dateRangeStart=dateRangeStart, dateRangeEnd=dateRangeEnd)
moneyCents = getMoneyInCents(timesheetJson=timesheetJson)
createStripeInvoice(moneyCents=moneyCents)
os.system("open \"\" https://dashboard.stripe.com/invoices")
This will be the start of our program. I'll break down each function individually.
But first to run this make sure to add
if __name__ == "__main__":
createInvoice()
to the bottom of the file so you can simply run
python3 CreateInvoice.py
where CreateInvoice.py is the name of my file name. You will need to run that command from the directory your file is located in.
getDateRanges()
GetDateRanges() requires 2 helper functions getMonth() and getDay(month). getMonth() will ask for user input on the month you want to export. I created it this way in case I am sending out an invoice for the prior month on the 1st of the next month. I wanted a little more customizability on what month I am actually creating an invoice for. GetDay is simply getting how many days are in the inputted month so we can create the correct range. There will be 1 day every 4 years where this doesn't work (leap year). I may update this in the future to accommodate for that.
In order to get the year you will have to add this to the top of your file
from datetime import datetime
Once you have the month and day you can create the ranges. Some simple string concatenation and you have your start time and end time.
def getDateRanges():
year = datetime.now().year
month = getMonth()
day = getDay(month=month)
dateRangeStart = str(year) + "-" + month + "-01T00:00:00.000"
dateRangeEnd = str(year) + "-" + month + "-" + str(day) + "T23:59:59.000"
return dateRangeStart, dateRangeEnd
def getMonth():
month = input("Enter invoice month: ")
if int(month) < 10:
month = "0" + month
return month
def getDay(month):
day = 31
if month == "04" or month == "06" or month == "09" or month =="11":
day = 30
elif month == "02":
day = 28
return day
getTimeSheetForDateRanges(dateRangeStart, dateRangeEnd)
In order to run this function you are going to need 2 things from Clockify.
Workspace Id
API Key
I showed how to get both of these in the Clockify section above.
Once you have both of those things you can fill in the 2 variables url and apiKey below
def getTimeSheetForDateRange(dateRangeStart, dateRangeEnd):
url = "https://reports.api.clockify.me/v1/workspaces/{workspaceId}/reports/summary"
apiKey = "" #enter your API key here
body = json.dumps({
"dateRangeStart": dateRangeStart,
"dateRangeEnd": dateRangeEnd,
"summaryFilter": {
"groups": [
"MONTH"
]
},
"exportType": "JSON"
})
headers = {
'X-Api-Key': apiKey,
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=body)
responseJson = json.loads(response.content)
return responseJson
Then you can create the JSON request by entering the date ranges, the summary filter, and export type. I just exported as JSON, but you can, for example, export as a CSV or XLSX if you just wanted a spreadsheet for local data tracking.
Make sure to import both of these so you can send requests and use JSON
import requests
import json
You also may need to run
pip3 install requests
as requests does come preinstalled with python3
Enter the headers with the apiKey and voila you have your time data for the month.
getMoneyInCents(timesheetJson)
This function is going to take in your timesheetJson data and spit out the money to charge in cents. Stripe's API takes the money in cents which is why we need to create it this way.
In order to use math you will need to
import math
Clockify sends us the data in seconds so the first thing I do is calculate the hours worked. Next I multiply my hours by my hourly rate, add a 1.5% service charge and round up that number to 2 decimal places. Finally I multiply by 100 and cast to int to make sure that number doesn't have any decimal places.
def getMoneyInCents(timesheetJson):
totalSeconds = timesheetJson["totals"][0]["totalTime"]
hours = totalSeconds / 60 / 60
# rounds up to nearest 2 decimal places
def ceil(number, digits) -> float: return math.ceil((10.0 ** digits) * number) / (10.0 ** digits)
money = ceil((hours * 100) * 1.015, 2)
moneyCents = int(money * 100)
return moneyCents
createStripeInvoice(moneyCents)
The penultimate thing we are going to do is create the Stripe invoice. This section is broken into 3 parts. First, add api key and customerId. Second, building the product. Third, building the invoice.
To use stripe you will have to run
pip3 install stripe
as stripe does not come preinstalled with Python. Also,
import stripe
We have out API key and customerId from the Stripe section of this post. Head up there if you need help getting those 2 things. We can enter that data in the strings below.
def createStripeInvoice(moneyCents):
stripe.api_key = ""
customer = ""
invoicePrice = buildStripeProduct(moneyCents=moneyCents)
buildStripeInvoice(invoicePrice=invoicePrice, customer=customer)
I have to build a product every time because unfortunately I have not found a way in Stripe to enter an hourly rate for a task. So I simply create a product with the amount of money I am charging. In this case I am creating a product called Development and entering the moneyCents we created in the prior function.
Once we create this product I pull the latest product created so I can get the Id stripe set.
def buildStripeProduct(moneyCents):
stripe.Product.create(
name="Development",
default_price_data={
"unit_amount": moneyCents,
"currency": "usd",
},
expand=["default_price"],
)
prices = stripe.Price.list(limit=1)
invoicePrice = prices["data"][0]["id"]
return invoicePrice
Now that we have the productId we can build the invoice.
def buildStripeInvoice(invoicePrice, customer):
# Create an Invoice
invoice = stripe.Invoice.create(
customer=customer,
collection_method='send_invoice',
days_until_due=30,
)
stripe.InvoiceItem.create(
customer=customer,
price=invoicePrice,
invoice=invoice.id
)
This function is first going to create an invoice and then simply add that invoice item (Product) to the invoice.
Last, but not least we are going to run this line of code from the first function we discussed.
os.system("open \"\" https://dashboard.stripe.com/invoices")
This simply opens a browser to the production invoice page so you can review the invoice and send it out.
Don't forget to add
import os
Wrapping Up
This is just the start of what my company Apptat can help your business with. We specialize in mobile development and design. If you are interested please email us at info@apptat.com or send us a message on our page.
If you have any questions feel free to add it in the comments below and I will get to it as soon as I can.
Also I have made the code to this tutorial free at this link here. If you would like to contribute to this or make any changes feel free to open a PR.
Comments