Payments#

With hourly, issuing invoices is a breeze. To issue a stripe invoice:

hourly-report invoice=stripe stripe.customer.email=myclient@momandpop.com

See the Instructions for configuring hourly for Stripe.

To issue a BTCPay invoice connected to your BTCPay Server:

hourly-report invoice=btcpay

See the Instructions for configuring hourly with a BTCPay server.

Currently, only BTCPay and Stripe are supported. If you are interested in adding support for other invoicing platforms, issue a pull request. If you want to sponsor development for an hourly feature, contact Asher.

Stripe#

Stripe is a popular global payment processing platform for credit cards. They have an invoice API that allows hourly to issue invoices on your behalf in a single command:

hourly-report invoice=stripe stripe.customer.email=my.client@momandpop.com

Stripe Setup#

Step 1 - install the stripe python api:#

pip install --upgrade stripe

Step 2 - Create a stripe account#

You will need an account at Stripe. Be sure to follow the steps for a developer looking to handle one-time payments. You should also set up your invoice template settings.

Step 3 - Set environment variables#

From the Stripe dashboard:

  • copy the Secret key and set it as an environment variable STRIPE_API_SECRET_KEY

You can also paste in the Secret key later as a command-line argument argument to hourly: stripe.secret_key=<your stripe secret key>

Warning

You will probably want to use your test_ API keys first!

Hourly Configuration#

Hourly provides the following default configuration for stripe invoices:

invoice:
  # see https://stripe.com/docs/billing/invoices/create-invoice#python
  stripe:
    secret_key: ${env:STRIPE_API_SECRET_KEY}
    customer:
      name: null
      email: null
      description: null
    customer_id: null # skips customer creation if set
    invoice_item:
      customer: # overridden by customer_id
      amount: # overrides earnings
      currency: #  overrides compensation currency
      description: # overrides <hours worked> from repo.start_date to repo.end_date
    invoice:
      customer: # overridden by customer_id
      collection_method: send_invoice
      days_until_due: 30
      auto_advance: True # auto-finalize this draft after ~1 hour.
      footer: Time sheet generated by hourly
    send_invoice: true # sends invoice to customer.email immediately
    return_status: false
    logging: 40 # https://docs.python.org/3/library/logging.html#logging-level

The following fields are subsets of stripe's own API, which has additional fields you can use:

Any of the defaults can be overridden, either at command line or by your project's configuration override. However, stripe.customer.email is a required field.

Generating Stripe invoices#

To generate a stripe invoice for your repo, you will need to specify an email address. Hourly will prepare an invoice and ask for confirmation. Here is an example of what that looks like when I run hourly on the hourly repo:

hourly-report invoice=stripe repo.start_date="Jan 1, 2020" stripe.customer.email=apembroke+hourly@gmail.com"

1 days 04:02:14, 28.04 hours worked
2803.72 USD
Generating stripe invoice for Asher Pembroke
creating new customer
new customer_id: cus_GVy3BWS792lu4D
customer:
  description: null
  email: apembroke+hourly@gmail.com
  name: null
customer_id: cus_GVy3BWS792lu4D
invoice:
  auto_advance: false
  collection_method: send_invoice
  customer: cus_GVy3BWS792lu4D
  days_until_due: 30
  footer: Time sheet generated by hourly
invoice_item:
  amount: 280373
  currency: usd
  customer: cus_GVy3BWS792lu4D
  description: 28.04 hours worked from 2020-01-03T18:44:04-05:00 to 2020-01-09T02:04:18-05:00
logging: 40
return_status: false
secret_key: ${env:STRIPE_API_SECRET_KEY}
send_invoice: true

Is this correct? (yes/n): yes
Success!
Invoice will be sent to apembroke+hourly@gmail.com
Invoice may be paid at https://pay.stripe.com/invoice/<---- redacted ----->
View your invoice at https://dashboard.stripe.com

Note

I have redacted actual payment URL

