Old System vs New System — Stage 2 (OTP & Background Checks) & Stage 3 (Email Verification)

For each check/function: what it does, how old code did it, how new code does it • Collapsible sections

Stage 2 — OTP Verification & Background Check Pipeline

2.1 OTP Verification
Validate the 4-digit OTP submitted by the customer against the in-memory store. Enforce attempt limits, resend limits, and cooldown rules.
BRD / PRD Requirement PRD

BRD Stage 2 — OTP_VERIFIED: 4-digit OTP validated against in-memory store. Customer auto-submits on 4th digit entry.

  • Max 5 wrong attempts → lead state = DROPPED
  • Auto-submit on 4th digit (frontend behavior)
  • Resend max 3 times within 30 minutes
  • 30-second cooldown between resend requests
  • OTP never stored in database — in-memory only
Old System OLD
  • SP: RESUME_AUTHENTICATE_USER_OTP_PASS — validated OTP stored in database
  • SP: USP_VALIDATE_UPDATE_OTP_MOBILE_SJET — updated attempt counters
  • Called from: LoginRepository.AuthenticateLogin()
  • OTP Storage: OTP value stored in database (persisted)
  • Files: Joruney_imp_code\Registration\LoginRepository.cs, LoginController.cs
New System NEW
  • Service: OtpVerificationService.VerifyOtpAsync()
  • OTP Retrieval: InMemoryOtpStore.Get(lead.Mobile, "MOBILE")
  • Config: MaxWrongAttempts=5, MaxResendCount=3, MinResendIntervalSeconds=30
  • OTP Storage: OTP NEVER in database — ConcurrentDictionary in-memory only
  • Tracking: OtpVerification entity tracks attempt counts and resend counts (but not the OTP value itself)
  • File: Services/Stage2/OtpVerificationService.cs
Key Difference: Old system stored OTP in database and validated via stored procedure. New system stores OTP exclusively in-memory (ConcurrentDictionary) — OTP value never touches the database. Attempt tracking moved from SP to OtpVerification entity.
2.2 RM Resume OTP Bypass
When an RM resumes a journey from Zoho CRM, OTP verification is bypassed entirely. Lead transitions directly to OTP_VERIFIED.
BRD / PRD Requirement PRD

BRD — RM Resume: When RM resumes a dropped-off journey from Zoho, OTP is bypassed entirely. The lead state transitions directly to OTP_VERIFIED without any OTP exchange.

  • Applies only to RM-assisted Zoho resume (not franchise users)
  • Background checks still fire after bypass
Old System OLD
  • SP: USP_INSERT_UPDATE_MOBILE_EMAIL_OTP_BYPASS_SJET — franchise bypass mechanism
  • Table: TBL_MOBILE_EMAIL_OTP_BYPASS_DETAILS — ISMOBILEBYPASS / ISEMAILBYPASS flags
  • Scope: Generic franchise bypass, not RM-specific
New System NEW
  • Service: OtpVerificationService checks lead.IsRmResumeBypass
  • If true: Skips OTP validation, transitions to OTP_VERIFIED, fires background checks
  • Audit: State transition logged with TriggerAction = "RM_RESUME_BYPASS"
  • File: Services/Stage2/OtpVerificationService.cs
Key Difference: Old system had a generic franchise bypass via a dedicated bypass table. New system uses a boolean flag (IsRmResumeBypass) on the lead entity, checked inline by OtpVerificationService. Audit trail records the bypass action with a specific trigger.
2.3 Background Check Pipeline (Post-OTP Verified)
After OTP is verified, fire all background checks asynchronously. Customer proceeds to Stage 3 immediately without waiting.
BRD / PRD Requirement PRD

BRD — Background Pipeline: Ordered async pipeline: Zintlr → (Hyperverge + C-safe parallel) → (NSDL + CVL KRA parallel). UTI fallback only when NSDL is DOWN. All async — customer moves to Stage 3 immediately.

  • Step 1: Zintlr phone-to-PAN lookup (plain mobile)
  • Step 2: Hyperverge PAN details + C-safe AML check (parallel)
  • Step 3: NSDL PAN validation + CVL KRA status (parallel)
  • UTI is fallback for NSDL only — triggered when NSDL is down
  • Customer does NOT wait for any of these checks
