Project Metadata
Overview of the project context, scope, and tech choices.
Overview
- Role
- Full Stack Developer
- Client
- Confidential (Banking Sector)
- Type
- Enterprise Web Application
- Industry
- Financial Technology (FinTech)
- Platform
- Web
- Duration
- -
Description
A digital lending application for banking tender requirements, featuring a modular KYC onboarding system with customer validation, document verification, and integration with external services.
Key Features
- • KYC Customer Onboarding Flow
- • Document Verification & Validation
- • External Service Integration
- • Multi-step Registration Form
- • Reusable Frontend Components
- • Modular Backend Services
Tech Stack
Unlike personal projects where I had full autonomy over every decision, this was my first real experience working in a genuine enterprise context, with strict business constraints, non-negotiable security standards, and a cross-functional team that had to collaborate effectively under real delivery pressure.
The platform was developed to fulfill requirements for a banking tender, where the digital lending system had to comply with regulations set by financial authorities. Every technical decision, from how customer data is stored to how documents are verified, had to be grounded in compliance, not just convenience.
The Complexity of KYC in a Banking Context
Know Your Customer (KYC) is far more than a registration form. It’s a layered process designed to ensure that:
- A customer’s identity can be legally verified
- Supporting documents (national ID, tax ID, etc.) are valid and unmanipulated
- Collected data is consistent and complete before proceeding to the next stage
- The entire process produces an audit trail that can be accounted for
This complexity creates an interesting technical challenge: how do you build a multi-step flow that feels simple to the end customer, while running layered validation and integrations under the hood?
Multi-Service Architecture
The platform was built with a modular, multi-service architecture, with each functional domain (onboarding, document verification, customer management) operating as an independent service with a well-defined API contract.
I contributed across two primary areas:
Frontend (Next.js): Building a wizard-style onboarding flow that guides customers through each KYC stage linearly, with real-time validation at every step before allowing a transition to the next.
Backend (Spring Boot): Developing the service layer for the onboarding process, including integration with external identity verification services.
Onboarding Flow: Step 1: Personal Information → Format & completeness validation Step 2: Document Upload → Verification via external service Step 3: Review & Submit → Final validation + persistence Step 4: Confirmation → Audit log entry + notificationFrontend: Building a Reusable Component System
One of the most impactful technical contributions I made on this project was establishing a reusable component system for all user input flows.
Early in the project, each onboarding page was built ad hoc with inconsistent components. The customer form in step 1 behaved differently from the form in step 2, even though they looked visually similar to the user. I proposed and implemented a component abstraction that standardized:
FormField: Input wrapper with consistent label, error state, and helper text behaviorStepContainer: Standard layout for each step with navigation controlsDocumentUploader: Upload component with preview, file type validation, and progress feedback
The result: building a new onboarding step that previously took 2–3 days could be done in half a day because all the building blocks were already in place.
Managing Complex Form State
KYC forms come with non-trivial state conditions: certain fields appear or hide based on choices in other fields (conditional fields), some fields depend on data fetched from an API (dynamic options), and all state must persist when the user navigates back to a previous step.
I used Formik as the form state management solution, with Yup as the schema validator. This combination provides a declarative approach to form handling and validation while keeping validation rules centralized and maintainable:
const step1Schema = Yup.object({ nationalId: Yup.string() .length(16, 'National ID must be exactly 16 digits') .matches(/^\d+$/, 'National ID must contain only numbers') .required('National ID is required'), fullName: Yup.string() .min(2, 'Full name must be at least 2 characters') .max(100, 'Full name must be at most 100 characters') .required('Full name is required'), dateOfBirth: Yup.date() .max(subYears(new Date(), 17), 'Applicant must be at least 17 years old') .required('Date of birth is required'), occupation: Yup.string() .oneOf(['employed', 'self_employed', 'student', 'other']) .required('Occupation is required'), // conditional fields based on occupation value...});Cross-step state is managed at the parent component level and passed down via props, ensuring data isn’t lost as users navigate between steps.
Integrating External Verification Services
The most technically complex part was integration with an external identity verification service. This service had characteristics that made it difficult to work with: inconsistent response times (anywhere from 2 to 15 seconds) and non-standard error formats.
To handle this, I implemented:
- Timeout with retry logic: If the first request times out, it automatically retries up to N times with exponential backoff
- Error normalization: All external error formats are mapped to a standardized internal format before being passed to the frontend
- Async polling: For long-running verifications, the frontend polls a status endpoint rather than waiting on a synchronous response
Working Agile in an Enterprise Environment
This was the first project I worked on with a real Agile process: two-week sprints, daily standups, sprint reviews with stakeholders, and regular retrospectives.
The experience taught something that no tutorial can: a feature that’s “done” in sprint one can have its requirements change in sprint three due to QA feedback or shifting client requirements. The ability to modify an implementation without breaking what surrounds it depends entirely on how modular the code was written from the start.
This project solidified my conviction that clean architecture isn’t a preference, it’s a prerequisite for software that needs to live, evolve, and be maintained under real-world conditions.
Client details and specific implementation details are confidential per NDA agreement.
