Chartered Accountant CRM — Multi-Role Practice Management
Independently architected the entire frontend and ~70% of the backend for a multi-role CRM serving a Chartered Accountant practice, with role-based access control and an end-to-end Razorpay payment pipeline.
Contents
Executive Summary
A multi-role CRM built for a Chartered Accountant practice, supporting Admin, Relationship Manager, and Client roles across onboarding, document processing, filings, and workflow tracking. Independently architected and built the entire frontend, and owned roughly 70% of the backend — designing the relational schema, implementing JWT authentication and role-based access control with NestJS Guards, and structuring validation layers around every request. Also integrated Razorpay end to end, from order creation through webhook-verified payment confirmation and reconciliation, so payment status in the CRM could be trusted rather than assumed.
Business Context
Constraints
Three-role permission surface
Admin, RM, and Client roles each needed different CRM access. UI restrictions and protected routes had to stay aligned with backend RBAC policies rather than becoming a second, independently-drifting source of truth.
Payment integrity
Razorpay order creation, verification, and webhook handling had to be trustworthy end to end — payment credentials and verification signatures needed secure handling to prevent fraud, not just a successful-looking client redirect.
Partial backend ownership
Owning ~70% of the backend meant schema and API decisions had to stay consistent with the remaining 30% built by other engineers, without a single owner to unilaterally resolve every design question.
Data integrity across workflow states
Clients, filings, and workflow states moved through multiple stages. Validation layers and DTO pipelines had to hold data integrity at every request, not just on initial creation.
System Blueprint
The frontend talks to NestJS REST APIs guarded by JWT auth and role checks, which read and write a relational schema covering clients, filings, and workflow state, with a separate Razorpay integration path for order creation, verification, and reconciliation.
Frontend
React.js application with Redux-managed global state, rendering role-scoped views for Admin, RM, and Client users and enforcing protected routes aligned with backend RBAC.
API Layer
NestJS REST APIs guarded by JWT authentication and NestJS Guards for RBAC, with DTO validation pipelines on every request.
Data Layer
Relational schema for clients, filings, workflow states, and interactions, managed through TypeORM, plus invoice and transaction records for reconciliation.
Payments
Razorpay integration handling order creation, payment verification, and webhook-based confirmation, kept in sync with internal CRM payment records.
Architecture
Frontend ownership across three roles
The entire frontend was architected and built independently, with role-based UI restrictions and protected routes for Admin, RM, and Client roles kept aligned with backend RBAC policies rather than treated as a separate permission model.
Backend RBAC as the enforcement layer
Access control is enforced by NestJS Guards on the backend, with JWT authentication securing every request. Frontend role restrictions shape what a user sees, but the Guards are what actually decide what a request is allowed to do.
Relational schema for CRM workflow state
Clients, filings, and workflow states are modeled relationally in SQL via TypeORM, with validation layers and DTO pipelines enforcing data integrity as records move through onboarding, document processing, and filing stages.
Razorpay payment pipeline
Payments are confirmed through order creation, webhook handling, and signature verification rather than a client-reported success state, with dedicated database structures tracking invoices, transaction history, and reconciliation.
Engineering Decisions
Trusting a client redirect vs. verifying payment via webhook
- Mark a payment as complete based on the client-side redirect after Razorpay checkout.
- Confirm payment only after Razorpay's webhook fires, with the payment signature verified server-side.
Where role restrictions are the source of truth: UI or backend
- Restrict access primarily through frontend route guards and conditional rendering.
- Enforce access control at the backend with NestJS Guards, and treat frontend restrictions as UX rather than security.
Production Stories
This section is planned but not yet documented.
Lessons Learned
Frontend role restrictions are a UX layer, not a security boundary — the backend Guard is what actually has to hold.
Verifying payments through a webhook and signature check, instead of trusting a client redirect, is the difference between a payment record you can trust and one that's a guess.
Owning a majority share of a shared backend still means designing schema and API changes so the remaining owners can build against them without renegotiating the model each time.
Related Notes
- Don't Trust the Redirect: Verifying Payments Server-Side
A successful client-side redirect after checkout isn't proof a payment cleared — why payment status should only ever change after a verified webhook.