Skip to content

krishan93985/highlevel-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wallet System

A robust financial transaction management system built with NestJS and MongoDB, providing a scalable and reliable solution for managing digital wallets.

Tech Stack

  • Framework: NestJS (Node.js)
  • Database: MongoDB with Mongoose ODM
  • API Documentation: Swagger/OpenAPI
  • Validation: class-validator & class-transformer
  • Error Handling: Custom error handling with global filters
  • Code Quality: ESLint & Prettier
  • API Response Interceptors: Custom response formatting

Codebase Structure

src/
├── common/                 # Shared utilities, filters, and interceptors
│   ├── constants/         # Application constants
│   ├── decorators/        # Custom decorators
│   ├── dto/              # Common DTOs
│   ├── errors/           # Error handling
│   ├── filters/          # Global filters
│   ├── interceptors/     # Response interceptors
│   ├── types/            # Common types and interfaces
│   └── utils/            # Utility functions
├── wallet/                # Wallet module
│   ├── domain/           # Domain objects and business logic
│   ├── dto/              # Data transfer objects
│   ├── schemas/          # MongoDB schemas
│   ├── swagger/          # Swagger documentation
│   ├── types/            # Module-specific types
│   ├── wallet.controller.ts
│   ├── wallet.service.ts
│   ├── wallet.repository.ts
│   └── wallet.module.ts
└── app.module.ts         # Root application module

Features

  • Create and manage digital wallets
  • Process credit and debit transactions with atomic operations
  • Retrieve transaction history with pagination and sorting
  • Export transactions to CSV with streaming support
  • Comprehensive error handling and logging
  • Retry mechanism for handling concurrent operations

Table of Contents

Architecture

Core Components

  1. Controller Layer (WalletController)

    • Handles HTTP requests
    • Input validation using class-validator
    • Standardized response format
    • Global error handling
  2. Service Layer (WalletService)

    • Transaction management with MongoDB sessions
    • Retry mechanism for concurrent operations
    • Comprehensive logging
  3. Repository Layer (WalletRepository)

    • Atomic database operations
    • Optimized queries with indexes
    • Error handling with custom error types

Data Models

Wallet Schema

@Schema({ timestamps: true })
export class Wallet {
  @Prop({ type: Types.Decimal128, required: true })
  balance: number;

  @Prop({ type: String, required: true })
  name: string;

  @Prop({ type: Date, default: Date.now })
  date: Date;
}

Transaction Schema

@Schema({ timestamps: true })
export class Transaction {
  @Prop({ type: Types.ObjectId, required: true, ref: 'Wallet' })
  walletId: Types.ObjectId;

  @Prop({ type: Types.Decimal128, required: true })
  amount: number;

  @Prop({ type: Types.Decimal128, required: true })
  balance: number;

  @Prop({ type: String, required: true })
  description: string;

  @Prop({ type: Date, default: Date.now })
  date: Date;

  @Prop({ type: String, enum: TransactionType, required: true })
  type: TransactionType;
}

API Documentation

All endpoints use a standardized request/response format with comprehensive validation.

Standard Response Format

{
  "success": true,
  "statusCode": number,    // HTTP status code (default: 200)
  "message": string,       // Human-readable message
  "data": T | null        // Response payload or null for errors
}

Standard Error Format

{
  statusCode: status,
  message: error.message,
  error: {
    code: error.code
  }
}

Endpoints

1. Setup Wallet

POST /setup

Request:
{
  "balance": 100.5612,  // Initial balance (max 4 decimal places)
  "name": "My Wallet"   // Wallet name
}

Response:
{
  "success": true,
  "statusCode": 201,
  "message": "Wallet My Wallet created successfully with initial balance of 100.5612",
  "data": {
    "id": "507f1f77bcf86cd799439011",
    "balance": 100.5612,
    "transactionId": "507f1f77bcf86cd799439012",
    "name": "My Wallet",
    "date": "2024-03-20T10:30:00Z"
  }
}