Old System OLD
  • Timing: PAN check happened at PAN stage (Stage 4), NOT after OTP. Not a unified pipeline.
  • PAN APIs: PanAPI.VerifyPAN_NSDL(), PanAPI.GetPanDetailsHyperverge(), PanAPI.CallMobileToPanDetailsAsync()
  • C-safe: USP_INSERT_CSAFE_Req_Resp
  • KRA: USP_INSERT_KRA_LOGS_SJET
  • Execution: All sequential — no parallel execution
  • File: ExternalAPI/Pan/PanAPI.cs
New System NEW
  • Service: BackgroundCheckService.RunAllBackgroundChecksAsync()
  • Trigger: Fired via Task.Run() after OTP verified — non-blocking
  • Step 1: Zintlr (plain mobile number)
  • Step 2: Hyperverge + C-safe in parallel (Task.WhenAll)
  • Step 3: NSDL + CVL KRA in parallel (Task.WhenAll)
  • UTI Fallback: On NSDL failure (IsSuccess=false OR exception) → UTI fallback
  • File: Services/Stage2/BackgroundCheckService.cs
Key Difference: Old system ran PAN checks at Stage 4 (PAN entry), all sequentially. New system fires ALL background checks immediately after OTP verified, as a unified async pipeline with parallel steps. Customer never waits — proceeds to Stage 3 instantly.
2.4 Zintlr Phone-to-PAN Lookup
Query Zintlr with the verified mobile number to retrieve the customer's PAN. Store for pre-fill at Stage 4.
BRD / PRD Requirement PRD

BRD: Query Zintlr with the verified mobile number to retrieve PAN. Store the PAN (hashed) for pre-fill at Stage 4 PAN entry.

  • Uses plain mobile number (not hashed) for the API call
  • PAN hashed on return before storage
Old System OLD
  • Method: PanAPI.CallMobileToPanDetailsAsync() — called MobileToPanDetailsAPI URL
  • Logging: USP_MobileToPanLog_SJET
  • File: ExternalAPI/Pan/PanAPI.cs
New System NEW
  • Provider: ZintlrPhoneToPanProvider.LookupPanByPhoneAsync(name, plainMobile, leadId)
  • Input: Uses lead.Mobile (plain, not hash) for the API call
  • Output: PAN hashed on return before storage
  • File: ExternalProviders/Pan/ZintlrPhoneToPanProvider.cs
Key Difference: Old system used a generic MobileToPanDetailsAPI URL. New system has a dedicated ZintlrPhoneToPanProvider with explicit plain-mobile input and PAN hashing on return. Provider pattern enables easy vendor swap.
2.5 Hyperverge PAN Name+DOB Fetch
If Zintlr returns a PAN, fetch the customer's name and date of birth from the Income Tax Department via Hyperverge.
BRD / PRD Requirement PRD

BRD: If Zintlr returns a PAN, fetch name + DOB from the Income Tax Department via Hyperverge API. Used for pre-fill and validation at PAN stage.

Old System OLD
  • Methods: PanAPI.GetPanDetailsHyperverge() / GetPanDetailsHypervergeAsync()
  • Endpoint: POST to HYPERVERGE_PAN_APIURL_STD
  • Response: HyperData object with name and DOB fields
  • File: ExternalAPI/Pan/PanAPI.cs
New System NEW
  • Provider: HypervergePanProvider.FetchPanDetailsAsync(panNumber, leadId)
  • DOB Parsing: Handles multiple date formats from Hyperverge response
  • Output: Structured name + DOB for pre-fill
  • File: ExternalProviders/Pan/HypervergePanProvider.cs
Key Difference: Old system had sync/async variants in a monolithic PanAPI class. New system uses a dedicated HypervergePanProvider with robust DOB multi-format parsing. Clean separation of vendor-specific logic.
2.6 NSDL PAN Validation (with UTI Fallback)
Validate PAN is active, belongs to an individual (4th char = P), and name+DOB match. UTI is fallback only when NSDL is down.
BRD / PRD Requirement PRD