The recipient should get an email from stripe to pay by credit card. You can test credit card payment using one of their testing cards. Meanwhile, visiting the url should show you a page like this:

Stripe Invoice

BTCPay#

Background#

BTCPay is a decentralized payment processing platform for accepting cryptocurrency. With BTCpay integration, you can issue invoices and receive crypto payments with maximum privacy and minimal cost. You can use a third-party provider or host it yourself - the only difference will be the domain name used to create the local client.

Setup#

First you will need to register and create a store on a BTCPay server. There are a few free ones listed on btcpayserver.org, but please use caution when choosing a free service, as there are privacy trade-offs to consider. For maximum privacy and security, host one yourself.

Once you've chosen a server, connect a bitcoin wallet to your new store. This can be done in your store's general settings, under Derivation Scheme, where you provide your wallet's xpubkey - BTCPay Server uses this key to generate a unique payment address for every invoice issued.

Warning

A legitimate BTCPay Server should only ask for your wallet's xpubkey and NEVER YOUR PRIVATE KEY

Info

BTCPay also supports Lightning invoices, which allows for instant settlement. This involves some tradeoffs in security and availability.

Then you will need to install the btcpay-python client

pip install btcpay-python

Pairing with BTCPay server#

Follow these pairing instructions from the kind BTCPay developers.

Note

These instructions correspond to "The manual way" - we want to be able to create a btcpay client on-demand without storing it in a database.

I'm essentially repeating their instructions below:

Step 1 - Get a pairing code#

  • On your BTCPay server, browse to Stores > Store settings > Access tokens > Create new token
  • Fill in the form:
    • Label:
    • Public key: leave blank
  • Click save and then copy the 7 digit pairing_code from the success page

Step 2 - Generate a private key#

This can be done with the following code:

from btcpay import crypto
privkey = crypto.generate_privkey()

with open('btcpayserver.pem', 'w') as pem:
    pem.write(privkey)

Here we store the private key in a PEM file. By default, hourly will look for btcpayserver.pem in the top level of your git repo, but you can use a different name.

Warning

Do not add the pem file to your git repo! List it in your .gitignore so you don't do so by accident.

Step 3 - Create a client#

