Merge Payment CSVs Tool
This tool simplifies financial reporting by merging transaction data from Gumroad, Stripe, and PayPal into a single, unified CSV ledger. Managing multiple payment platforms often leads to fragmented data, making reconciliation and analysis time-consuming. This script standardizes the different CSV formats and combines them into one clean output.
Features
- Automatically detects and parses CSVs from Gumroad, Stripe, and PayPal
- Normalizes columns into a consistent schema: Date, Description, Amount (USD), Fee, Net, Payment Platform
- Handles currency conversion warnings (assumes USD; logs non-USD entries)
- Outputs a merged and sorted ledger by transaction date
- Lightweight and dependency-free (uses only Python standard library)
Usage
Run the script from the command line:
python merge_payment_csvs.py -o ledger_output.csv gumroad.csv stripe.csv paypal.csv
The input order does not matter β the script identifies each file by content. The output CSV will contain all transactions sorted from newest to oldest.
Output Schema
-
Date: Transaction date (YYYY-MM-DD) -
Description: Product or payment description -
Amount: Gross transaction amount in USD -
Fee: Processing fee (if applicable) -
Net: Net deposit amount -
Platform: Source platform (Gumroad, Stripe, PayPal)
Why Use This?
Whether youβre an indie hacker, freelancer, or small business, reconciling multiple payment reports manually is error-prone. This tool ensures accuracy and saves time during accounting or tax preparation.
Requirements
- Python 3.6+
- No external dependencies
Just drop in your CSVs and run β no configuration needed.
import csv
import argparse
from datetime import datetime
from pathlib import Path
def detect_platform(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
sample = f.read(4096)
if 'Gumroad' in sample:
return 'Gumroad'
elif 'Stripe' in sample:
return 'Stripe'
elif 'PayPal' in sample:
return 'PayPal'
return None
def parse_date(date_str, platform):
fmts = {
'Gumroad': '%Y-%m-%d %H:%M:%S',
'Stripe': '%Y-%m-%d',
'PayPal': '%m/%d/%Y',
}
for fmt in fmts.get(platform, []):
try:
return datetime.strptime(date_str.strip(), fmt).date()
except ValueError:
continue
return date_str
def parse_row(row, platform):
try:
if platform == 'Gumroad':
return [
parse_date(row[0], platform),
row[3],
row[6],
float(row[7]) - float(row[8]),
row[8],
platform
]
elif platform == 'Stripe':
return [
parse_date(row[0], platform),
row[4],
row[6],
row[7],
row[8],
platform
]
elif platform == 'PayPal':
return [
parse_date(row[0], platform),
row[2],
row[5],
row[6],
row[7],
platform
]
except (IndexError, ValueError):
return None
return None
def main():
parser = argparse.ArgumentParser(description='Merge Gumroad, Stripe, PayPal CSVs')
parser.add_argument('files', nargs='+', type=Path)
parser.add_argument('-o', '--output', type=Path, default='merged_ledger.csv')
args = parser.parse_args()
records = []
header = ['Date', 'Description', 'Amount', 'Fee', 'Net', 'Platform']
for f in args.files:
platform = detect_platform(f)
if not platform:
print(f'Skipping unknown file: {f}')
continue
with open(f, 'r', encoding='utf-8') as infile:
reader = csv.reader(infile)
next(reader)
for row in reader:
if row:
record = parse_row(row, platform)
if record:
records.append(record)
records.sort(key=lambda x: x[0] if isinstance(x[0], datetime.date) else datetime.min.date(), reverse=True)
with open(args.output, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(header)
writer.writerows(records)
print(f'Merged {len(records)} transactions into {args.output}')
if __name__ == '__main__':
main()
Top comments (0)