2. Process Transaction

POST /transact/:walletId

Request:
{
  "amount": -50.25,     // Negative for debit, positive for credit
  "description": "Monthly subscription"
}

Response:
{
  "success": true,
  "statusCode": 200,
  "message": "Successfully debited 50.25 from wallet",
  "data": {
    "balance": 50.3112,
    "transactionId": "507f1f77bcf86cd799439013"
  }
}

3. Get Transaction History

GET /transactions?walletId=507f1f77bcf86cd799439011&skip=0&limit=10&sortBy=date&sortOrder=desc

Response:
{
  "success": true,
  "statusCode": 200,
  "message": "Retrieved 2 transactions",
  "data": {
    "items": [
      {
        "id": "507f1f77bcf86cd799439013",
        "walletId": "507f1f77bcf86cd799439011",
        "amount": -50.25,
        "balance": 50.3112,
        "description": "Monthly subscription",
        "date": "2024-03-20T10:35:00Z",
        "type": "DEBIT"
      }
    ],
    "metadata": {
      "total": 2,
      "page": 1,
      "limit": 10,
      "hasMore": false
    }
  }
}

4. Get Wallet Details

GET /wallet/:id

Response:
{
  "success": true,
  "statusCode": 200,
  "message": "Retrieved wallet details for My Wallet",
  "data": {
    "id": "507f1f77bcf86cd799439011",
    "balance": 50.3112,
    "name": "My Wallet",
    "date": "2024-03-20T10:30:00Z"
  }
}

5. Export Transactions

GET /transactions/export?walletId=507f1f77bcf86cd799439011&sortBy=date&sortOrder=desc

Response Headers:
Content-Type: text/csv
Content-Disposition: attachment; filename="transactions.csv"

Response Body (CSV):
Date,Amount,Description,Type,Balance
2024-03-20T10:35:00Z,-50.25,Monthly subscription,DEBIT,50.3112
2024-03-20T10:30:00Z,100.5612,Setup,CREDIT,100.5612

Request Validation

All requests are validated using class-validator:

export class TransactionRequestDTO {
  @ApiProperty({ description: 'Transaction amount' })
  @IsNumber()
  @Transform(({ value }) => {
    const num = Number(value);
    const decimalStr = num.toString().split('.')[1];
    if (decimalStr && decimalStr.length > 4) {
      throw GHLError.validation('Amount must have at most 4 decimal places');
    }
    return num;
  })
  amount: number;

  @ApiProperty({ description: 'Transaction description' })
  @IsString()
  @IsNotEmpty()
  description: string;
}

Decimal Precision Handling

The system uses MongoDB's Decimal128 type to handle financial calculations with precision up to 4 decimal places.

Schema Definition

@Schema({ timestamps: true })
export class Transaction {
  @Prop({ 
    type: Types.Decimal128, 
    required: true,
    get: (val: Types.Decimal128) => DecimalUtils.fromDecimal128(val),
    set: (val: number) => DecimalUtils.toDecimal128(val)
  })
  amount: number;

  @Prop({ 
    type: Types.Decimal128, 
    required: true,
    get: (val: Types.Decimal128) => DecimalUtils.fromDecimal128(val),
    set: (val: number) => DecimalUtils.toDecimal128(val)
  })
  balance: number;
}

Decimal Utilities

export class DecimalUtils {
  static toDecimal128(value: number): Types.Decimal128 {
    return Types.Decimal128.fromString(value.toFixed(4));
  }

  static fromDecimal128(decimal: Types.Decimal128): number {
    return Number(decimal.toString());
  }

  static validatePrecision(value: number, field: string): void {
    const decimalStr = value.toString().split('.')[1];
    if (decimalStr && decimalStr.length > 4) {
      throw GHLError.validation(`${field} must have at most 4 decimal places`);
    }
  }
}

Performance Optimizations