BRD: Validate PAN is valid, individual (4th char = P), Name + DOB match. UTI fallback only when NSDL is DOWN.

  • PAN status: E = active
  • Name match: Y/N
  • DOB match: Y/N
  • Seeding status check
  • UTI only when NSDL provider is unavailable
Old System OLD
  • Method: PanAPI.VerifyPAN_NSDL()
  • Provider Selection: GET_PAN_SERVICE_NAME() to choose NSDL vs UTI
  • Fallback: Dual fallback between NSDL and UTI
  • Response Fields: pan_status (E=active), name (Y/N), dob (Y/N), seeding_status
  • File: ExternalAPI/Pan/PanAPI.cs
New System NEW
  • Method: BackgroundCheckService.RunNsdlPanValidationAsync()
  • Primary: NsdlPanProvider.ValidatePanAsync()
  • UTI Fallback Triggers:
    • Result is null
    • IsSuccess = false
    • Exception thrown
  • Both Fail: Store result as "PROVIDER_DOWN"
  • File: Services/Stage2/BackgroundCheckService.cs
Key Difference: Old system used a config-driven provider switch (GET_PAN_SERVICE_NAME) that could choose either NSDL or UTI as primary. New system always uses NSDL as primary, UTI as fallback only on failure. If both fail, explicitly stores "PROVIDER_DOWN" status.
2.7 CVL KRA Status Fetch
Fetch KRA status using PAN. Map raw CVL code to internal status. Download prefill data for later stages.
BRD / PRD Requirement PRD

BRD: Fetch KRA status with PAN. Map raw CVL code to internal status. Download prefill data (name, DOB, address, email, etc.) for use in later stages.

  • KRA status determines whether KYC data is available for pre-fill
  • Email from KRA used as prefill option at Stage 3
Old System OLD
  • Logging SP: USP_INSERT_KRA_LOGS_SJET — logged all KRA API calls
  • Decision SP: USP_CALL_KRAAPI_ONSUBMIT_SJET — determined if KRA API should be called
  • Storage: KRA data stored in TBL_KRAAPI_CALL_LOG
New System NEW
  • Method: BackgroundCheckService.RunCvlKraCheckAsync()
  • Provider Calls: CvlKraPanProvider.ValidatePanAsync() then DownloadKraDataAsync()
  • Status Mapping: Raw CVL code mapped via CvlStatusMapping table + hardcoded fallback dictionary
  • Entity: Results stored in KraRecord entity
  • File: Services/Stage2/BackgroundCheckService.cs
Key Difference: Old system used separate SPs for logging, decision, and storage across different tables. New system is a single method calling a dedicated provider, with status mapping via a configurable database table (with hardcoded fallback). Results stored in a clean KraRecord entity.
2.8 C-safe AML/SEBI/PEP Check
Check PAN against SEBI debarred list, AML watchlist, PEP list, and terrorism lists. FLAGGED leads become NON_STP at Stage 11 (not blocking here).
BRD / PRD Requirement PRD

BRD: Check PAN against SEBI debarred, AML, PEP, and terrorism lists via C-safe. If FLAGGED, the lead is marked NON_STP at Stage 11 — it does NOT block the journey at this stage.

  • SEBI debarred check
  • AML watchlist check
  • PEP (Politically Exposed Person) check
  • Terrorism list check
  • Non-blocking at Stage 2 — affects STP decision at Stage 11
Old System OLD
  • Config SP: USP_GETCSAFEFLAG_SJET — retrieved C-safe configuration flags
  • Request/Response SP: USP_INSERT_CSAFE_Req_Resp — stored API request and response
  • PEP Update SP: USP_INSERT_UPDATE_CSAFE_PEP_SJET — updated PEP flags on TBL_OAO_DETAILS and TBL_CLIENT_WORKDETAILS
New System NEW
  • Method: BackgroundCheckService.RunCsafeCheckWithPanAsync()
  • Provider: CsafeProvider.CheckPanAsync(panHash, leadId)
  • Entity: CsafeCheck with boolean fields:
    • SebiDebarred
    • AmlFlagged
    • PepFlagged
    • TerrorismFlagged
  • File: Services/Stage2/BackgroundCheckService.cs
