[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build a Payment Reconciliation Engine

Build a Payment Reconciliation Engine

Build a payment reconciliation engine with transaction matching, discrepancy detection, multi-source comparison, automated resolution, and audit reporting for financial operations.

Development#typescript#tsconfig#type-checking#type-safety#javascript-migration
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 96/100·All SAFE
>

typescript

v1.0.0

Sets up and repairs TypeScript projects: picks tsconfig.json settings for a Node.js service, an npm library or a bundled web app, runs type-checking in CI, and replaces `any` and unsafe casts with types the compiler can verify. Use when someone asks to "set up TypeScript", "fix this tsconfig", "fix a TypeScript error", "upgrade to TypeScript 6 or 7", "migrate JavaScript to TypeScript", "publish types for a package", "run .ts files in Node" or "make this type-safe". Covers the changed defaults in TypeScript 6.0 and 7.0, module and moduleResolution, strict mode, project references, declaration output, narrowing, satisfies and common compiler errors.

93/100 quality
7.30× impact
SAFE· stale score
View skill
>

redis

v1.1.0

Redis is an in-memory data store used as a cache, session store, message broker and counter service. Use when adding a cache layer, storing sessions, rate limiting an API, running pub/sub or Streams with consumer groups, taking a distributed lock, building a leaderboard, or configuring Redis for production (persistence, maxmemory, eviction).

93/100 quality
1.79× impact
SAFE· stale score
View skill
>

postgresql

v1.1.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

100/100 quality
5.15× impact
SAFE· stale score
View skill
>

hono

v1.1.0

Hono is a small web framework built on Web Standards (Request/Response) that runs unchanged on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda and Vercel. Use it when the user asks to build a Hono API, add Zod validation, JWT auth, CORS or middleware, use the type-safe RPC client (hc), render JSX, stream responses, or deploy one codebase to several runtimes.

100/100 quality
6.20× impact
SAFE· stale score
View skill
>

zod

v1.1.0

Zod is a TypeScript-first schema library that validates data at runtime and infers static types from the same schema. Use when validating API request bodies, form input, environment variables or config files, parsing untrusted JSON, or migrating from Zod 3 to Zod 4. Covers z.object, safeParse, z.infer, coercion, discriminated unions, refinements, JSON Schema output and the v4 API changes.

93/100 quality
3.87× impact
SAFE· stale score
View skill
$

The Problem

Pavel leads finance at a 25-person company processing $2M/month through Stripe, PayPal, and bank transfers. Monthly reconciliation — matching payments in their system with bank statements and payment processor reports — takes the finance team 3 days. Discrepancies (partial payments, refunds, FX differences, processing fees) require manual investigation. Last quarter, $15K in unreconciled transactions caused an audit flag. They need automated reconciliation: import transactions from multiple sources, match them algorithmically, detect discrepancies, suggest resolutions, and generate audit-ready reports.

Step 1: Build the Reconciliation Engine

typescript
import { pool } from "../db";
import { Redis } from "ioredis";
import { randomBytes } from "node:crypto";
const redis = new Redis(process.env.REDIS_URL!);

interface Transaction {
  id: string;
  source: "internal" | "stripe" | "paypal" | "bank";
  externalId: string;
  amount: number;
  currency: string;
  type: "charge" | "refund" | "payout" | "fee" | "adjustment";
  customerId: string | null;
  description: string;
  date: string;
  metadata: Record<string, any>;
}

interface ReconciliationMatch {
  id: string;
  internalTx: Transaction;
  externalTx: Transaction;
  status: "matched" | "partial" | "discrepancy" | "unmatched";
  discrepancyAmount: number;
  discrepancyReason: string | null;
  resolution: string | null;
  resolvedBy: string | null;
}

interface ReconciliationReport {
  period: string;
  totalInternal: number;
  totalExternal: number;
  matched: number;
  partialMatches: number;
  discrepancies: number;
  unmatched: number;
  totalDiscrepancyAmount: number;
  matches: ReconciliationMatch[];
}

export async function reconcile(period: string, sources: string[]): Promise<ReconciliationReport> {
  const [year, month] = period.split("-").map(Number);
  const startDate = new Date(year, month - 1, 1).toISOString();
  const endDate = new Date(year, month, 0).toISOString();

  // Load transactions from all sources
  const internal = await loadTransactions("internal", startDate, endDate);
  const external: Transaction[] = [];
  for (const source of sources) {
    external.push(...await loadTransactions(source as any, startDate, endDate));
  }

  const matches: ReconciliationMatch[] = [];
  const matchedExternal = new Set<string>();
  const matchedInternal = new Set<string>();

  // Pass 1: Exact matches (same external ID)
  for (const intTx of internal) {
    const extMatch = external.find((e) => !matchedExternal.has(e.id) && (e.externalId === intTx.externalId || intTx.externalId === e.externalId));
    if (extMatch) {
      const discrepancy = Math.abs(intTx.amount - extMatch.amount);
      matches.push({
        id: randomBytes(6).toString("hex"),
        internalTx: intTx, externalTx: extMatch,
        status: discrepancy < 0.01 ? "matched" : discrepancy < intTx.amount * 0.05 ? "partial" : "discrepancy",
        discrepancyAmount: Math.round(discrepancy * 100) / 100,
        discrepancyReason: discrepancy > 0.01 ? detectDiscrepancyReason(intTx, extMatch) : null,
        resolution: null, resolvedBy: null,
      });
      matchedExternal.add(extMatch.id);
      matchedInternal.add(intTx.id);
    }
  }

  // Pass 2: Fuzzy matches (same amount + date range + customer)
  for (const intTx of internal) {
    if (matchedInternal.has(intTx.id)) continue;
    const candidates = external.filter((e) => !matchedExternal.has(e.id) && Math.abs(e.amount - intTx.amount) < 0.5 && Math.abs(new Date(e.date).getTime() - new Date(intTx.date).getTime()) < 3 * 86400000);
    if (candidates.length === 1) {
      const extMatch = candidates[0];
      matches.push({
        id: randomBytes(6).toString("hex"),
        internalTx: intTx, externalTx: extMatch,
        status: "partial",
        discrepancyAmount: Math.round(Math.abs(intTx.amount - extMatch.amount) * 100) / 100,
        discrepancyReason: "Fuzzy match — verify manually",
        resolution: null, resolvedBy: null,
      });
      matchedExternal.add(extMatch.id);
      matchedInternal.add(intTx.id);
    }
  }

  // Unmatched transactions
  for (const intTx of internal) {
    if (!matchedInternal.has(intTx.id)) {
      matches.push({ id: randomBytes(6).toString("hex"), internalTx: intTx, externalTx: intTx, status: "unmatched", discrepancyAmount: intTx.amount, discrepancyReason: "No matching external transaction", resolution: null, resolvedBy: null });
    }
  }

  const report: ReconciliationReport = {
    period,
    totalInternal: internal.length,
    totalExternal: external.length,
    matched: matches.filter((m) => m.status === "matched").length,
    partialMatches: matches.filter((m) => m.status === "partial").length,
    discrepancies: matches.filter((m) => m.status === "discrepancy").length,
    unmatched: matches.filter((m) => m.status === "unmatched").length,
    totalDiscrepancyAmount: matches.reduce((s, m) => s + m.discrepancyAmount, 0),
    matches,
  };

  await pool.query(
    `INSERT INTO reconciliation_reports (period, total_internal, total_external, matched, discrepancies, unmatched, total_discrepancy, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
    [period, report.totalInternal, report.totalExternal, report.matched, report.discrepancies, report.unmatched, report.totalDiscrepancyAmount]
  );

  return report;
}

function detectDiscrepancyReason(internal: Transaction, external: Transaction): string {
  const diff = Math.abs(internal.amount - external.amount);
  if (diff < internal.amount * 0.03) return "Processing fee difference";
  if (internal.currency !== external.currency) return "Currency conversion difference";
  if (external.type === "refund") return "Partial refund";
  return "Amount mismatch — investigate";
}

async function loadTransactions(source: string, startDate: string, endDate: string): Promise<Transaction[]> {
  const { rows } = await pool.query(
    "SELECT * FROM transactions WHERE source = $1 AND date BETWEEN $2 AND $3 ORDER BY date",
    [source, startDate, endDate]
  );
  return rows.map((r: any) => ({ ...r, metadata: JSON.parse(r.metadata || "{}") }));
}

export async function resolveDiscrepancy(matchId: string, resolution: string, resolvedBy: string): Promise<void> {
  await pool.query(
    "UPDATE reconciliation_matches SET resolution = $2, resolved_by = $3, resolved_at = NOW() WHERE id = $1",
    [matchId, resolution, resolvedBy]
  );
}

Results

  • Reconciliation: 3 days → 2 hours — automated matching handles 95% of transactions; finance reviews only discrepancies and unmatched items
  • $15K audit flag resolved — all discrepancies tracked with reasons; "processing fee" vs "FX difference" vs "partial refund" categorized; audit passed
  • Two-pass matching — exact ID match catches 85%; fuzzy match (amount + date + customer) catches another 10%; only 5% truly unmatched
  • Discrepancy reasons auto-detected — $2.50 difference on $100 charge = processing fee; different currencies = FX; finance doesn't investigate obvious causes
  • Multi-source support — Stripe + PayPal + bank statements all imported; cross-matched in single reconciliation run; unified view across payment methods

Related use cases