Closed chadwhitacre closed 6 years ago
They are looking at Braintree's 1099-K for us which shows $36,428 income and we reported $18,758. In other words, they are counting our escrow as income to Gratipay. The reason we didn't file our own 1099-Ks for our own receivers is the "Exception for de minimis payments":
A TPSO is required to report any information concerning third party network transactions of any participating payee only if for the calendar year: The gross amount of total reportable payment transactions exceeds $20,000, and The total number of such transactions exceeds 200.
We won't have anyone over the $20,000 limit for 2015, but now that I think of it we may have people that exceed the 200 limit, since we are dealing in micro-payments.
I think of it we may have people that exceed the 200 limit, since we are dealing in micro-payments.
We only bill people once a week, no? i.e. maximum of 52 transactions per payee, per year.
@rohitpaulk But if someone has 10 givers that could be 520 transactions for the year. Also interacts with $10 minimum charge-in. Bottom line: we need to run the numbers.
We've got about two weeks to get a response in the (registered) mail to meet the October 5 deadline.
Just wrapped a conversation with a CPA, who serendipitously happened to be here at Paramount. I explained Gratipay to him, our status as a payment processor, etc. He reviewed the IRS letter, and he is going to put together an engagement letter to help us sort this out for an amount not to exceed $500. He doesn't think it'll escalate beyond that but of course who knows? :) He has experience with IRS audits (I guess technically this isn't an audit yet), and knows how to navigate the system (e.g., call the local office vs. responding by mail).
Engagement letter coming next week. He took the IRS letter with him. We also need to provide:
We don't need to worry about the number of transactions in 2015 at this point.
Emailed the CPA to establish contact. Started working up a query but it will take some thinking.
Need to account for transfers
and payments
(pre- and post-Gratipocalypse).
gratipay::DATABASE=> select sum(amount) from payments where direction='to-team' and "timestamp"::text >= '2015-01-01' and "timestamp"::text < '2016-01-01' and team != 'Gratipay';
┌──────────┐
│ sum │
├──────────┤
│ 31244.01 │
└──────────┘
(1 row)
Sent the tax return. Need numbers ...
Can I repro the 18,758 number, first of all?
Phew. :)
gratipay::DATABASE=> \i irs.sql
┌──────────┐
│ ?column? │
├──────────┤
│ 18758.03 │
└──────────┘
(1 row)
select
(select sum(amount) from payments where direction='to-team' and team='Gratipay'
and "timestamp"::text < '2016-01-01' and "timestamp"::text >= '2015-01-01')
+
(select sum(amount) from transfers where tippee='Gratipay'
and "timestamp"::text < '2016-01-01' and "timestamp"::text >= '2015-01-01');
🤔
gratipay::DATABASE=> \i irs.sql
┌──────────────┐
│ total volume │
├──────────────┤
│ 212514.20 │
└──────────────┘
(1 row)
select
(select sum(amount) from payments where direction='to-team'
and "timestamp"::text < '2016-01-01' and "timestamp"::text >= '2015-01-01')
+
(select sum(amount) from transfers where
"timestamp"::text < '2016-01-01' and "timestamp"::text >= '2015-01-01')
as "total volume";
That's way more than the 36,428 that Braintree reported. I guess the rest was Balanced? Did they not report 1099-Ks for 2015 since they went out of business? Seems likely ...
Alright, can we repro the 36,428 figure coming in from Braintree?
Um ... when did we start charging on Braintree?
Alright, so our first Braintree charges were on 2015-05-29.
(We have charges against network='braintree'
exchange_routes
dating back before that because of the potential corruption mentioned at https://github.com/gratipay/gratipay.com/issues/4442#issuecomment-305864193.)
Hmm ... low by $7,203 relative to Braintree's 1099-K? What am I missing? 🤔
gratipay::DATABASE=> \i irs.sql
┌──────────┐
│ sum │
├──────────┤
│ 29225.88 │
└──────────┘
(1 row)
select sum(amount + fee)
from exchanges e
join exchange_routes er
on e.route = er.id
where "timestamp"::text < '2016-01-01' and "timestamp"::text >= '2015-05-29'
and network = 'braintree-cc'
;
What does the Braintree dashboard report?
$27,671.68 if I constrain to status='succeeded'
.
2,266 rows. Braintree reports 2,516 successful transactions for the time period 2015-01-01 through 2015-12-31 inclusive.
They begin on 2015-05-29 as expected.
Oh! My query includes some refunds ...
(https://gratipay.news/charging-in-arrears-18cacf779bee per note field.)
Good lord we stopped keeping ref
at that point (2015-10-15)? 😞
Looking at amount > 0 gets me to $30,232.90, but then only 1,687 transactions.
Alright, filtering out refunds at Braintree drops it to 2,246. Do those sum to $36,428?
Card Type: All Created At: 01/01/2015 12:00 AM - 12/31/2015 11:59 PM Created Using: All Customer Location: All Payment Instrument Type: Credit Card, PayPal Account Status: authorized, submitted_for_settlement, settlement_pending, settling, settled, voided Transaction Source: All Transaction Type: sale
Now seeing this...don't we have the raw downloads for Braintree in raw https://github.com/gratipay/logs/tree/master/misc/braintree-import
Yeah, I've got a new one as well now. Thank goodness we at least have participant_id
in there. 😅
Filtering to only settled
status brings it down to 2,225.
Card Type: All
Created At: 01/01/2015 12:00 AM - 12/31/2015 11:59 PM
Created Using: All
Customer Location: All
Payment Instrument Type: Credit Card, PayPal Account
Status: settled
Transaction Source: All
Transaction Type: sale
Okay, close!
Summing the transactions in that last download gives me $37,538.56, off by $1,110.56.
What about looking at disbursements instead of transactions?
ಠ_ಠ
$ ./process-disbursements.py
35550.43
$
Alright, will pick up there next time!
Aren't they charging tax on income? Would disbursements be an expense (sort of)? And should we be taxed for money paid to Gratipay team and not just the money passing through gratipay.com?
Yes but the question is how do we file with the IRS to show them that that is the situation, because clearly we didn't file in the best way in 2015 so presumably also for 2016.
Can I dredge up Braintree's 1099-K for us?
Yes!
Dashboard > Documents > Tax Statements > IRS 1099K - 2015
Got it! Needed to use "Settled date range" vs. "Creation date range."
Card Type: All Created Using: All Customer Location: All Payment Instrument Type: Credit Card, PayPal Account Settled At: 01/01/2015 12:00 AM - 12/31/2015 11:59 PM Status: settled Transaction Source: All Transaction Type: sale
Transactions in download count to 2,165 and sum to $36,428.04. 👍
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import csv
from decimal import Decimal as D
inp = csv.reader(open('transaction_search.csv'))
headers = next(inp)
total = D('0.00')
for row in inp:
rec = dict(zip(headers, row))
amount = rec['Settlement Amount']
date = rec['Settlement Date']
participant_id = rec['participant_id']
try:
total += D(amount)
except:
import pdb; pdb.set_trace()
print(date, participant_id, amount)
if not participant_id:
raise
print(total)
P.S. Forwarded 1099-K to accountant, still waiting for engagement letter from him.
Okay! Now to match Braintree transactions to the exchanges
table ... :rage1:
Well, let's see how many we can match on ref
, I guess, to start with?
744 / 2165 = 34% of refs in the Braintree transaction download from 2015 are missing from the exchanges
table.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import csv
from decimal import Decimal as D
from gratipay import wireup
db = wireup.db(wireup.env())
inp = csv.reader(open('transaction_search.csv'))
headers = next(inp)
known_refs = set(db.all('''
SELECT ref
FROM exchanges
WHERE "timestamp"::text >= '2015-01-01' and "timestamp"::text < '2016-01-01'
'''))
for row in inp:
rec = dict(zip(headers, row))
amount = D(rec['Settlement Amount'])
date = rec['Settlement Date']
participant_id = rec['participant_id']
ref = rec['Transaction ID']
if ref not in known_refs:
print(ref)
Of the 1,421 transactions for which we have a ref
, the amount
and participant_id
all match exactly, and the date matches within one day.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import csv
import datetime
from decimal import Decimal as D
from gratipay import wireup
db = wireup.db(wireup.env())
inp = csv.reader(open('transaction_search.csv'))
headers = next(inp)
exchanges = {e.ref: e for e in db.all('''
SELECT ref
, "timestamp"::date date
, amount + fee amount
, p.id participant_id
FROM exchanges e
JOIN participants p
ON p.username = e.participant
WHERE "timestamp"::text >= '2015-01-01' and "timestamp"::text < '2016-01-01'
''')}
for row in inp:
rec = dict(zip(headers, row))
amount = D(rec['Settlement Amount'])
m,d,y = map(int, rec['Settlement Date'].split('/'))
date = datetime.date(y,m,d)
participant_id = int(rec['participant_id'])
ref = rec['Transaction ID']
ours = exchanges.get(ref)
if ours:
assert amount == ours.amount, (amount, ours.amount)
assert (date - ours.date).days <= 1, (date, ours.date)
assert participant_id == ours.participant_id, (participant_id, ours.participant_id)
Alright, so the scary possibility here is that we did not record 34% of the Braintree transactions that happened during 2015.
That is a disheartening thought.
[gratipay] $ run defaults.env local.env ./match-2015.py
number unmatched: 744
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import csv
import datetime
import sys
from collections import defaultdict
from decimal import Decimal as D
from gratipay import wireup
db = wireup.db(wireup.env())
inp = csv.reader(open('transaction_search.csv'))
headers = next(inp)
one_day = datetime.timedelta(days=1)
# load up transactions known to us
gratipay = {e.ref: e for e in db.all('''
SELECT e.id exchange_id
, ref
, "timestamp"::date date
, amount + fee amount
, p.id participant_id
FROM exchanges e
JOIN participants p
ON p.username = e.participant
WHERE "timestamp"::text >= '2015-01-01' and "timestamp"::text < '2016-01-01'
''')}
# loop through transactions known to braintree, looking for those that don't
# have a corresponding match on ref in exchanges: these are the ones we'll need
# to fuzzy-match
braintree = list()
for row in inp:
rec = dict(zip(headers, row))
participant_id = int(rec['participant_id'])
m,d,y = map(int, rec['Settlement Date'].split('/'))
date = datetime.date(y,m,d)
amount = D(rec['Settlement Amount'])
ref = rec['Transaction ID']
ours = gratipay.pop(ref, None)
if ours:
assert participant_id == ours.participant_id, (participant_id, ours.participant_id)
assert (date - ours.date).days <= 1, (date, ours.date)
assert amount == ours.amount, (amount, ours.amount)
else:
braintree.append((ref, participant_id, date, amount))
# now loop through the ones we need to fuzzy-match, and try to match them up
gratipay_remaining = {(e.participant_id, e.date, e.amount): e.exchange_id for e in gratipay.values()}
possible_matches = defaultdict(list)
unmatched = []
for ref, participant_id, date, amount in braintree:
guesses = [ (participant_id, date - one_day, amount)
, (participant_id, date, amount)
, (participant_id, date + one_day, amount)
]
for guess in guesses:
exchange_id = gratipay_remaining.get(guess)
if exchange_id:
possible_matches[ref].append(exchange_id)
if ref not in possible_matches:
unmatched.append(ref)
# look through the matches we found ... write out a csv of exchange_id,ref ...
# we'll use this to backfill the ref column in exchanges ... if any matches
# failed then say so
ambiguous = []
out = csv.writer(open('matches.csv', 'w+'))
for ref, m in possible_matches.items():
nmatches = len(m)
if nmatches > 1:
ambiguous.append(ref)
exchange_id = m[0]
out.writerow((ref, exchange_id))
if unmatched:
print('number unmatched: {}'.format(len(unmatched)), file=sys.stderr)
if ambiguous:
print('number ambiguous: {}'.format(len(ambiguous)), file=sys.stderr)
How about a spot-check of transactions for some given user?
The funny thing is that we do have participant_id
for all of these ... right?
Okay, so this is encouraging. I grabbed a random item from unmatched
, and manually dereferenced the transaction in the Braintree dashboard and the ~user history on Gratipay. I do see the transaction showing up in history as expected. Is there a bug in the match script that explains the misfire?
First time for everything, friends. :)