Key Difference: Old system used 3 separate SPs (config fetch, request/response logging, PEP flag update) and stored flags across multiple tables. New system has a single provider call with results stored in a clean CsafeCheck entity with explicit boolean fields for each check type.
2.9 Downstream Events (OTP Verified)
Publish events to CleverTap, Zoho CRM, Datalake, and CDP when OTP is verified.
BRD / PRD Requirement PRD

BRD: On OTP verification, publish events to CleverTap, Zoho CRM, Datalake, and CDP. All async and non-blocking.

Old System OLD
  • CRM: Inline calls to LeadSquare + Zoho CRM (synchronous)
  • Email: USP_SEND_EMAIL_ONLOGIN_SJET — sent email notification on login
  • Pattern: Direct API calls inline during OTP flow
New System NEW
  • Method: OtpVerificationService.PublishOtpVerifiedDownstreamEventsAsync()
  • Pattern: Creates 4 DownstreamEvent records:
    • CLEVERTAP
    • ZOHO_CRM
    • DATALAKE
    • CDP
  • Execution: Async, non-blocking — events queued in DB, processed by background worker
Key Difference: Old system made synchronous inline calls to LeadSquare + Zoho. New system publishes 4 async downstream events (no LeadSquare). CRM/analytics failures never block the OTP verification flow.

Stage 3 — Email Verification

3.1 Email Capture Decision (KRA Prefill / Google OAuth / Manual OTP)
Three paths to capture email: KRA email available (confirm, no OTP), Google OAuth (no OTP), or manual entry (4-digit OTP). Wait max 3 seconds for KRA result.
BRD / PRD Requirement PRD

BRD Stage 3 — EMAIL_VERIFIED: Three paths for email capture:

  • KRA Prefill: If KRA email is available from background check → customer confirms (no OTP needed)
  • Google OAuth: Customer authenticates via Google → email captured (no OTP needed)
  • Manual Entry: Customer types email → 4-digit OTP sent to that email
  • Wait maximum 3 seconds for KRA background check result before showing options
Old System OLD
  • Method: LoginRepository.GenerateEmailOTP() — single path only (manual OTP)
  • No KRA prefill path
  • No Google OAuth path
  • SP: USP_SEND_RESUME_EMAILOTP_SJET — generated OTP for email
  • File: Joruney_imp_code\Registration\LoginRepository.cs
New System NEW
  • Service: EmailVerificationService.InitiateEmailVerificationAsync()
  • Routing by EmailSource:
    • KRA_PREFILLHandleKraPrefillVerificationAsync() (no OTP)
    • GOOGLE_OAUTHHandleGoogleOAuthVerificationAsync() (no OTP)
    • MANUAL_OTPHandleManualOtpInitiationAsync() (4-digit OTP, 10-min TTL)
  • KRA Wait: GetEmailDetailsAsync() has 3-second KRA wait via polling loop
  • File: Services/Stage3/EmailVerificationService.cs
Key Difference: Old system had only one path (manual OTP via SP). New system supports 3 paths: KRA prefill (no OTP), Google OAuth (no OTP), and manual OTP. The 3-second KRA polling wait is entirely new — enables pre-fill from background checks fired at Stage 2.
3.2 Email Format Validation (Custom Rules)
Reject forbidden patterns (notprovided, noemail, xyz), domains starting with digits, domains ending with periods, and multiple @ signs.
BRD / PRD Requirement PRD

BRD: Custom email validation rules beyond standard format check:

  • Reject forbidden patterns: notprovided, noemail, xyz, etc.
  • Reject domain starting with a digit
  • Reject domain ending with a period
  • Reject multiple @ signs
  • Whitelist of allowed domains can skip format checks
Old System OLD
  • SP: USP_CUSTOM_EMAIL_VALIDATION_SJET — extensive SP-based format checks
  • SP: USP_ALLOW_EMAIL_SJET — whitelist check
  • Table: TBL_ALLOW_EMAIL — whitelist of allowed email patterns
  • Logic: Domain and pattern rules implemented in SQL stored procedures
