Companies using multiple payment processors (Stripe + PayPal + Square + bank ACH) often spend 10–20 hours/week manually matching transactions to accounting.
And the painful part is: even when the processor “charged correctly”, your internal logic can still be wrong — leading to:
- missed transactions
- duplicates / double-counting
- fee mismatches
- refunds not linking back to originals
- delayed cash visibility
So I built an open-source system that automates the workflow:
✅ daily ingestion from processors → ✅ unified ledger → ✅ intelligent matching → ✅ exception queue → ✅ optional QuickBooks journal sync
This post is a full build breakdown + how you can run it yourself.
What I built
A Multi-Processor Payment Reconciliation pipeline using:
- n8n for orchestration
- PostgreSQL for storage (works on Supabase or any Postgres)
- Python inside n8n Code nodes for transforms + matching
It delivers:
- Daily auto-pull from Stripe, PayPal, Square, and ACH (CSV)
- Unified transaction ledger (canonical schema)
- Matching engine that handles refunds, split payments, timing differences, fee variance, and currency conversion
- Discrepancy queue (missing txns, duplicates, fee mismatches, unmatched payouts)
- (Optional) QuickBooks sync for journal entries
Value: save ~40+ hours/month and remove the majority of human error.
Repo
The 8 workflows (exactly what’s in the build)
These are the only workflows in the project (names are consistent across repo + screenshots):
- 01- Ingest Stripe
- 02 - Ingest PayPal
- 03 - Ingest Square
- 04 - Ingest ACH (CSV)
- 05 - Normalize & Enrich
- 06 - Reconcile & Match Engine
- 07 - QuickBooks Sync
- 08 - Exception Notifications
Screenshots for each workflow live in
docs/screenshots/.
Screenshots
Architecture (high level)
The system follows a simple but reliable pipeline:
- Ingest from each processor (API pulls + CSV)
- Transform into a canonical ledger schema
- Persist in Postgres with idempotent upserts
- Normalize & enrich (standardize fields, compute derived values)
- Match transactions to payouts/settlements (multi-pass)
- Write outputs
matches-
exceptions- Notify: daily digest of exceptions
- Optional: build + sync QuickBooks journals
More detail in: docs/architecture.md
Why a unified ledger matters
Each processor has different terminology and event shapes:
- Stripe: charges, payment intents, balance transactions, payouts
- PayPal: transaction events, fees, settlement quirks
- Square: payments, refunds, fees
- ACH/bank: CSV exports with their own structure
If you try to reconcile directly from raw payloads, you end up rebuilding logic repeatedly.
So the core idea is:
Convert everything into one canonical structure first.
Once everything is in a unified ledger, reconciliation becomes a deterministic matching problem.
Database design (Postgres)
The schema is designed around three layers:
1) Raw ingestion (audit trail)
- Store raw payloads for traceability and debugging.
2) Normalized transactions
- Convert each processor’s format into consistent fields.
3) Reconciliation artifacts
- Runs, candidates, matches, exceptions, and sync jobs.
I keep the database portable: it’s standard SQL in db/schema.sql.
Matching engine (how it works)
The matching strategy is intentionally rule-based and explainable.
Pass 1 — Exact linking
- external_id matches
- same processor account
- same currency
- within time window
Pass 2 — Fee-aware matching
Processors often represent amounts differently (gross vs net vs fee separately).
So we normalize:
- gross_amount
- fee_amount
- net_amount
…and match on net/gross using tolerances.
Pass 3 — Refund linking
- link refunds back to original transaction
- support partial refunds
Pass 4 — Split / partial payments
When multiple transactions sum to one expected settlement:
- group candidates within a window
- pick combinations that sum to target within tolerance
Pass 5 — Score + threshold
Each candidate gets a score.
- above threshold → match
- below threshold → exception
This keeps the system deterministic and auditable.
Walkthrough of the workflows
01- Ingest Stripe
Cron → HTTP Request: Stripe → Transform to Ledger → Upsert row(s)
What matters:
- pagination
- incremental cursor sync (so you don’t re-fetch everything)
- idempotent upsert keys
02 - Ingest PayPal
Same structure:
Cron → HTTP Request: PayPal → Transform to Ledger → Upsert row(s)
03 - Ingest Square
Same structure:
Cron → HTTP Request: Square → Transform to Ledger → Upsert row(s)
04 - Ingest ACH (CSV)
This is the “bank feed” option:
Cron → Read CSV → Parse CSV → Upsert row(s)
05 - Normalize & Enrich
This phase standardizes and enriches:
Cron → Query Ledger → Enrich Transactions → Upsert row(s)
Examples of enrichment:
- compute net = gross − fee (when missing)
- standardize currency codes
- compute base currency conversion (if you store FX)
- normalize descriptions
06 - Reconcile & Match Engine
This is the core:
Cron → Create Recon Run → Load Transactions + Load Payouts → Match & Create Exceptions → Insert Matches + Insert Exceptions
It produces two outputs:
- matches: automatically reconciled items
- exceptions: what a human needs to review
07 - QuickBooks Sync
This builds journal entries (and optionally posts):
Cron → Load Payouts → Build Journal Entries → Insert QB Sync Job
A typical clearing account journal model:
- Debit Bank (net payout)
- Debit Merchant Fees (fees)
- Credit Processor Clearing (gross)
08 - Exception Notifications
Daily digest of issues:
Cron → Load Exceptions → Format Digest → Send a message
I kept this intentionally simple (Gmail), but you can swap to Slack/SMS.
How to run it (quick start)
1) Create database tables
Run:
psql "$DATABASE_URL" -f db/schema.sql
2) Load seed data (optional)
Seed CSVs exist under db/seed/ (or data/ depending on your repo layout).
3) Import n8n workflows
Import JSONs under:
n8n/workflows/
4) Configure credentials
Set:
- database connection
- Stripe / PayPal / Square API keys
- QuickBooks credentials (optional)
5) Trigger the run
Execute in order:
- 01 → 04 (ingestion)
- 05 (normalize/enrich)
- 06 (match engine)
- 07 (optional)
- 08 (digest)
What I learned building this
1) Reconciliation is mostly “data normalization”
Matching becomes easy when your ledger is consistent.
2) Idempotency is non-negotiable
Every ingestion + write must be safe to rerun.
3) Exceptions are the product
The automation is nice — but the real value is a clean, reliable exception queue.
4) Rule-based works surprisingly well
Most businesses can get 80–95% match rates without ML.
Extensions / next improvements
- Add FX rate ingestion + robust multi-currency settlement handling
- Add Slack notifications + severity thresholds
- Add dashboards (Metabase / Superset / simple UI)
- Add a small “review UI” for exceptions
- Add support for more processors (Adyen, Braintree, etc.)
If you want to use this
Feel free to:
- fork the repo
- adapt the schema
- plug in your own chart of accounts / journal logic
If you’re building finance ops automation at scale, I’m also happy to share patterns and templates.
Repo: https://github.com/Etherlabs-dev/multi-processor-reconciliation
If you found this useful, drop a comment — I’m publishing more systems like this as part of my 30-day build challenge.








Top comments (0)