Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Codefied-CodePix/Karokar-backend/llms.txt

Use this file to discover all available pages before exploring further.

Trust is a foundational requirement for a B2B2C mobility marketplace. Before a vendor can list a vehicle, an organization must be verified. Before a corporate organization can make bookings, its authorized representative must be confirmed. Before a vehicle enters service, its registration, insurance, and fitness certificate must all clear review. KaroKar models this not as a simple isVerified: boolean flag on each entity, but as a fully independent Verification Domain that maintains complete lifecycle records — who was verified, what was verified, how it was verified, when it was verified, when it expires, and who approved it. This design supports auditing, expiry tracking, document references, and future integration with external authorities without touching any other domain’s schema.

Why Not Boolean Flags?

The alternative — adding isVerified fields directly to Vendor, Vehicle, and User entities — was explicitly considered and rejected. Boolean flags provide no history, no expiry support, no auditability, and no path to multiple verification types. They make external integration painful and compliance reporting impossible. Verification is a business process, not an entity attribute.
In Phase 01, all verifications are performed manually by Platform Admins. A platform administrator reviews uploaded documents and transitions the verification through its lifecycle states. The architecture is designed so that future external integrations (NADRA, driving license authorities, insurance providers, biometric systems) can feed into the same framework without any structural changes.

Verifiable Entity Types

The verification framework is generic. Any domain entity can be verified by recording a Verification row that points to it via entityType + entityId.
// src/verification/domain/enums/verifiable-entity-type.enum.ts
export enum VerifiableEntityType {
  ORGANIZATION = 'ORGANIZATION',
  USER         = 'USER',
  VEHICLE      = 'VEHICLE',
}
Future entity types such as DRIVER, BOOKING, CONTRACT, and INSURANCE_POLICY are anticipated and require only a new enum value — no schema changes.

Verification Types by Entity

Each entity type carries its own set of domain-specific verification requirements.

Organization

OrganizationVerificationType
  • COMPANY_REGISTRATION
  • TAX_REGISTRATION
  • AUTHORIZED_REPRESENTATIVE

User

UserVerificationType
  • EMAIL
  • PHONE
  • IDENTITY

Vehicle

VehicleVerificationType
  • REGISTRATION
  • INSURANCE
  • FITNESS_CERTIFICATE
// src/verification/domain/enums/organization-verification-type.enum.ts
export enum OrganizationVerificationType {
  COMPANY_REGISTRATION      = 'COMPANY_REGISTRATION',
  TAX_REGISTRATION          = 'TAX_REGISTRATION',
  AUTHORIZED_REPRESENTATIVE = 'AUTHORIZED_REPRESENTATIVE',
}

// src/verification/domain/enums/user-verification-type.enum.ts
export enum UserVerificationType {
  EMAIL    = 'EMAIL',
  PHONE    = 'PHONE',
  IDENTITY = 'IDENTITY',
}

// src/verification/domain/enums/vehicle-verification-type.enum.ts
export enum VehicleVerificationType {
  REGISTRATION      = 'REGISTRATION',
  INSURANCE         = 'INSURANCE',
  FITNESS_CERTIFICATE = 'FITNESS_CERTIFICATE',
}

The Verification Entity

The Verification entity is the central record of the domain. Its verificationType column is stored as a plain string rather than a single enum, which allows all three type enums to feed into the same table without a discriminated union schema.
// src/verification/domain/entities/verification.entity.ts
@Entity('verifications')
@Index(['organizationId', 'entityType', 'entityId'])
@Index(['entityType', 'entityId', 'verificationType'])
export class Verification {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column({ type: 'enum', enum: VerifiableEntityType, name: 'entity_type' })
  entityType!: VerifiableEntityType;

  @Column({ name: 'entity_id', type: 'uuid' })
  entityId!: string;

  @Column({ name: 'verification_type' })
  verificationType!: string;          // OrganizationVerificationType | UserVerificationType | VehicleVerificationType

  @Column({ type: 'enum', enum: VerificationStatus })
  status!: VerificationStatus;

  @Column({ name: 'requested_at', type: 'timestamptz' })
  requestedAt!: Date;

  @Column({ name: 'verified_at', type: 'timestamptz', nullable: true })
  verifiedAt!: Date | null;

  @Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
  expiresAt!: Date | null;

  @Column({ name: 'verified_by', type: 'uuid', nullable: true })
  verifiedBy!: string | null;

  @Column({ type: 'jsonb', default: {} })
  metadata!: Record<string, unknown>;

  @Column({ name: 'organization_id', type: 'uuid' })
  organizationId!: string;
}
The compound indexes on (organizationId, entityType, entityId) and (entityType, entityId, verificationType) ensure that looking up all verifications for a given entity — a common access pattern during gatekeeping checks — is performant even at large scale.