New System NEW
  • Method: EmailVerificationService.ValidateEmailFormat() (static method)
  • Checks:
    • ForbiddenEmailPatterns array (notprovided, noemail, xyz, etc.)
    • char.IsDigit(domain[0]) — domain starts with digit
    • endsWith('.') — domain ends with period
    • @ count > 1
  • Whitelist: AllowedEmailDomain table — whitelisted domains skip format checks
  • Config Toggles: ENABLE_CUSTOM_FORMAT_CHECK, ENABLE_WHITELIST_CHECK, ENABLE_RESTRICTED_DOMAIN_CHECK
  • File: Services/Stage3/EmailVerificationService.cs
Key Difference: Old system ran all validation in SQL SPs. New system uses a static C# method with a configurable forbidden patterns array and feature toggles for each check type. Whitelisted domains skip all custom format checks.
3.3 Restricted Email Domain Check
Block disposable email domains (temporary/throwaway email providers).
BRD / PRD Requirement PRD

BRD: Block disposable/temporary email domains. If the email domain is on the restricted list, reject with error.

Old System OLD
  • SP: USP_RESTRICT_EMAIL_DOMAIN_SJET
  • Table: TBL_RESTRICT_EMAIL_DOMAIN
New System NEW
  • Query: _db.Set<RestrictedEmailDomain>().AnyAsync(r => r.Domain == emailDomain && r.IsActive)
  • Table: restricted_email_domains
  • Error: EMAIL_DOMAIN_RESTRICTED
Key Difference: Old system used a stored procedure to check TBL_RESTRICT_EMAIL_DOMAIN. New system queries the restricted_email_domains table directly via EF Core. Same logic, cleaner implementation.
3.4 Email Duplicate Check (Post-eSign)
If the email hash is already linked to a post-eSign lead, block the email. Bypass logic checked before blocking.
BRD / PRD Requirement PRD

BRD: Email hash already linked to a post-eSign lead → block. Check bypass eligibility before hard-blocking.

Old System OLD
  • SP: USP_SEND_RESUME_EMAILOTP_SJET — checked duplicates inline
  • Tables checked:
    • TBL_DEDUPE_DATA_DUMP
    • TBL_CLIENT_CLIENTCODE
    • TBL_CLIENT_STAGEDETAILS
    • TBL_CLIENT_PERSONALDETAILS
New System NEW
  • Service: EmailVerificationService joins Leads (stage ≥ Esign) with EmailVerifications by emailHash
  • Bypass: If duplicate found, calls BackOfficeCheckService.CheckBypassEligibilityAsync("EMAIL", email) before blocking
  • Error: BE_EMAIL_DUPLICATE
Key Difference: Old system checked duplicates across 4 separate tables in a single massive SP. New system joins Leads + EmailVerifications with a stage filter (post-eSign only) and checks bypass eligibility via a separate service before blocking.
3.5 Email Bypass Logic
Same bypass pattern as mobile but for EMAIL type. Allow registration when email exists in back-office for INACTIVE, PMS-only, or OWNER accounts.
BRD / PRD Requirement PRD

BRD: Same as mobile bypass but for EMAIL type. Bypass conditions: INACTIVE accounts, PMS-only accounts, branch/subbroker OWNER accounts.

Old System OLD
  • SP: USP_BYPASS_MOBILE_EMAIL_PAN_SJET — EMAIL section
  • Tables checked: MOSL_FEED_CLIENT_DETAILS by EmailId, MOSL_FEED_BRANCH by BA_EMAIL / CORR_EMAIL
New System NEW
  • Method: BackOfficeCheckService.CheckEmailBypassAsync()
  • Tables checked:
    • client_master — by Email
    • branch_master — by BaEmail / CorrEmail
    • subbroker_master
    • duplicate_bypass_whitelist
  • File: Services/Common/BackOfficeCheckService.cs