1. Database Indexing

// Transaction Schema Indexes for efficient querying
TransactionSchema.index({ walletId: 1, date: -1 });   // For date-based sorting
TransactionSchema.index({ walletId: 1, amount: 1 });  // For amount-based sorting
TransactionSchema.index({ walletId: 1 });             // For wallet-based queries

// Wallet Schema Index
WalletSchema.index({ balance: 1 });  // For balance-based operations

2. Atomic Operations

Example of atomic debit operation:

const updatedWallet = await this.walletModel.findOneAndUpdate(
  {
    _id: toObjectId(walletId),
    balance: { $gte: debitAmountDecimal }, // Atomic balance check
  },
  { $inc: { balance: toDecimal128(-amountToDebit) } }, // Atomic update
  { new: true, session }
);

3. Transaction Retry Mechanism

@WithTransactionRetry(RETRY_DELAYS.MEDIUM)
async processTransaction(walletId: Types.ObjectId, amount: number, description: string) {
  // Retry configuration
  // maxRetries: 5
  // baseDelay: 100ms
  // maxDelay: 2000ms
  // Retries on specific MongoDB error codes: 112 (WriteConflict), 251 (TransactionAborted)
}

4. Streaming for Large Datasets

CSV export implementation using streaming:

async exportTransactionsToCSV(walletId: Types.ObjectId, writer: CSVStreamWriter) {
  const cursor = await this.getTransactionsCursor(walletId);
  writer.writeHeaders(['Date', 'Amount', 'Description', 'Type', 'Balance']);
  
  for await (const doc of cursor) {
    writer.writeRow({
      Date: doc.date,
      Amount: doc.amount,
      Description: doc.description,
      Type: doc.type,
      Balance: doc.balance
    });
  }
}

Error Handling

1. Validation Examples

@Transform(({ value }) => {
  const num = Number(value);
  const decimalStr = num.toString().split('.')[1];
  if (decimalStr && decimalStr.length > 4) {
    throw GHLError.validation('Amount must have at most 4 decimal places');
  }
  return num;
})
amount: number;

Common error responses:

// Error Types
GHLError.validation('Invalid input')        // 400 Bad Request
GHLError.notFound('Resource not found')     // 404 Not Found
GHLError.internal('Internal server error')  // 500 Internal Server Error

// Error with Metadata
throw GHLError.validation('Insufficient balance', {
  required: 100,
  available: 50
});

Example error responses:

  1. Validation Error
{
  "success": false,
  "statusCode": 400,
  "message": "Amount must have at most 4 decimal places",
  "error": {
    "code": "VALIDATION_ERROR",
    "metadata": {
      "field": "amount",
      "value": "100.12345"
    }
  }
}
  1. Not Found Error
{
  "success": false,
  "statusCode": 404,
  "message": "Wallet not found",
  "error": {
    "code": "NOT_FOUND",
    "metadata": {
      "id": "507f1f77bcf86cd799439011"
    }
  }
}

Performance & Scalability Summary

Database Optimizations

  • Compound indexes for efficient querying and sorting
  • Atomic operations for concurrent transactions
  • MongoDB transactions for data consistency

Concurrency Handling

  • Optimistic locking for concurrent updates
  • Retry mechanism with exponential backoff
  • Transaction isolation with MongoDB sessions

Resource Management

  • Cursor-based streaming for large datasets
  • Efficient pagination with skip/limit
  • Connection pooling for database operations

Read The API Documentation Here

API Documentation

Getting Started

Prerequisites

  • Node.js (v14 or higher)
  • MongoDB (v4.4 or higher)
  • npm or yarn

Installation

  1. Clone the repository
git clone <repository-url>
  1. Install dependencies
npm install
  1. Set up environment variables
# .env
MONGODB_URI=mongodb://localhost:27017/wallet
  1. Run the application
# Development
npm run start:dev

# Production
npm run start:prod

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published