Stealth fintech startup
Payment Reconciliation Service
Engineering Assessment
You designed this before you coded it. It shows.
AI collaboration
You clearly used AI for first drafts and boilerplate. That is fine. The decisions that matter were yours: one adapter per provider, errors with real names, and duplicate protection in the database. AI does not make those calls on its own.
What you built
- Reconcile across 3 providerscomplete
- Flag mismatches livecomplete
- Same error handling everywhereone handler drops errorspartial
- Batches with retriescomplete
- No duplicate paymentscomplete
- API documentationmissing
Ownership
Your commits tell the story. The adapter interface came first, before any provider code. Error handling and duplicate checks were added, then reworked over several sessions. That is what owning a design looks like.
Review Summary
Putting each payment provider behind its own adapter was the best decision in this repo. Adding or swapping a provider now touches one file. The main thing to fix: your retry logic is written three times, with three different wait times. Pull it into one helper.
Build & Test
6 of 8 tests passed. The 2 failures are both in the batch tests.
Code Quality
- –Short names like txn and rcn in BatchProcessor.ts slow down reading. Spell them out.
- +Every service file is laid out the same way, so things are easy to find.
- +Types sit right next to the code that uses them.
- –An empty catch block at BatchProcessor.ts:92 hides errors during retries.
- +Named error types (GatewayTimeoutError, ReconciliationError) let a timeout be retried while a bad request fails fast.
- +Adapters pass the original error up instead of replacing it with a vague one.
- –No test covers a provider failing halfway through a batch.
- –Test data is copy-pasted into each test. A small helper would remove the repetition.
- +The core matching logic is well tested, including odd cases like zero-amount refunds.
Design Quality
- –BatchProcessor imports all three adapters directly, so it has to change every time a provider is added.
- +Matching, provider code and mismatch checks live in separate folders and stay out of each other.
- +The engine talks to one GatewayAdapter interface, so it never needs to know which provider it is using.
- –The same retry loop is copied into StripeAdapter.ts:61, PayPalAdapter.ts:44 and SquareAdapter.ts:52.
- –Settings like timeouts are spread across files instead of one config module.
- +The adapter interface is small and clear: fetch, normalise, report.
- +A unique index on Transaction stops duplicate entries, even after a crash and restart.
- +Routes use the right HTTP methods and status codes.
- +Provider-specific fields are kept out of the shared transactions table.
- –DiscrepancyDetector passes callbacks through three functions. An event emitter would be simpler.
- +No extra layers. The design fits the size of the problem.
Feedback
Strengths
- Every provider follows the same GatewayAdapter interface. A fourth provider is one new file, not a rewrite.
- The unique index on your transactions table stops duplicates. If the service crashes halfway and restarts, nothing gets counted twice.
- Your errors have real names, like GatewayTimeoutError. So a timeout can be retried while a bad request fails straight away.
Areas for Improvement
- The retry loop lives in three places. Move it into one withRetry() helper so a fix lands everywhere at once.
- Your tests check each adapter on its own. None of them check what happens when a provider goes down in the middle of a batch.
- Timeouts are hardcoded in two adapters. Read them from config so you can change them without a redeploy.
Key Issues
src/adapters/StripeAdapter.ts:61, PayPalAdapter.ts:44, SquareAdapter.ts:52
→ Write one withRetry() helper that takes the wait time as a setting, and call it from each adapter.
Copied code drifts apart. Someone fixes a bug in one copy and the other two keep it.
tests/adapters/
→ Add a test where PayPal times out on item 40 of 100. Check that items 1 to 39 are saved and none are recorded twice.
This is the failure that actually happens with payment providers, so it is the first one worth testing.
src/handlers/BatchProcessor.ts:12-14
→ Pass the adapters in through the constructor. Then a test can hand it a fake provider.
This is called dependency injection. Mostly, it makes code easy to test.
All three providers run through one shared adapter interface.
Catches wrong amounts, missing payments and duplicates during every run.
The error types are well named. But one handler catches errors and drops them, so the caller never finds out.
Batching works. Each adapter retries on its own schedule instead of sharing one.
The database itself blocks duplicates, even when two runs overlap.
Another developer would have to read the code to know how to call this API.
- src/config/defaults.ts:12
The default timeout is hardcoded. Read it from an environment variable.
Code files
18
Avg lines per file
94.0
Longest file
312
Code in largest file
14%
Folder depth
4
Comment ratio
6%