Key Difference: Old system checked MOSL_FEED tables directly in a shared SP (MOBILE/EMAIL/PAN in one SP). New system has a dedicated CheckEmailBypassAsync() method checking synced tables. Added duplicate_bypass_whitelist as an additional bypass path.
3.6 Email OTP Generation & Delivery
Generate a 4-digit OTP with 10-minute TTL for manual email entry. Max 5 wrong attempts (must change email), max 3 resends, 30-second cooldown.
BRD / PRD Requirement PRD

BRD: 4-digit OTP for manual email verification:

  • 10-minute TTL
  • Max 5 wrong attempts → must change email address
  • Max 3 resends
  • 30-second cooldown between resends
Old System OLD
  • SP: USP_SEND_RESUME_EMAILOTP_SJET — stored OTP in database
  • Email Delivery: EmailAPI.sendMail_Netcore() — sent OTP email via Netcore
  • Validation (removed): CYBRIDGE/Karza/Custom SP email validation — all removed per Product team
New System NEW
  • OTP Storage: InMemoryOtpStore.Store(leadId.ToString(), "EMAIL", otp, leadId, 10min)
  • Delivery: SendEmailOtpAsync() via INotificationService
  • Cooldown: InMemoryOtpStore.Store(leadId, "EMAIL_COOLDOWN", "1", 30sec) — cooldown tracked as separate in-memory entry
  • File: Services/Stage3/EmailVerificationService.cs
Key Difference: Old system stored email OTP in database and sent via EmailAPI.sendMail_Netcore(). New system stores OTP in-memory only (10-min TTL) and uses INotificationService abstraction. Cooldown tracked as a separate in-memory entry with 30-sec TTL. CYBRIDGE/Karza email validation removed entirely.
3.7 Email OTP Verification
Verify the email OTP. On success, transition lead state to EMAIL_VERIFIED. 5 wrong attempts locks the email (must enter a different one).
BRD / PRD Requirement PRD

BRD: Verify OTP submitted by customer. On success → lead.state = EMAIL_VERIFIED. 5 wrong attempts → email locked, customer must enter a different email address.

Old System OLD
  • Validation SP: USP_VALIDATE_UPDATE_OTP_EMAIL_SJET — validated OTP from database
  • Stage Update SP: USP_UPDATE_OTPEMAIL_STAGE_SJET — updated stage in TBL_CLIENT_STAGEDETAILS
  • Integration: SuperApp integration for cloud-enabled leads
New System NEW
  • Method: EmailVerificationService.VerifyEmailOtpAsync()
  • OTP Retrieval: InMemoryOtpStore.Get(leadId, "EMAIL")
  • Tracking: AttemptCount tracked in OtpVerification entity
  • Lock: 5th wrong attempt → IsLocked = true, must enter different email
  • On Success: lead.state = EMAIL_VERIFIED, publishes 4 downstream events
  • File: Services/Stage3/EmailVerificationService.cs
Key Difference: Old system validated OTP from database via SP, updated stage via separate SP. New system validates from in-memory store, tracks attempts in OtpVerification entity, and locks the email (not the lead) after 5 wrong attempts — customer can try a different email. Downstream events published automatically on success.
3.8 Downstream Events (Email Verified)
Publish events to CleverTap, Zoho CRM, CDP, and Datalake when email is verified.
BRD / PRD Requirement PRD

BRD: On email verification, publish events to CleverTap, Zoho CRM, CDP, and Datalake. All async and non-blocking.

Old System OLD
  • Stage Update: USP_UPDATE_OTPEMAIL_STAGE_SJET
  • CRM: LSQ opportunity update (synchronous)
  • Email: Email sent on login notification
New System NEW
  • Pattern: 4 DownstreamEvent records created with EventType = EMAIL_VERIFIED:
    • CLEVERTAP
    • ZOHO_CRM
    • CDP
    • DATALAKE
  • Payload: Includes EmailSource (KRA_PREFILL / GOOGLE_OAUTH / MANUAL_OTP) and KraPrefillUsed flag
  • Execution: Async, non-blocking — events queued in DB, processed by background worker
Key Difference: Old system updated stage via SP and made synchronous CRM calls. New system publishes 4 async downstream events with rich payload (EmailSource, KraPrefillUsed). No LeadSquare — Zoho CRM only. Event payloads carry the email capture method for analytics.