Candidate: Ajala Oluwaferanmi Ayokusibe

Assessment: Lendsqr Backend Engineering Assessment V2

Live API: https://ajala-feranmi-lendsqr-be-test.onrender.com/api

Swagger Docs: https://ajala-feranmi-lendsqr-be-test.onrender.com/api-docs/

GitHub: https://github.com/Eranmonnie/lendsqr-assessment

Loom Video: https://www.loom.com/share/2870f2c512b34bdd949069ec371cc27a


Overview

This document explains the reasoning behind the key decisions I made while building the Demo Credit wallet service. It is not a description of what was built — the README covers that — but rather an account of why I built it the way I did, where I went beyond the requirements, and what I would do differently with more time.


Going Beyond the Assessment Requirements

The assessment explicitly said a faux token-based authentication would suffice. I chose to implement proper JWT authentication instead.

My reasoning was that faux auth would have meant hardcoding a user identity into headers or skipping auth middleware entirely. This would have made it impossible to properly scope wallet operations, transaction history, and PIN validation to the authenticated user, all of which are central to a wallet service. JWT was the minimum viable auth that made the rest of the system coherent.

Similarly, the assessment did not require Paystack integration. just fund, transfer, and withdraw endpoints. I integrated Paystack because wallet funding without a real payment provider is essentially just a balance update with no real-world trigger, and withdrawal without a payout mechanism has no practical meaning. Integrating Paystack made these flows testable end-to-end and closer to what a real lending product would require.

I also added a PAYSTACK_MODE=mock configuration so the app runs fully locally without needing a live Paystack account, which I felt was important for reviewers trying to run the project.


Why a Ledger Model Instead of Just Updating a Balance

The simplest implementation of wallet balance would be a single balance column on the wallets table — just increment or decrement on every operation.

I rejected this because it produces no audit trail. If a balance is wrong, there is no way to reconstruct how it got there. I mentally needed to be able to answer "show me every operation that touched this balance" — which a single column cannot do.

Instead I separated the model into two concerns: transactions represent business events (a user initiated a transfer), and ledger_entries represent the actual balance mutations that resulted from that event. Each ledger entry is append-only and immutable, recording the delta and the resulting balance at that point in time.