Skip to content
Back to skills

Payment Reconciliation

ASecurity

Step-by-step playbook for reconciling your internal double-entry ledger against the PSP's payout and transaction reports — identifying discrepancies, classifying them, and closing the books with confidence.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentsgosqlapi

Works with

  • api

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill payment-reconciliation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Payment Reconciliation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Payment Reconciliation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-payment-reconciliation/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-payment-reconciliation)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: payment-reconciliation
description: "Step-by-step playbook for reconciling your internal double-entry ledger against the PSP's payout and transaction reports — identifying discrepancies, classifying them, and closing the books with confidence."
---

# Payment Reconciliation

## When to invoke

Use when building the reconciliation job for a new payments integration, investigating a balance discrepancy, or auditing an existing reconciliation process for gaps.

## Concepts

| Term | Meaning |
|---|---|
| PSP report | CSV/API export of all transactions, fees, refunds, and disputes settled in a period |
| Internal ledger | Your double-entry record of every money movement (credit/debit entries) |
| Settlement | When the PSP actually transfers funds to your bank account |
| Payout | A single bank transfer from PSP; may aggregate many transactions |
| Disputed amount | Chargebacks currently open; may not yet be settled/reversed |

**The goal:** every PSP transaction maps 1:1 to a ledger entry, every payout maps to a bank credit, and the sum of fees matches the PSP's fee report.

## Step 1 — Data collection

Pull two sources daily (or per-payout cycle):

1. **PSP transaction report** — via API (preferred) or SFTP CSV export:
   - Stripe: `GET /v1/balance/history?limit=100&created[gte]=...`
   - Each record: `{id, type, amount, fee, net, currency, created, available_on, description}`

2. **Internal ledger entries** for the same period:
   ```sql
   SELECT
     ledger_entry_id,
     external_transaction_id,   -- PSP charge/refund ID
     amount_minor_units,
     currency,
     entry_type,                -- charge | refund | fee | payout
     created_at
   FROM ledger_entries
   WHERE created_at BETWEEN :start AND :end
   ORDER BY created_at;
   ```

## Step 2 — Matching algorithm

Match on `external_transaction_id` (PSP id = your ledger's `external_transaction_id`):

```
for each psp_record in psp_report:
    ledger_match = find ledger_entry WHERE external_transaction_id = psp_record.id

    if no match:
        flag UNMATCHED_PSP — money in PSP not in ledger
    elif ledger_match.amount != psp_record.net:
        flag AMOUNT_MISMATCH (delta = psp_record.net - ledger_match.amount)
    else:
        mark MATCHED
```

```
for each ledger_entry in ledger WHERE type in (charge, refund):
    if not matched:
        flag UNMATCHED_LEDGER — money in ledger not in PSP
```

## Step 3 — Discrepancy classification

| Code | Description | Typical cause | Resolution |
|---|---|---|---|
| `UNMATCHED_PSP` | PSP record has no ledger entry | Missed webhook; duplicate prevention over-fired | Replay the webhook or create compensating entry |
| `UNMATCHED_LEDGER` | Ledger entry has no PSP record | Phantom charge; ledger bug | Investigate and void/reverse the ledger entry |
| `AMOUNT_MISMATCH` | Amounts differ | Fee deducted at wrong point; currency conversion | Correct fee ledger entries |
| `TIMING_GAP` | Present in one source, absent in other at T | Timing window — PSP and webhook not yet synced | Re-run reconciliation after settlement lag |
| `DISPUTE_OPEN` | Chargeback open; PSP holds funds | Normal during dispute window | Track in a dispute register; reconcile on resolution |

## Step 4 — Fee reconciliation

PSP fees are deducted per transaction. Verify separately:

```
expected_fees = Σ (psp_report.fee for all records in period)
ledger_fees   = Σ (ledger_entries WHERE type = 'fee' AND period)

if abs(expected_fees - ledger_fees) > tolerance:
    flag FEE_RECONCILIATION_FAILURE
```

Tolerance: zero for integer minor-unit systems. Any delta is a real discrepancy.

## Step 5 — Payout reconciliation

```
for each bank_credit (from bank statement):
    psp_payout = find psp_payout WHERE payout_id = bank_credit.reference

    if no match: flag PAYOUT_UNMATCHED
    elif psp_payout.amount != bank_credit.amount: flag PAYOUT_AMOUNT_MISMATCH
```

Payouts aggregate transactions — verify the payout total equals the sum of its constituent transactions minus fees:

```
payout_total = Σ (psp_record.net WHERE payout_id = X)
```

## Step 6 — Reporting and alerting

Run reconciliation on a **scheduled job** (daily, after settlement window closes):

- Emit metrics: `matched_count`, `unmatched_psp_count`, `unmatched_ledger_count`, `discrepancy_amount_minor_units`.
- Alert on-call if `discrepancy_amount > 0` or `unmatched_count > 0`.
- Write a reconciliation report to an audit table — never overwrite, always append-only.
- Route `FEE_RECONCILIATION_FAILURE` and `PAYOUT_UNMATCHED` to finance for the accounting close.

## Pitfalls

- **Reconciling on created-at instead of settled/available-at** — transactions created near midnight may settle the next day; use `available_on` from the PSP for period boundaries.
- **Not handling refunds as negative charges** — a refund reverses a charge; both create ledger entries; matching only on charge IDs misses refund records.
- **Treating `TIMING_GAP` as an error** — PSP webhooks can arrive hours after the transaction; rerun after the settlement lag before flagging.
- **Discarding the discrepancy record after resolution** — always preserve the original mismatch and the resolution action for the audit trail.
- **Floating-point comparison of amounts** — store and compare in integer minor units; a float equality check on currency amounts fails due to rounding.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…