Invoice tool for generating accounting compliant reports.
This commit is contained in:
parent
40473f9fbd
commit
4bb24e8ff9
1 changed files with 138 additions and 0 deletions
138
tools/invoices.py
Normal file
138
tools/invoices.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
import stripe as _stripe
|
||||
_stripe.api_version = '2016-06-15'
|
||||
|
||||
import logging
|
||||
import time
|
||||
import sys
|
||||
import csv
|
||||
import codecs
|
||||
|
||||
from itertools import groupby
|
||||
from datetime import datetime, timedelta, date
|
||||
from cStringIO import StringIO
|
||||
|
||||
from app import billing as stripe
|
||||
|
||||
|
||||
def _format_timestamp(stripe_timestamp):
|
||||
date_obj = date.fromtimestamp(stripe_timestamp)
|
||||
return date_obj.strftime('%m/%d/%Y')
|
||||
|
||||
|
||||
def _format_money(stripe_money):
|
||||
return stripe_money/100.0
|
||||
|
||||
|
||||
def list_charges(num_days):
|
||||
""" List all charges that have occurred in the past specified number of days.
|
||||
"""
|
||||
now = datetime.utcnow()
|
||||
starting_from = now - timedelta(days=num_days)
|
||||
starting_timestamp = str(int(time.mktime(starting_from.timetuple())))
|
||||
created = {'gte': starting_timestamp}
|
||||
starting_after_kw = {}
|
||||
|
||||
while True:
|
||||
charges = stripe.Charge.list(limit=100, expand=['data.invoice'], created=created,
|
||||
**starting_after_kw)
|
||||
for charge in charges.data:
|
||||
yield charge
|
||||
|
||||
if not charges.has_more:
|
||||
break
|
||||
|
||||
starting_after_kw = {'starting_after': charge.id}
|
||||
|
||||
|
||||
def _date_key(line_item):
|
||||
return line_item.start, line_item.end
|
||||
|
||||
|
||||
def expand_invoice(invoice, total_amount):
|
||||
if invoice is None:
|
||||
yield total_amount, None, None, None
|
||||
else:
|
||||
data_iter = groupby(invoice.lines.data, lambda li: (li.period.start, li.period.end))
|
||||
for (period_start, period_end), line_items_iter in data_iter:
|
||||
line_items = list(line_items_iter)
|
||||
period_amount = sum(line_item.amount for line_item in line_items)
|
||||
yield period_amount, period_start, period_end, line_items[-1].plan
|
||||
|
||||
|
||||
def format_charge(charge):
|
||||
""" Generator which will return one or more line items corresponding to the
|
||||
line items for this charge.
|
||||
"""
|
||||
ch_status = 'Paid'
|
||||
if charge.failure_code is not None:
|
||||
ch_status = 'Failed'
|
||||
elif charge.amount_refunded > 0:
|
||||
ch_status = 'Refunded'
|
||||
|
||||
card = charge.source
|
||||
|
||||
invoice_iterable = expand_invoice(charge.invoice, charge.amount)
|
||||
for line_amount, period_start, period_end, plan in invoice_iterable:
|
||||
yield [
|
||||
_format_timestamp(charge.created),
|
||||
_format_timestamp(period_start) if period_start is not None else None,
|
||||
_format_timestamp(period_end) if period_end is not None else None,
|
||||
_format_money(line_amount),
|
||||
'credit_card',
|
||||
ch_status,
|
||||
plan.name if plan is not None else None,
|
||||
charge.id,
|
||||
charge.customer,
|
||||
card.address_city,
|
||||
card.address_state,
|
||||
card.address_country,
|
||||
card.address_zip,
|
||||
card.country,
|
||||
]
|
||||
|
||||
|
||||
class _UnicodeWriter(object):
|
||||
"""
|
||||
A CSV writer which will write rows to CSV file "f",
|
||||
which is encoded in the given encoding.
|
||||
"""
|
||||
def __init__(self, f, dialect=csv.excel, encoding='utf-8', **kwds):
|
||||
# Redirect output to a queue
|
||||
self.queue = StringIO()
|
||||
self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
|
||||
self.stream = f
|
||||
self.encoder = codecs.getincrementalencoder(encoding)()
|
||||
|
||||
@staticmethod
|
||||
def _encode_cell(cell):
|
||||
if cell is None:
|
||||
return cell
|
||||
return unicode(cell).encode('utf-8')
|
||||
|
||||
def writerow(self, row):
|
||||
self.writer.writerow([self._encode_cell(s) for s in row])
|
||||
# Fetch UTF-8 output from the queue ...
|
||||
data = self.queue.getvalue()
|
||||
data = data.decode('utf-8')
|
||||
# ... and reencode it into the target encoding
|
||||
data = self.encoder.encode(data)
|
||||
# write to the target stream
|
||||
self.stream.write(data)
|
||||
# empty queue
|
||||
self.queue.truncate(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.WARN)
|
||||
|
||||
days = 30
|
||||
if len(sys.argv) > 1:
|
||||
days = int(sys.argv[1])
|
||||
|
||||
transaction_writer = _UnicodeWriter(sys.stdout)
|
||||
rows = (line_item
|
||||
for one_charge in list_charges(days)
|
||||
for line_item in format_charge(one_charge))
|
||||
for row in rows:
|
||||
transaction_writer.writerow(row)
|
||||
sys.stdout.flush()
|
Reference in a new issue