
Payment Reconciliation Automation
- 63 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Automate payment reconciliation across Stripe, PayPal, and bank accounts with matching rules, exception handling, and discrepancy alerting.
About
Automates reconciliation of payments across Stripe, PayPal, and bank accounts using matching rules with exception handling and discrepancy alerts. A developer uses it to eliminate manual finance reconciliation and catch payment mismatches.
- Automated matching rules across Stripe, PayPal, and bank data
- Exception handling and discrepancy alerting
Payment Reconciliation Automation by the numbers
- 63 all-time installs (skills.sh)
- Ranked #565 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill payment-reconciliation-automationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 63 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Automate payment reconciliation across Stripe, PayPal, and bank accounts with matching rules, exception handling, and discrepancy alerting.
Files
Payment Reconciliation Automation
Overview
Payment reconciliation matches what your payment processors (Stripe, PayPal, Shopify Payments) say they collected against what your accounting system recorded and what your bank actually received. Discrepancies arise from processor fees, refunds, chargebacks, rolling reserves, and currency conversion — none of which are automatically journaled in most ecommerce setups. Manual reconciliation does not scale beyond a few hundred transactions per day.
For most merchants, automated reconciliation is solved by connecting your ecommerce platform to your accounting system (QuickBooks, Xero) via a dedicated sync tool rather than building custom matching logic.
When to Use This Skill
- When your finance team spends more than two hours per day on manual payment reconciliation
- When processing transactions across two or more payment processors (e.g., Stripe + PayPal)
- When month-end close is blocked by unresolved payment discrepancies
- When chargebacks or refunds are not being reliably reflected in your accounting system
- When you need SOC 2 or PCI-DSS audit documentation for payment flows
Core Instructions
Step 1: Determine your platform and choose the reconciliation approach
| Platform | Recommended Reconciliation Tool | Notes |
|---|---|---|
| Shopify Payments | A2X or Bench Accounting | A2X maps Shopify payouts to QuickBooks/Xero journal entries automatically, handling fees, refunds, and adjustments |
| Shopify + Stripe | A2X for Stripe + A2X for Shopify | Connect both sources to QuickBooks/Xero; A2X reconciles each payout separately |
| WooCommerce + Stripe | Synder or WooCommerce QuickBooks Online plugin | Synder syncs every Stripe transaction to QuickBooks with fees separated |
| WooCommerce + PayPal | Synder or PayPal for WooCommerce + QuickBooks | Synder handles both Stripe and PayPal in one connection |
| BigCommerce | A2X (has BigCommerce connector) or Synder | Same approach as Shopify |
| Custom / Headless | Build reconciliation pipeline using Stripe Balance Transactions API | See Custom / Headless section below |
Rule of thumb: if you are on Shopify, WooCommerce, or BigCommerce, use A2X or Synder before building anything custom. These tools cost $25–$100/month and eliminate weeks of development work.
Step 2: Set up automated reconciliation with a sync tool
---
Shopify (A2X — recommended)
1. Sign up at a2xaccounting.com and install the Shopify app 2. Connect your Shopify store: go to A2X → Connect a store → Shopify and authorize 3. Connect your accounting system: go to A2X → Settings → Accounting and connect QuickBooks Online or Xero 4. Configure account mapping:
- In A2X, map Shopify's sales, refunds, fees, and adjustments to your chart of accounts
- Map Shopify Payments fees to "Merchant Fees" expense account
- Map gift card sales to a liability account (not revenue)
5. A2X automatically processes each Shopify payout when it arrives and creates a summarized journal entry in QuickBooks/Xero that matches the bank deposit exactly
What A2X handles automatically:
- Shopify processing fees netted from payouts
- Refunds issued in a different period than the original sale
- Chargebacks and chargeback reversals
- Gift card redemptions vs. purchases
- Multi-currency conversions
WooCommerce (Synder — recommended)
1. Sign up at synderapp.com and connect your WooCommerce store via the plugin (install from WordPress.org) 2. Connect your payment processors: go to Synder → Settings → Platforms and connect Stripe and/or PayPal with API keys 3. Connect QuickBooks Online or Xero under Synder → Settings → Accounting 4. Configure transaction categorization: map product categories to income accounts, fees to expense accounts 5. Synder syncs every transaction in near-real-time, recording both the gross amount and the processor fee separately
Synder vs. A2X for WooCommerce: Synder works at the transaction level (one QuickBooks entry per transaction); A2X works at the payout level (one summary per deposit). Synder is better for detailed reporting; A2X is better for high-volume stores.
BigCommerce
1. Install A2X from the BigCommerce App Marketplace 2. Follow the same setup process as Shopify above — A2X's BigCommerce connector works identically to the Shopify one 3. Connect your payment gateway (Stripe or PayPal) separately to A2X for full reconciliation including processor fees
---
Custom / Headless
For custom storefronts, build a reconciliation pipeline that uses Stripe's balance transactions (the authoritative record for every funds movement) as the source of truth:
// Ingest Stripe balance transactions — the only feed that includes fees, refunds, and payouts
async function ingestStripeTransactions(startDate, endDate) {
const transactions = [];
for await (const txn of stripe.balanceTransactions.list({
created: {
gte: Math.floor(new Date(startDate).getTime() / 1000),
lte: Math.floor(new Date(endDate).getTime() / 1000),
},
limit: 100,
expand: ['data.source'],
})) {
const orderId = txn.source?.metadata?.order_id;
transactions.push({
stripe_id: txn.id,
type: txn.type, // 'charge', 'refund', 'payout', 'stripe_fee'
gross: txn.amount / 100, // in dollars
fee: txn.fee / 100,
net: txn.net / 100,
currency: txn.currency.toUpperCase(),
date: new Date(txn.created * 1000),
order_id: orderId ?? null,
description: txn.description,
});
}
return transactions;
}
// Match Stripe transactions to internal order records
async function reconcileDay(date) {
const stripeTxns = await ingestStripeTransactions(date, date);
const internalOrders = await db.orders.findMany({
where: { createdAt: { gte: startOfDay(date), lte: endOfDay(date) }, status: 'confirmed' },
});
const matched = [];
const exceptions = [];
for (const stripeTxn of stripeTxns.filter(t => t.type === 'charge')) {
const order = internalOrders.find(o => o.id === stripeTxn.order_id || o.stripeChargeId === stripeTxn.stripe_id);
if (order && Math.abs(order.total - stripeTxn.gross) < 0.01) {
matched.push({ stripeTxn, order, delta: 0 });
} else {
exceptions.push({ stripeTxn, order: order ?? null, delta: order ? order.total - stripeTxn.gross : stripeTxn.gross });
}
}
// Alert on exceptions
if (exceptions.length > 0) {
await sendSlackAlert(`Reconciliation: ${exceptions.length} unmatched transactions on ${date}`);
}
return { matched: matched.length, exceptions: exceptions.length };
}PayPal reconciliation: Use the PayPal Reporting API (/v1/reporting/transactions) to fetch settled transactions. Filter to transaction_status: 'S' (settled only) to avoid matching in-flight authorizations.
Step 3: Handle the most common reconciliation edge cases
Stripe payouts do not equal sum of charges: This is expected — payouts are net of fees, refunds, and rolling reserve. In A2X and Synder, the payout amount is reconciled against the bank deposit; individual charges are recorded separately at their gross amount with fees as expenses.
Refunds issued in a different month: A2X and Synder handle this automatically — refunds are recorded in the period they were issued, not the period of the original sale. Configure your accounting system to use accrual accounting so refunds reduce the relevant revenue period.
Chargebacks: Both Stripe and Shopify Payments automatically create a balance transaction for the chargeback amount plus the chargeback fee. A2X and Synder record these as negative transactions. Verify your chart of accounts has a "Chargebacks" expense account for the fees.
Multi-currency transactions: If you process in EUR and your books are in USD, configure A2X or Synder to use the exchange rate at transaction time for recording (not the rate at payout time). This matches the accrual accounting principle.
Step 4: Set up daily reconciliation monitoring
Configure a daily report in QuickBooks or Xero that shows: 1. Deposits in bank vs. Payments received in accounting: these should match within $0.01 2. Unmatched transactions: any transaction in your accounting system not matched to a bank entry 3. Exception rate: aim for less than 1% unmatched transactions per day
In QuickBooks: use Reports → Banking → Reconciliation Reports In Xero: use Accounting → Bank Accounts → Reconciliation Reports
Best Practices
- Use A2X or Synder before building custom — for Shopify, WooCommerce, and BigCommerce stores these tools solve reconciliation in a day; custom code adds months of maintenance
- Use Stripe balance transactions as the source of truth — not charges or payment intents; balance transactions are the only feed that includes fees, refunds, and payouts in a single consistent record
- Reconcile T-1 (yesterday), not same day — most processors finalize settlement 24 hours after the transaction; same-day reconciliation produces false exceptions for in-flight authorizations
- Never auto-resolve exceptions — all exception resolutions must have a human approval step and leave an audit trail; configure A2X and Synder to flag rather than auto-post exceptions
- Reconcile fees separately — processor fees are often charged in aggregate on a payout; verify your fee rate against your merchant agreement quarterly
Common Pitfalls
| Problem | Solution |
|---|---|
| Shopify payout doesn't match bank deposit | A2X automatically maps payout net amount to the bank deposit; verify A2X is connected and payout processing is enabled |
| Stripe refunds creating duplicate accounting entries | Synder and A2X handle refunds as debit entries against the original sale; disable any manual refund entries you may have created |
| PayPal transactions not reconciling | PayPal settlements can take 2–5 business days; filter PayPal ingestion to transaction_status: S (settled) only |
| Currency mismatch causing false exceptions | Configure your sync tool to record transactions in their original currency; use your accounting system's built-in currency conversion at the transaction date rate |
| Month-end delta growing over time | Run a monthly roll-up in your accounting system to identify all unmatched items older than 30 days and resolve them before close |
Related Skills
- @stripe-integration
- @paypal-integration
- @tax-compliance-automation
- @accounts-receivable-automation
- @payout-split-management
{
"context": "Tests whether the agent designs the reconciliation schema with the correct table structure (canonical transactions table, matches table with confidence score and audit fields), creates proper indexes, implements the correct alert thresholds, sends alerts to the right Slack channel, and separates fee reconciliation as a distinct concern.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Canonical transactions table",
"max_score": 10,
"description": "schema.sql includes a transactions table with at minimum these columns: source, source_transaction_id, source_reference, transaction_type, gross_amount, fee_amount, net_amount, currency, transaction_date, settlement_date, status"
},
{
"name": "Matches table with confidence score",
"max_score": 8,
"description": "schema.sql includes a matches/reconciliation table with a confidence_score column (numeric, representing 0 to 1 range)"
},
{
"name": "Audit trail columns",
"max_score": 10,
"description": "The matches table includes both resolved_by (who resolved) and resolved_at (when resolved) columns for audit trail purposes"
},
{
"name": "Match type and status columns",
"max_score": 8,
"description": "The matches table includes separate match_type (e.g. exact, fuzzy, partial, manual) and match_status (e.g. matched, exception, under_review, resolved) columns"
},
{
"name": "Performance indexes",
"max_score": 8,
"description": "schema.sql creates at least 2 indexes: one on (source, transaction_date) or similar composite, and one on match_status or similar query-pattern column"
},
{
"name": "Single exception threshold",
"max_score": 10,
"description": "alerting.js alerts immediately for individual exceptions exceeding $100 (or a threshold in the $100 range, not $10 or $1000)"
},
{
"name": "Daily exception rate threshold",
"max_score": 10,
"description": "alerting.js triggers an alert when the daily exception rate exceeds 2% (0.02) of total transactions"
},
{
"name": "Daily net delta threshold",
"max_score": 8,
"description": "alerting.js triggers an alert when total unmatched amount exceeds $500 per day"
},
{
"name": "Slack #finance-alerts channel",
"max_score": 10,
"description": "alerting.js sends Slack notifications specifically to the '#finance-alerts' channel (exact channel name)"
},
{
"name": "Fee reconciliation separation",
"max_score": 10,
"description": "SCHEMA_NOTES.md or schema.sql explicitly addresses fee reconciliation as a separate concern from transaction matching (e.g. separate table, separate process note, or explicit comment explaining fees are aggregated per payout)"
},
{
"name": "Unique constraint on source+ID",
"max_score": 8,
"description": "schema.sql includes a UNIQUE constraint on (source, source_transaction_id) in the transactions table"
}
]
}
Payment Reconciliation Database Schema and Alerting System
Problem/Feature Description
Paylens, a fintech company processing payments for e-commerce merchants, is building a new payment reconciliation platform from scratch. Their CTO has approved the project and the first sprint covers two things: defining the database schema that will store all reconciliation data, and building the alerting system that notifies the finance team when discrepancies are detected.
The finance team has shared their requirements: they need the database to track not just whether a transaction was matched, but the quality of each match, who resolved any discrepancies, and when. For alerting, they want immediate notifications for large individual discrepancies, daily summary emails, and they want Slack alerts routed to the dedicated finance monitoring channel that their treasury team watches around the clock. The team is also aware that processor fees are billed differently from individual transactions (often as aggregated monthly charges), and wants the system designed to handle fee reconciliation as a distinct concern.
Output Specification
Produce the following files:
schema.sql— Complete SQL schema for the reconciliation databasealerting.js— The alerting module implementationSCHEMA_NOTES.md— Brief notes explaining the schema design decisions, including how match quality is tracked and how exception resolution is audited
The SQL schema should be self-contained and executable. The alerting code should reference environment variables for credentials but include all logic inline.
{
"context": "Tests whether the agent implements the matching engine with the correct two-strategy approach (reference ID first, then amount+date), uses fixed dollar tolerances rather than percentage tolerances, applies the correct tolerance values, uses a ±2-day date window for fallback matching, handles four match types, and never auto-resolves exceptions.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Reference ID first strategy",
"max_score": 12,
"description": "The matcher attempts to match on reference/order ID as the primary strategy before falling back to amount-based matching"
},
{
"name": "Amount fallback strategy",
"max_score": 8,
"description": "The matcher includes a fallback strategy that matches on amount (within a tolerance) when reference ID matching fails"
},
{
"name": "Fixed dollar tolerances",
"max_score": 15,
"description": "Amount tolerances are expressed as fixed dollar amounts (e.g. 0.005 and 1.00), NOT as percentages of the transaction amount"
},
{
"name": "Exact match tolerance value",
"max_score": 10,
"description": "The exact match tolerance is $0.005 (half a cent) for floating-point rounding"
},
{
"name": "Fuzzy match tolerance value",
"max_score": 10,
"description": "The fuzzy/loose match tolerance is $1.00 for known processor rounding differences"
},
{
"name": "Date window for fallback",
"max_score": 10,
"description": "The amount-based fallback matching uses a ±2 day date window around the transaction date"
},
{
"name": "No auto-resolve exceptions",
"max_score": 15,
"description": "Unmatched transactions are flagged as exceptions but NOT automatically resolved or closed — DESIGN.md or code comments explicitly state that exceptions require human approval"
},
{
"name": "Match type classification",
"max_score": 10,
"description": "The engine classifies matches into distinct types (at minimum exact and fuzzy/partial), based on the delta between matched amounts"
},
{
"name": "Design doc explains tolerance rationale",
"max_score": 10,
"description": "DESIGN.md explains WHY fixed dollar tolerances are used rather than percentage tolerances (e.g. mentions scaling issues with large transactions)"
}
]
}
Payment Transaction Matching Engine
Problem/Feature Description
Clearbook, an accounting automation startup, has built a data pipeline that pulls transactions from Stripe and PayPal into a normalized database alongside their internal order records. Now they need to build the matching engine — the core component that compares internal order records against external processor records and determines which ones correspond to each other.
The matching engine needs to handle realistic payment scenarios: most payments will have clean reference IDs to match against, but some processors strip or mangle order references, and settlement timing varies such that a payment captured on one day may not appear in the processor feed until two days later. Additionally, some transactions will have no match at all and need to be flagged for human review — but the finance team is insistent that no flagged exceptions be automatically closed or resolved by the system itself, since this creates audit compliance issues.
Output Specification
Implement the matching engine as a JavaScript module and write a brief design document:
matcher.js— The matching engine implementationmatcher.test.js— Unit tests demonstrating the matching logic with at least 3 test cases covering different matching scenariosDESIGN.md— A short document (can be bullet points) explaining the matching strategy, the tolerance values chosen for amount comparison, and the exception handling policy
The code should be self-contained and not require a live database connection to understand — use comments or mock data where needed.
{
"context": "Tests whether the agent builds the Stripe and PayPal ingesters correctly, using the right Stripe API (balance transactions), filtering PayPal to settled-only, implementing idempotent upserts, mapping transaction types correctly, ingesting T-1 data, and running ingesters in parallel.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Stripe balance transactions API",
"max_score": 15,
"description": "The Stripe ingester calls stripe.balanceTransactions.list() (or equivalent balance transactions endpoint), NOT stripe.charges.list(), stripe.paymentIntents.list(), or other endpoints"
},
{
"name": "PayPal settled-only filter",
"max_score": 12,
"description": "The PayPal ingester filters to only settled transactions (e.g. transaction_status: 'S' or equivalent settled status parameter), not all transaction statuses"
},
{
"name": "Upsert on composite key",
"max_score": 12,
"description": "Both ingesters use an upsert/insert-or-update operation (not a plain insert) keyed on (source, source_transaction_id) to prevent duplicates on re-runs"
},
{
"name": "T-1 date selection",
"max_score": 12,
"description": "The orchestration job targets yesterday's date (T-1), not today's date, as the ingestion window for both processors"
},
{
"name": "Parallel ingestion",
"max_score": 10,
"description": "The orchestration script runs Stripe and PayPal ingestion concurrently (e.g. Promise.all or equivalent), not sequentially"
},
{
"name": "Stripe transaction type mapping",
"max_score": 10,
"description": "The Stripe ingester maps Stripe-specific types (e.g. 'dispute' → 'chargeback', 'reserve_transaction' → 'reserve_release', 'stripe_fee' → 'fee') to normalized internal types"
},
{
"name": "PayPal transaction type mapping",
"max_score": 8,
"description": "The PayPal ingester maps PayPal event codes to normalized types (e.g. T00x → 'charge', T11x → 'refund', T12x → 'chargeback', T20x → 'payout')"
},
{
"name": "Gross/fee/net amounts stored",
"max_score": 8,
"description": "Both ingesters store gross_amount, fee_amount, and net_amount separately (not just a single amount field)"
},
{
"name": "Implementation notes explains Stripe choice",
"max_score": 8,
"description": "IMPLEMENTATION_NOTES.md explicitly states WHY balance transactions were chosen over charges/payment intents (e.g. mentions fees, refunds, payouts in same feed, or authoritative record)"
},
{
"name": "No currency conversion",
"max_score": 5,
"description": "Neither ingester performs currency conversion — amounts are stored in the transaction's original currency"
}
]
}
Multi-Processor Transaction Ingestion Service
Problem/Feature Description
FinFlow, a SaaS billing platform processing $2M/month in payments, accepts payments through both Stripe and PayPal. Their finance team currently exports CSV files from each processor manually every morning, but this process is fragile and sometimes pulls incomplete data because transactions from the previous evening haven't fully settled yet. Additionally, when the team re-runs exports to fix missing data, they accidentally create duplicate records in their accounting system.
The engineering team has been asked to build an automated ingestion service that reliably pulls transaction data from both Stripe and PayPal and stores it in a normalized internal database. The service must be safe to re-run multiple times without creating duplicates, and should only pull data that is fully settled to avoid false exception alerts later in the reconciliation pipeline.
Output Specification
Write the ingestion service as Node.js/JavaScript source files. Produce the following:
ingesters/stripe.js— Stripe transaction ingesteringesters/paypal.js— PayPal transaction ingesterjobs/daily-ingest.js— Orchestration script that runs both ingesters for the previous day's dataIMPLEMENTATION_NOTES.md— A brief document explaining key design decisions made in the implementation, including which Stripe API endpoints were used and why, how duplicate prevention works, and how the date range for ingestion is determined
The code should reference environment variables for credentials (STRIPE_SECRET_KEY, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_API_BASE) but does not need to actually connect to live APIs.
{
"name": "finsi/payment-reconciliation-automation",
"version": "0.1.0",
"summary": "Automate payment reconciliation across Stripe, PayPal, and bank accounts with exception handling, automated matching rules, and discrepancy alerting",
"skills": {
"payment-reconciliation-automation": {
"path": "SKILL.md"
}
}
}