Create a client using host url of your btcpayserver (e.g. https://btc.exitpay.org) and private key:

client = BTCPayClient(host=host_url, pem=privkey)

Store your server's host url in the environment variable BTCPAYSERVER_HOST.

Step 4 - Generate a pairing token#

using the pairing code from Step 1

token = client.pair_client(pairing_code)

merchant_token = token['merchant']

Save the merchant_token as an environment variable BTCPAYSERVER_MERCHANT

Step 5 - Recreate the client#

Whenever you like:

client = BTCPayClient(
    host = host_store,
    pem = privkey,
    tokens = token,
)

Step 6 - Generate a test invoice#

Assuming you have completed the steps to connect a wallet to your btcpayserver, you should be able to run the following code to generate an invoice.

new_invoice = client.create_invoice({"price": 20, "currency": "USD"})
print(new_invoice['url'])

This should give you a payment url you can email to your client/employer.

Depending on how you set up your BTCPay Server, the invoice will only be valid for a short period of time (default is 15 minutes). There is a trade-off here: a short time period mitigates the risk of currency fluctuation, but requires that the client/employer must act quickly to pay the invoice.

Hourly configuration#

Hourly creates a BTCPayClient through the following configuration:

# for invoice spec, see https://bitpay.com/api/#rest-api-resources-invoices

invoice:
  btcpay:
    host: ${env:BTCPAYSERVER_HOST}
    tokens:
      merchant: ${env:BTCPAYSERVER_MERCHANT}
    pem: btcpayserver.pem # file holding btcpayserver private key
    return_status: false
    invoice:
      currency: null # will be honored if set
      price: null # will be honored if set, else determined by wage
      orderId: null 
      fullNotifications: True
      extendedNotifications: True
      transactionSpeed: medium
      notificationURL: null # https://mywebhook.com
      notificationEmail: null # myemail@email.com
      redirectURL: null # https://yourredirecturl.com
      buyer: 
        email: null # fox.mulder@trustno.one
        name: null # Fox Mulder
        phone: null # 555-123-456
        address1: null # 2630 Hegal Place
        address2: null # Apt 42
        locality: null # Alexandria
        region: # VA
        postalCode: # 23242
        country: # US
        notify: True
      itemDesc: null # will be honored if set, else hourly will provide

This allows hourly to access your environment variables and the pem file you created above. Any of these parameters can be overridden when you run hourly. Here are some examples.

hourly-report invoice=btcpay btcpay.pem=<private key> 
hourly-report invoice=btcpay btcpay.pem=/path/to/other/btcpayserver.pem
hourly-report invoice=btcpay btcpay.host=https://myprivateserver.com

Hourly Invoicing#

If you configured hourly with BTCPay, you can generate an invoice for your git repo in a given date range. Here is what that looks like when applied to the hourly repo:

hourly-report invoice=btcpay payment=btcpay repo.start_date="Jan 1, 2020" repo.end_date="Jan 6, 2020"

Processing timesheet for Asher Pembroke
pay period: 2020-01-03 18:44:04-05:00 -> 2020-01-05 18:34:41-05:00
ignoring pro bono
                     TimeIn     LogIn                email                   TimeOut     LogOut                email TimeDelta     Hours
0 2020-01-03 18:44:04-05:00  clock-in  apembroke@gmail.com 2020-01-03 20:31:57-05:00  clock-out  apembroke@gmail.com  01:47:53  1.798056
1 2020-01-03 20:45:54-05:00  clock-in  apembroke@gmail.com 2020-01-03 22:40:56-05:00  clock-out  apembroke@gmail.com  01:55:02  1.917222
2 2020-01-04 13:16:11-05:00  clock-in  apembroke@gmail.com 2020-01-04 14:01:43-05:00  clock-out  apembroke@gmail.com  00:45:32  0.758889
3 2020-01-04 14:55:18-05:00  clock-in  apembroke@gmail.com 2020-01-04 16:35:04-05:00  clock-out  apembroke@gmail.com  01:39:46  1.662778
4 2020-01-04 19:56:53-05:00  clock-in  apembroke@gmail.com 2020-01-04 21:06:20-05:00  clock-out  apembroke@gmail.com  01:09:27  1.157500
5 2020-01-04 23:59:21-05:00  clock-in  apembroke@gmail.com 2020-01-05 03:59:59-05:00  clock-out  apembroke@gmail.com  04:00:38  4.010556
6 2020-01-05 16:32:33-05:00  clock-in  apembroke@gmail.com 2020-01-05 17:03:22-05:00  clock-out  apembroke@gmail.com  00:30:49  0.513611
7 2020-01-05 17:29:01-05:00  clock-in  apembroke@gmail.com 2020-01-05 18:34:41-05:00  clock-out  apembroke@gmail.com  01:05:40  1.094444
0 days 12:54:47, 12.91 hours worked
1291.31 USD
generating invoice for current user Asher Pembroke
buyer:
  address1: null
  address2: null
  country: null
  email: null
  locality: null
  name: null
  notify: true
  phone: null
  postalCode: null
  region: null
currency: USD
extendedNotifications: true
fullNotifications: true
itemDesc: 12.91 hours worked from 2020-01-03T18:44:04-05:00 to 2020-01-05T18:34:41-05:00
notificationEmail: null
notificationURL: null
orderId: null
price: 1291.3055555555554
redirectURL: null
transactionSpeed: medium

Is this correct? (yes/n)yes
Success! Your invoice may be paid here: https://btc.exitpay.org/invoice?id=MoSbFujB7AwcrvfMN21gGC

Navigate to the payment url provided:

Hourly Invoice