DEV Community

Golden Alien
Golden Alien

Posted on

πŸ› οΈ merge_payment_csvs: Merge Gumroad, Stripe, and PayPal CSVs into a unified ledger for easy

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Top comments (0)