Payment Module
The Payment module is a comprehensive financial transaction system for the Comdeall platform that manages all payment operations including subscription payments, appointment fees, refunds, payouts, and wallet transactions. It integrates with Razorpay for payment processing and RazorpayX for payouts, providing a complete financial ecosystem with automated fee distribution, webhook processing, and transaction management.
Table of Contents
- Module Structure
- Payment Endpoints
- Core Features
- Payment Flow Architecture
- Payment Providers
- Webhook System
- Database Functions & Triggers
- Transaction Management
- Wallet System
- Refund Management
- Payout System
- Data Models
- Security & Validation
Module Structure
The Payment module follows a provider-based architecture with multiple payment gateways:
@Module({
imports: [RedisModule, forwardRef(() => UserModule), DBModule, forwardRef(() => BackgroundModule)],
controllers: [PaymentController],
providers: [PaymentService, RazorpayProvider, RazorpayXProvider, RazorpaySignatureProvider],
exports: [PaymentService, RazorpayProvider],
})
export class PaymentModule {}
Components:
- PaymentController: API endpoints for payment operations
- PaymentService: Business logic and orchestration
- RazorpayProvider: Razorpay payment gateway integration
- RazorpayXProvider: RazorpayX payout system integration
- RazorpaySignatureProvider: Webhook signature verification
- PaymentDBService: Database operations and queries
Payment Endpoints
Core Payment Operations
| Endpoint | Method | Description | Auth Required | Roles |
|---|---|---|---|---|
/create-payment-intent | POST | Create Razorpay order | JWT | ADMIN |
/create-payment-link | POST | Generate payment link | JWT | ADMIN |
/get-order-details | POST | Fetch order information | JWT | ADMIN |
/get-payment-details | POST | Retrieve payment data | JWT | ADMIN |
/verify-payment-signature | POST | Validate payment signature | JWT | ADMIN |
/payment-webhook | POST | Handle payment webhooks | None | Public |
Payout Operations
| Endpoint | Method | Description | Auth Required | Roles |
|---|---|---|---|---|
/create-rzp-contact | POST | Create RazorpayX contact | JWT | ADMIN |
/create-fund-account | POST | Setup bank account | JWT | ADMIN |
/create-payout | POST | Initiate payout | JWT | ADMIN |
/get-payout-details | POST | Fetch payout status | JWT | ADMIN |
/payout-webhook | POST | Handle payout webhooks | None | Public |
Refund Operations
| Endpoint | Method | Description | Auth Required | Roles |
|---|---|---|---|---|
/initiate-refund | POST | Start refund process | JWT | ADMIN |
/get-refund-details | POST | Check refund status | JWT | ADMIN |
/refund-webhook | POST | Handle refund webhooks | None | Public |
GraphQL Function Operations
| Function | Method | Description | Auth Required | Roles |
|---|---|---|---|---|
transfer_appointment_fee | Mutation | Transfer fee to therapist wallet | JWT | THERAPIST, PARENT |
Core Features
1. Multi-Gateway Payment Processing
- Razorpay Integration for customer payments (orders, links, subscriptions)
- RazorpayX Integration for business payouts and fund transfers
- Dual webhook system for real-time payment status updates
- Signature verification for webhook security
2. Automated Fee Distribution
// Payment share calculation
v_appointment_fee := v_therapist.fee * ((100 - v_payment_share) / 100);
// Platform commission deduction
// Therapist receives: fee * (100 - commission_percentage) / 100
3. Comprehensive Transaction Types
enum TransactionType {
SUBSCRIPTION = 'SUBSCRIPTION',
APPOINTMENT = 'APPOINTMENT',
APPOINTMENT_REFUND = 'APPOINTMENT_REFUND',
PAYOUT = 'PAYOUT'
}
enum TransactionStatus {
PENDING = 'PENDING',
SUCCESSFUL = 'SUCCESSFUL',
FAILED = 'FAILED',
REFUNDED = 'REFUNDED'
}
4. Resilient Payment Processing
- Retry mechanism with exponential backoff for rate limiting
- Idempotency keys for payout deduplication
- Fallback payout modes (IMPS → NEFT automatic retry)
- Redis-based duplicate webhook prevention
5. Advanced Webhook Management
- Signature verification for all incoming webhooks
- Event deduplication using Redis state store
- Background job processing for webhook handling
- Multi-event support (payment, payout, refund)
Payment Flow Architecture
1. Subscription Payment Flow
// Subscription payment workflow
1. Create Razorpay order/payment link
2. Customer completes payment
3. Webhook triggers status update
4. Subscription activation
5. Feature access granted
2. Appointment Payment Flow
// Appointment payment and fee distribution
1. Create appointment payment order
2. Customer pays appointment fee
3. Payment webhook updates status
4. Admin/Therapist triggers fee transfer
5. Platform commission deducted
6. Therapist wallet credited
7. Transaction recorded
3. Payout Flow
// Therapist payout workflow
1. Create RazorpayX contact
2. Setup fund account (bank details)
3. Initiate payout from wallet balance
4. Automatic IMPS → NEFT fallback
5. Payout webhook updates status
6. Wallet balance adjustment
Payment Providers
1. RazorpayProvider
Purpose: Customer-facing payment processing
@Injectable()
export class RazorpayProvider extends Razorpay {
constructor(configService: ConfigService<EnvConfig>) {
super({
key_id: configService.get('RAZORPAY_KEY_ID'),
key_secret: configService.get('RAZORPAY_KEY_SECRET')
});
}
}
Capabilities:
- Order creation and management
- Payment link generation
- Payment verification and capture
- Refund processing
- Subscription management
2. RazorpayXProvider
Purpose: Business payout and fund management
@Injectable()
export class RazorpayXProvider {
private readonly axiosInstance: AxiosInstance;
// Contact and fund account management
async createContact(contact_parameters: CreateContactDto)
async createFundAccount(fund_account_parameters: CreateFundAccountDto)
// Payout processing with fallback
async createPayout(payout_parameters: CreatePayoutDto)
}
Advanced Features:
- Automatic mode fallback: IMPS failures automatically retry with NEFT
- Idempotency support: Prevents duplicate payouts
- Low balance queueing: Handles insufficient balance scenarios
- Contact and fund account lifecycle management
3. RazorpaySignatureProvider
Purpose: Webhook security and verification
// Webhook signature verification
verifyWebhookSignature(webhook_input: {
webhookBody: string;
webhookSignature: string;
}): boolean
// Payment signature verification
verifyPaymentSignature(verify_payment_parameters: VerifyPaymentDto): boolean
Webhook System
1. Payment Webhooks
Endpoint: /payment-webhook
Authentication: Signature-based verification
Supported Events:
payment.capturedpayment.failedpayment_link.paidpayment_link.cancelled
Processing Flow:
async paymentWebhook(payment_body: any, signature: string, event_id: string) {
// 1. Verify webhook signature
const is_valid = this.razorpaySignatureProvider.verifyWebhookSignature({
webhookBody: JSON.stringify(payment_body),
webhookSignature: signature
});
// 2. Check for duplicate processing
const isProcessed = await this.redisClient.get(event_id);
// 3. Process if valid and new
if (is_valid && !isProcessed) {
// Background job for async processing
this.backgroundServiceManager.addRazorpayPaymentWebhookJob(payment_body, jobId);
}
}
2. Payout Webhooks
Endpoint: /payout-webhook
Events: Payout status changes (processing, success, failure)
3. Refund Webhooks
Endpoint: /refund-webhook
Events: Refund status updates and processing notifications
4. Webhook Security Features
- Signature verification using HMAC-SHA256
- Event ID deduplication with Redis TTL
- Rate limiting protection with retry logic
- Background job processing for heavy operations
Database Functions & Triggers
1. Appointment Fee Transfer Function (deprecated)
Function: transfer_appointment_fee()
Type: PostgreSQL function exposed as GraphQL mutation
Parameters:
hasura_session: Authentication contextinput_appointment_id: Appointment UUID
Authorization Logic:
-- Multi-role authorization
IF token_role NOT IN ('ADMIN', 'THERAPIST', 'PARENT') THEN
RAISE EXCEPTION 'Unauthorized access';
END IF;
Business Logic:
-- Payment share calculation
IF v_therapist.payment_share IS NULL OR v_therapist.payment_share = 0 THEN
SELECT share INTO v_payment_share FROM settings LIMIT 1;
ELSE
v_payment_share := v_therapist.payment_share;
END IF;
-- Fee calculation and wallet credit
v_appointment_fee := v_therapist.fee * ((100 - v_payment_share) / 100);
v_updated_balance := v_therapist.balance + v_appointment_fee;
-- Update therapist wallet
UPDATE therapist SET balance = v_updated_balance WHERE id = v_therapist.therapist_id;
-- Record transaction
INSERT INTO wallet_transaction (entry_type, therapist_id, amount, appointment_id)
VALUES ('CREDITED', v_therapist.therapist_id, v_appointment_fee, input_appointment_id);
Validation Features:
- Duplicate transaction prevention: Checks existing wallet transactions
- Appointment status validation: Ensures appointment is not cancelled
- Therapist status verification: Confirms therapist is active
- Multi-therapist support: Handles appointments with multiple therapists
2. Invoice ID Generation Trigger
Function: set_invoice_id()
Trigger: set_invoice_id_trigger
Purpose: Automatically generates unique invoice IDs for all payment types
CREATE TRIGGER set_invoice_id_trigger
BEFORE INSERT ON payment
FOR EACH ROW
EXECUTE FUNCTION set_invoice_id();
Invoice Format Logic:
-- Dynamic prefix based on payment type
IF NEW.type = 'SUBSCRIPTION' THEN prefix := 'SUB';
ELSEIF NEW.type = 'APPOINTMENT_REFUND' THEN prefix := 'REF';
ELSEIF NEW.type = 'PAYOUT' THEN prefix := 'PYT';
ELSE prefix := 'APT';
END IF;
-- Format: PREFIX-YYYYMMDD-XXX
formatted_date := TO_CHAR(CURRENT_DATE, 'YYYYMMDD');
seq_num := LPAD(NEXTVAL('invoice_id_seq')::TEXT, 3, '0');
NEW.invoice_id := prefix || '-' || formatted_date || '-' || seq_num;
Invoice Examples:
- Subscription:
SUB-20250112-001 - Appointment:
APT-20250112-002 - Refund:
REF-20250112-003 - Payout:
PYT-20250112-004
3. Update Triggers
Standard timestamp triggers:
-- Payment table updates
CREATE TRIGGER set_public_payment_updated_at
BEFORE UPDATE ON payment
EXECUTE FUNCTION set_updated_at_column();
-- Wallet transaction updates
CREATE TRIGGER set_public_wallet_transaction_updated_at
BEFORE UPDATE ON wallet_transaction
EXECUTE FUNCTION set_updated_at_column();
Transaction Management
1. Payment Processing with Retry Logic
Retry Configuration:
private readonly max_retries = 5;
private readonly max_timeout = 10000;
private readonly min_timeout = 500;
// Exponential backoff for rate limiting
await pRetry(async () => {
// Payment operation
}, {
retries: this.max_retries,
maxTimeout: this.max_timeout,
minTimeout: this.min_timeout,
onFailedAttempt: (error) => {
if (error.statusCode === HttpStatus.TOO_MANY_REQUESTS) {
Logger.log(`Rate limit hit, retrying attempt ${error.attemptNumber}`);
}
}
});
2. Payment States and Transitions
// Payment lifecycle states
CREATED → PENDING → SUCCESSFUL/FAILED → REFUNDED (optional)
// Status validation in webhooks
if (payment_details.status === 'REFUNDED') {
// Handle already refunded payment
const refund_details = await this.getPaymentRefunds(payment_id);
refund_initiation = refund_details.items[0];
}
3. Transaction CSV Export
Advanced reporting with multi-type support:
async getTransactionsCsvData(query: GetTransactionsCsvDataQuery) {
// Supports multiple transaction types
// Includes therapist fee breakdowns
// Handles subscription and appointment data
// Provides tax and service fee details
}
Wallet System
1. Wallet Transaction Types
enum WalletEntryType {
CREDITED = 'CREDITED', // Fee payments from appointments
DEBITED = 'DEBITED' // Payout withdrawals
}
2. Balance Management
-- Automatic balance calculation
v_updated_balance := v_therapist.balance + v_appointment_fee;
-- Transaction recording with full audit trail
INSERT INTO wallet_transaction (
entry_type, therapist_id, amount, appointment_id, created_at
) VALUES (
'CREDITED', therapist_id, calculated_fee, appointment_id, now()
);
3. Wallet Security Features
- Transaction validation: Prevents duplicate fee transfers
- Balance verification: Ensures sufficient funds for payouts
- Audit trail: Complete transaction history
- Atomic operations: Database transactions ensure consistency
Refund Management
1. Automatic Refund Detection
// Smart refund handling
if (payment_details.status === 'REFUNDED') {
const existing_refunds = await this.getPaymentRefunds(payment_id);
return existing_refunds.items[0]; // Return existing refund
}
// Create new refund if none exists
const refund = await this.razorpayInstance.payments.refund(payment_id, {
amount: refund_amount,
speed: 'normal', // or 'optimum'
notes: { organization, transaction_id }
});
2. Refund Workflow
- Validation: Check payment status and eligibility
- Duplication check: Prevent multiple refunds for same payment
- Initiation: Create refund request with Razorpay
- Webhook processing: Handle status updates asynchronously
- Database updates: Record refund status and amounts
3. Refund Types
- Full refunds: Complete transaction reversal
- Partial refunds: Specific amount refunding
- Speed options: Normal vs optimum processing
- Automatic detection: Prevents duplicate refund requests
Payout System
1. Multi-Mode Payout Support
Automatic fallback mechanism:
// Primary: IMPS (Immediate Payment Service)
payout_parameters.mode = 'IMPS';
// Automatic fallback on IMPS restriction
if (response.error?.reason === 'IMPS_NOT_ALLOWED') {
payout_parameters.mode = 'NEFT'; // National Electronic Funds Transfer
return this.createPayout(payout_parameters); // Recursive retry
}
2. Payout Lifecycle Management
// Contact creation → Fund account setup → Payout execution
1. createContact(therapist_details)
2. createFundAccount(bank_details)
3. createPayout(amount, fund_account_id)
4. Webhook status updates
5. Wallet balance adjustment
3. Payout Security & Features
- Idempotency keys: Prevent duplicate payouts
- Queue support: Handle low balance scenarios
- Contact verification: Bank account validation
- Automatic retries: IMPS to NEFT mode switching
- Real-time status: Webhook-driven updates
Data Models
Core Payment Model
interface Payment {
id: string;
invoice_id: string; // Auto-generated: PREFIX-YYYYMMDD-XXX
provider_id: string; // Razorpay payment/order ID
amount: number;
currency: string;
status: TransactionStatus;
type: TransactionType;
created_at: Date;
updated_at: Date;
}
Wallet Transaction Model
interface WalletTransaction {
id: string;
therapist_id: string;
appointment_id?: string;
entry_type: 'CREDITED' | 'DEBITED';
amount: number;
created_at: Date;
updated_at: Date;
}
Payout Models
interface RazorpayXContact {
name: string;
email: string;
contact: string;
type: 'employee' | 'vendor' | 'customer';
}
interface FundAccount {
contact_id: string;
account_type: 'bank_account';
bank_account: {
name: string;
ifsc: string;
account_number: string;
};
}
Security & Validation
1. Webhook Security
// HMAC-SHA256 signature verification
const expectedSignature = crypto
.createHmac('sha256', webhook_secret)
.update(webhook_body)
.digest('hex');
const isValid = expectedSignature === received_signature;
2. Input Validation
- DTO validation using class-validator decorators
- Amount validation for positive values and currency formats
- Bank account validation for IFSC codes and account numbers
- Contact validation for email and phone number formats
3. Authorization Patterns
// Admin-only financial operations
@Roles(UserRole.ADMIN)
@Auth(AuthType.JWT)
// Function-level authorization in database
IF token_role NOT IN ('ADMIN', 'THERAPIST', 'PARENT') THEN
RAISE EXCEPTION 'Unauthorized access';
END IF;
4. Error Handling & Resilience
- Rate limiting protection with exponential backoff
- Duplicate prevention using Redis event tracking
- Graceful degradation for third-party service failures
- Comprehensive logging for audit and debugging
- Transaction rollback on validation failures
Key Implementation Details
Payment Share Calculation
// Dynamic commission calculation
const platform_commission = therapist.payment_share || settings.default_share;
const therapist_fee = appointment_fee * ((100 - platform_commission) / 100);
Webhook Deduplication
// Redis-based event tracking
const event_key = `webhook:${event_id}`;
const isProcessed = await redis.get(event_key);
if (!isProcessed) {
await redis.setex(event_key, STATESTORE_TTL, 'processed');
// Process webhook
}
Invoice Generation
-- Atomic sequence-based invoice generation
seq_num := LPAD(NEXTVAL('invoice_id_seq')::TEXT, 3, '0');
invoice_id := prefix || '-' || formatted_date || '-' || seq_num;
The Payment module provides a comprehensive financial transaction system with robust payment processing, automated fee distribution, secure webhook handling, and complete audit trails, enabling seamless financial operations across the Comdeall platform.
Conclusion
The Payment module serves as the financial backbone of the Comdeall platform, providing secure, scalable, and automated payment processing with sophisticated webhook management, multi-gateway integration, and comprehensive transaction tracking. Through the combination of Razorpay and RazorpayX integrations, database functions for fee distribution, and robust error handling, it delivers a complete financial ecosystem for managing subscription payments, appointment fees, therapist payouts, and refund processing.