Verification Statuses

// src/verification/domain/enums/verification-status.enum.ts
export enum VerificationStatus {
  PENDING     = 'PENDING',       // Submitted, awaiting assignment
  UNDER_REVIEW = 'UNDER_REVIEW', // Actively being reviewed by a Platform Admin
  VERIFIED    = 'VERIFIED',      // Approved and currently valid
  REJECTED    = 'REJECTED',      // Declined — resubmission may be required
  EXPIRED     = 'EXPIRED',       // Validity period has elapsed
  REVOKED     = 'REVOKED',       // Withdrawn due to fraud, violation, or compliance breach
}

Verification Lifecycle

1

Requested → PENDING

An entity (Vendor, Corporate, or Vehicle owner) submits documents. A Verification record is created with status: PENDING and requestedAt is stamped. A VerificationRequested domain event is emitted, which notifies the Notification and Audit domains.
2

PENDING → UNDER_REVIEW

A Platform Admin picks up the verification case. The status transitions to UNDER_REVIEW as the admin examines the attached documents.
3

UNDER_REVIEW → VERIFIED or REJECTED

  • Approved: status becomes VERIFIED, verifiedAt and verifiedBy are stamped. If the verification type requires an expiry (e.g., vehicle insurance), expiresAt is set. A VerificationApproved event is emitted.
  • Declined: status becomes REJECTED. The submitter is notified via the VerificationRejected event so they can correct and resubmit.
4

VERIFIED → EXPIRED

A background job monitors expiresAt for all VERIFIED records. When the validity period elapses, the status transitions to EXPIRED and a VerificationExpired domain event is emitted. The Fleet Domain and Compliance Domain subscribe to this event to restrict the entity’s capabilities.
5

Any State → REVOKED

Platform Admins may revoke a verification at any time if fraud is detected, a document is withdrawn, or a compliance violation occurs. A VerificationRevoked event is emitted, consumed by the Fleet, Compliance, Administration, and Notification domains.

Verification Document References

Supporting documents (CNIC, business registration certificates, insurance certificates, vehicle registration papers) are uploaded to and owned by the Document Domain. The Verification Domain references them without owning them.
// src/verification/domain/entities/verification-document.entity.ts
@Entity('verification_documents')
export class VerificationDocument {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column({ name: 'verification_id', type: 'uuid' })
  verificationId!: string;

  @Column({ name: 'document_id', type: 'uuid' })
  documentId!: string;

  @Column({ name: 'uploaded_by', type: 'uuid' })
  uploadedBy!: string;

  @Column({ name: 'uploaded_at', type: 'timestamptz' })
  uploadedAt!: Date;
}
This separation means the Document Domain can handle storage, access control, and virus scanning independently, while the Verification Domain purely tracks the review lifecycle.

Expiry Tracking and Domain Events

Not all verification types expire. Email and phone verifications are indefinite. Vehicle insurance and fitness certificates have strict validity windows. The expiresAt field is nullable — it is populated only when the specific verification type requires it.
Verification TypeExpiry RequiredRenewal Required
COMPANY_REGISTRATIONNoNo
TAX_REGISTRATIONNoNo
AUTHORIZED_REPRESENTATIVENoNo
EMAILNoNo
PHONENoNo
IDENTITYNoNo
REGISTRATIONNoNo
INSURANCEYesYes
FITNESS_CERTIFICATEYesYes
When a VerificationExpired event fires, the following domains react:
  • Fleet Domain — may suspend the vehicle from availability
  • Notification Domain — alerts the relevant organization
  • Compliance Domain — logs the expiry for regulatory reporting

Verification Gatekeeping

Certain platform actions are gated behind specific verification requirements. These rules are configurable — they must not be hardcoded into service logic.
Requires: OrganizationVerificationType.COMPANY_REGISTRATIONVERIFIED
Requires all of:
  • VehicleVerificationType.REGISTRATIONVERIFIED
  • VehicleVerificationType.INSURANCEVERIFIED
  • VehicleVerificationType.FITNESS_CERTIFICATEVERIFIED
Requires: Organization-level verification for the corporate tenant → VERIFIED

Future External Integrations

Phase 01 is manual-only, but the framework is deliberately integration-ready. External providers update verification records through the same domain model — they do not bypass it. Planned future integrations include:
  • NADRA — automated national identity verification
  • Driving License Authority — license validation for drivers
  • Insurance Providers — real-time policy status checks
  • FBR — tax registration number validation
  • Biometric Providers — liveness and identity matching
Each integration will produce VerificationApproved or VerificationRejected events through the same lifecycle, ensuring audit trails and downstream reactions remain consistent regardless of whether a human or an external API performed the review.

Build docs developers (and LLMs) love