Psyter - WindowsService Detailed Structure (Part 1 of 3)

Repository Overview

Location: D:\Dev\Projects\Psyter\WindowsService\

Purpose: Background Windows Service for automated payment gateway inquiry and refund processing

Primary Function: The WindowsService repository contains a .NET Framework Windows Service that runs continuously on a server to:
- Automatically check payment gateway for pending payment transactions
- Process payment inquiry responses and update booking statuses
- Handle refund requests and notifications
- Manage FCM (Firebase Cloud Messaging) notifications and appointment reminders
- Cleanup old log files periodically
- Notify users about SCHFS card expiry

Technology Stack:
- .NET Framework: 4.8
- Service Type: Windows Service (System.ServiceProcess)
- Data Access: Microsoft Enterprise Library 3.1 (Data, Common, ObjectBuilder)
- Database: Direct SQL Server connection (psyter_v1 database)
- Serialization: Newtonsoft.Json 13.0.1
- Timer-Based: Multiple System.Timers.Timer for scheduled tasks
- Threading: Multi-threaded background processing
- APIs: HTTP client for PsyterAPI and SchedulingAPI integration

Key Characteristics:
- Deployment: Windows Service installed on server infrastructure
- Execution Model: Long-running background process with timer-based triggers
- Database Access: Direct database connection using Enterprise Library (bypasses API layer)
- Logging: File-based logging to local disk (one file per day per log type)
- Scalability: Single-threaded task execution with thread lifecycle management
- Reliability: Built-in retry logic and error handling


Root Directory Structure

WindowsService/
├── PsyterPaymentInquiry.sln              # Visual Studio 2017 Solution
├── .git/                                  # Git repository metadata
├── .vs/                                   # Visual Studio cache
├── packages/                              # NuGet packages cache
├── Psyter Payment Inquiry Service/       # Advanced Installer project
│   ├── Psyter Payment Inquiry Service.aip
│   └── Setup Files/
└── PsyterPaymentInquiry/                 # Main Windows Service project
    ├── App.config                         # Configuration (API URLs, DB connection)
    ├── PsyterPaymentInquiry.csproj       # Project file
    ├── PaymentInquiryService.cs           # Main service implementation
    ├── PaymentInquiryService.Designer.cs  # Designer code
    ├── Program.cs                         # Entry point
    ├── ProjectInstaller.cs                # Windows Service installer
    ├── ProjectInstaller.Designer.cs       # Installer designer
    ├── Properties/
    │   └── AssemblyInfo.cs
    ├── DAL/                               # Data Access Layer
    │   ├── DBConstants.cs
    │   ├── DBHelper.cs
    │   ├── PaymentDataAccess.cs
    │   └── XmlHelper.cs
    └── DTO/                               # Data Transfer Objects
        ├── AppConfigSetting.cs
        ├── FCMNotificationModal.cs
        ├── InquiryPayment.cs
        ├── PendingPaymentModal.cs
        └── PsyterAPIAuth.cs

Solution File (PsyterPaymentInquiry.sln)

File: WindowsService/PsyterPaymentInquiry.sln

Purpose: Visual Studio solution container for the Windows Service project

Configuration:
- Visual Studio Version: 2017 (v15.0.26730.12)
- Target Framework: .NET Framework 4.8
- Projects: Single project (PsyterPaymentInquiry)
- Project GUID: {552DFF33-29C6-42CD-9F91-A6C9D00E9F31}

Build Configurations:
- Debug|Any CPU
- Release|Any CPU

How It Connects:
- Opens the PsyterPaymentInquiry.csproj project
- Manages build settings and dependencies
- Configures solution-level settings for debugging and deployment


Project File (PsyterPaymentInquiry.csproj)

File: WindowsService/PsyterPaymentInquiry/PsyterPaymentInquiry.csproj

Purpose: MSBuild project configuration defining dependencies, source files, and build settings

Key Configurations:
- Output Type: WinExe (Windows Service executable)
- Target Framework: .NET Framework 4.8
- Assembly Name: PsyterPaymentInquiry
- Platform Target: Any CPU
- Auto-Generate Binding Redirects: True

NuGet Package Dependencies:

<PackageReference>
  - Microsoft.Practices.EnterpriseLibrary.Common v3.1.0
  - Microsoft.Practices.EnterpriseLibrary.Data v3.1.0
  - Microsoft.Practices.ObjectBuilder v3.1.0
  - Newtonsoft.Json v13.0.1
</PackageReference>

Framework Dependencies:
- System.ServiceProcess (Windows Service framework)
- System.Configuration (App.config access)
- System.Data (Database ADO.NET)
- System.Net.Http (HTTP API calls)
- System.Xml (XML serialization)

Source Files Included:
- Service Layer: PaymentInquiryService.cs, PaymentInquiryService.Designer.cs
- Entry Point: Program.cs
- Installer: ProjectInstaller.cs, ProjectInstaller.Designer.cs
- DAL: DBConstants.cs, DBHelper.cs, PaymentDataAccess.cs, XmlHelper.cs
- DTO: AppConfigSetting.cs, FCMNotificationModal.cs, InquiryPayment.cs, PendingPaymentModal.cs, PsyterAPIAuth.cs
- Properties: AssemblyInfo.cs

How It Connects:
- Defines the entire project structure and dependencies
- Specifies build output as Windows Service executable
- Links all source files and NuGet packages
- Configures Enterprise Library for database access


Configuration (App.config)

File: WindowsService/PsyterPaymentInquiry/App.config

Purpose: Application configuration file containing API URLs, database connection strings, and runtime settings

Configuration Structure

1. Application Settings

<appSettings>
  <add key="PsyterAPIBaseURL" value="https://dvx.innotech-sa.com/Psyter/Master/APIs/" />
  <add key="SchedulingAPIBaseURL" value="https://dvx.innotech-sa.com/Scheduling/SchedulingAPI/" />
</appSettings>

API URLs:
- PsyterAPIBaseURL: Main Psyter API for notifications and refund processing
- SchedulingAPIBaseURL: Scheduling API for booking status updates

2. Connection Strings

<connectionStrings>
  <add name="PsyterDatabase" 
       connectionString="Data Source=devdb.innotech-sa.com;
                        Initial Catalog=psyter_v1;
                        User ID=psyter_dev;
                        Password=***;
                        Application Name=PsyterPaymentInquiry"
       providerName="System.Data.SqlClient" />
</connectionStrings>

Database Configuration:
- Server: devdb.innotech-sa.com
- Database: psyter_v1
- Provider: System.Data.SqlClient (SQL Server)
- Application Name: PsyterPaymentInquiry (for connection tracking)

3. Data Configuration (Enterprise Library)

<dataConfiguration defaultDatabase="PsyterDatabase" />

Enterprise Library Setup:
- Uses named connection string “PsyterDatabase”
- Enables Enterprise Library Data Access Application Block

How It Connects:
- App.config is loaded at service startup
- DAL layer uses DatabaseFactory.CreateDatabase("PsyterDatabase") to get database connection
- Service methods use ConfigurationManager.AppSettings to access API URLs
- Connection string is managed by Enterprise Library for all database operations


Entry Point (Program.cs)

File: WindowsService/PsyterPaymentInquiry/Program.cs

Purpose: Main entry point for the Windows Service application

Code Analysis

static void Main()
{
    ServiceBase[] ServicesToRun;
    ServicesToRun = new ServiceBase[]
    {
        new PaymentInquiryService()
    };
    ServiceBase.Run(ServicesToRun);
}

What It Does:
1. Service Initialization: Creates array of Windows Services to run
2. Service Instantiation: Instantiates PaymentInquiryService object
3. Service Execution: Calls ServiceBase.Run() to start the Windows Service

Debugging Support:

////For Debugging
//PaymentInquiryService service = new PaymentInquiryService();
//service.GetPendingFCMNotificationsAndReminders();
//System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);

- Commented Code: Allows manual method testing outside service context
- Purpose: Developers can uncomment to debug specific methods without installing service

How It Connects:
- Called by Windows Service Control Manager (SCM) when service starts
- Creates instance of PaymentInquiryService class
- Hands control to Windows Service framework (ServiceBase)
- Service framework calls OnStart() method in PaymentInquiryService


Windows Service Installer (ProjectInstaller.cs)

File: WindowsService/PsyterPaymentInquiry/ProjectInstaller.cs

Purpose: Installer component for registering the Windows Service with the operating system

Code Analysis

[RunInstaller(true)]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
    public ProjectInstaller()
    {
        InitializeComponent();
    }
}

What It Does:
- [RunInstaller(true)]: Marks class as Windows Service installer
- InitializeComponent(): Loads designer-configured service installer settings
- Installer Framework: Uses System.Configuration.Install.Installer base class

Designer Configuration (ProjectInstaller.Designer.cs):
The designer file (not shown) typically configures:
- ServiceInstaller: Service name, display name, description, start type
- ServiceProcessInstaller: Account type (LocalSystem, NetworkService, etc.)

How It Connects:
- Used by installutil.exe to install/uninstall the Windows Service
- Configured in Visual Studio designer
- Registers service with Windows Service Control Manager (SCM)
- Sets service startup behavior (Automatic, Manual, Disabled)

Installation Commands:

# Install service
installutil.exe PsyterPaymentInquiry.exe

# Uninstall service
installutil.exe /u PsyterPaymentInquiry.exe


Main Service Implementation (PaymentInquiryService.cs) - Overview

File: WindowsService/PsyterPaymentInquiry/PaymentInquiryService.cs

Purpose: Core Windows Service class implementing all payment inquiry, refund, and notification logic

Service Architecture

Class Declaration:

public partial class PaymentInquiryService : ServiceBase

- Inheritance: Extends System.ServiceProcess.ServiceBase
- Partial Class: Works with designer-generated code

Timer Components

The service uses 4 independent timers for different scheduled tasks:

System.Timers.Timer timer = new System.Timers.Timer();                              // Payment inquiry
System.Timers.Timer timerForDeleteLogFiles = new System.Timers.Timer();            // Log cleanup
System.Timers.Timer timerNotifySCHFSCardExpiry = new System.Timers.Timer();        // SCHFS expiry
System.Timers.Timer timerSendFCMNotification = new System.Timers.Timer();          // FCM notifications

Timer Intervals:
- timer: 600,000 ms (10 minutes) - Payment inquiry and refund processing
- timerForDeleteLogFiles: 1,296,000,000 ms (15 days) - Delete old log files
- timerNotifySCHFSCardExpiry: 86,400,000 ms (24 hours) - SCHFS card expiry notifications
- timerSendFCMNotification: 600,000 ms (10 minutes) - FCM notifications and reminders

Thread Management

Thread RefundAndInquiryThread, DeleteLogFilesThread, NotifySCHFSCardExpiryThread, SendFCMNotifications;

Threading Strategy:
- Purpose: Prevents timer overlap and service blocking
- Lifecycle: Threads are checked before creation to avoid duplicate execution
- Pattern: if (thread == null || !thread.IsAlive) before creating new thread

Authentication Tokens

protected string PsyterAPIApplicationToken = null;
protected string PsyterAPIAuthToken = null;
protected string SchedulingAPIApplicationToken = null;
protected string SchedulingAPIAuthToken = null;

Token Management:
- ApplicationToken: Retrieved from database configuration
- AuthToken: OAuth bearer token obtained from API authentication endpoint
- Lifecycle: Tokens are refreshed for each API call series


Service Lifecycle Methods

OnStart Method

protected override void OnStart(string[] args)
{
    WriteToFile("Service is started at " + DateTime.Now, "Inquiry");
    WriteToFile("Service is started at " + DateTime.Now, "Refund");
    WriteToFile("Service is started at " + DateTime.Now, "NotifySCHFSCardExpiry");
    WriteToFile("Service is started at " + DateTime.Now, "DeleteFileLog");
    WriteToFile("Service is started at " + DateTime.Now, "SendFCMNotification");

    // Timer 1: Payment Inquiry and Refund (10 minutes)
    timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
    timer.Interval = 600000;
    timer.Enabled = true;

    // Timer 2: Delete Log Files (15 days)
    timerForDeleteLogFiles.Elapsed += new ElapsedEventHandler(OnElapsedTime30Days);
    timerForDeleteLogFiles.Interval = 1296000000;
    timerForDeleteLogFiles.Enabled = true;

    // Timer 3: SCHFS Card Expiry Notification (24 hours)
    timerNotifySCHFSCardExpiry.Elapsed += new ElapsedEventHandler(OnElapsedTimeNotifySCHFSExpiry);
    timerNotifySCHFSCardExpiry.Interval = 86400000;
    timerNotifySCHFSCardExpiry.Enabled = true;

    // Timer 4: FCM Notifications and Reminders (10 minutes)
    timerSendFCMNotification.Elapsed += new ElapsedEventHandler(OnElapsedTimeForFCMNotificationsAndReminders);
    timerSendFCMNotification.Interval = 600000;
    timerSendFCMNotification.Enabled = true;
}

What It Does:
1. Logging: Writes service start timestamp to 5 different log files
2. Timer Configuration: Sets up 4 independent timers with different intervals
3. Event Handlers: Attaches elapsed event handlers to each timer
4. Timer Activation: Enables all timers to start firing

How It Connects:
- Called by Windows Service Control Manager when service is started
- Initializes the service’s timer-based execution model
- Sets up the foundation for all automated processing

OnStop Method

protected override void OnStop()
{
    WriteToFile("Service is stopped at " + DateTime.Now, "Inquiry");
    WriteToFile("Service is stopped at " + DateTime.Now, "Refund");
}

What It Does:
1. Logging: Records service stop timestamp
2. Cleanup: Allows timers and threads to complete current operations

How It Connects:
- Called when service is stopped via Windows Service Manager
- Provides graceful shutdown notification in logs


Timer Event Handlers

1. Payment Inquiry/Refund Timer

private void OnElapsedTime(object source, ElapsedEventArgs e)
{
    WriteToFile("Service is recall at " + DateTime.Now, "Inquiry");
    WriteToFile("Service is recall at " + DateTime.Now, "Refund");
    CreateThread();
}

What It Does:
- Trigger: Fires every 10 minutes
- Action: Logs recall timestamp and creates background thread
- Thread Creation: Calls CreateThread() to start payment processing

2. Log File Cleanup Timer

private void OnElapsedTime30Days(object source, ElapsedEventArgs e)
{
    WriteToFile("Delete File Service is recall at " + DateTime.Now, "DeleteFileLog");
    CreateThreadForDeleteLogFiles();
}

What It Does:
- Trigger: Fires every 15 days
- Action: Logs recall timestamp and creates cleanup thread
- Thread Creation: Calls CreateThreadForDeleteLogFiles()

3. SCHFS Expiry Notification Timer

private void OnElapsedTimeNotifySCHFSExpiry(object source, ElapsedEventArgs e)
{
    WriteToFile("Notify SCHFS Service is recall at " + DateTime.Now, "NotifySCHFSCardExpiry");
    CreateThreadForNotifySCHFSExpiry();
}

What It Does:
- Trigger: Fires every 24 hours
- Action: Logs recall timestamp and creates SCHFS notification thread
- Thread Creation: Calls CreateThreadForNotifySCHFSExpiry()

4. FCM Notification/Reminder Timer

private void OnElapsedTimeForFCMNotificationsAndReminders(object source, ElapsedEventArgs e)
{
    WriteToFile("Service is recall at " + DateTime.Now, "SendFCMNotificationAndReminders");
    WriteToFile("Service is recall at " + DateTime.Now, "SendFCMNotificationAndReminders");
    CreateThreadForSendFCMNotificationsAndReminders();
}

What It Does:
- Trigger: Fires every 10 minutes
- Action: Logs recall timestamp and creates FCM thread
- Thread Creation: Calls CreateThreadForSendFCMNotificationsAndReminders()


Thread Management Methods

Payment Inquiry/Refund Thread

public void CreateThread()
{
    try
    {
        if (RefundAndInquiryThread == null)
        {
            RefundAndInquiryThread = new Thread(() => GetPendingPayment());
            RefundAndInquiryThread.Name = "RefundAndInquiryThread";
            RefundAndInquiryThread.Start();
            WriteToFile("RefundAndInquiryThread started " + DateTime.Now, "Inquiry");
            WriteToFile("RefundAndInquiryThread started " + DateTime.Now, "Refund");
        }
        else
        {
            if (!RefundAndInquiryThread.IsAlive)
            {
                RefundAndInquiryThread = new Thread(() => GetPendingPayment());
                RefundAndInquiryThread.Name = "RefundAndInquiryThread";
                RefundAndInquiryThread.Start();
                WriteToFile("RefundAndInquiryThread started " + DateTime.Now, "Inquiry");
                WriteToFile("RefundAndInquiryThread started " + DateTime.Now, "Refund");
            }
            else
            {
                WriteToFile("Service thread is already in running state " + DateTime.Now, "Inquiry");
                WriteToFile("Service thread is already in running state " + DateTime.Now, "Refund");
            }
        }
    }
    catch (Exception ex)
    {
        WriteToFile("Exception on creating thread " + DateTime.Now, "Inquiry");
        WriteToFile("Exception on creating thread " + DateTime.Now, "Refund");
    }
}

What It Does:
1. Thread Lifecycle Check: Checks if thread exists and is alive
2. Thread Creation: Creates new thread if previous completed or doesn’t exist
3. Thread Naming: Sets thread name for debugging purposes
4. Thread Execution: Starts thread which calls GetPendingPayment() method
5. Overlap Prevention: Logs warning if previous thread is still running

Thread Safety:
- Prevents multiple simultaneous executions
- Logs all thread state changes
- Gracefully handles exceptions

Log File Cleanup Thread

public void CreateThreadForDeleteLogFiles()
{
    try
    {
        DeleteLogFilesThread = new Thread(() => DeleteLogFilesOfPreviousMonth());
        DeleteLogFilesThread.Name = "DeleteLogFilesThread";
        DeleteLogFilesThread.Start();
        WriteToFile("RefundAndInquiryThread started " + DateTime.Now, "DeleteFileLog");
    }
    catch (Exception ex)
    {
        WriteToFile("Exception on creating thread " + DateTime.Now, "DeleteFileLog");
    }
}

What It Does:
1. Thread Creation: Creates thread for log file cleanup
2. Method Execution: Calls DeleteLogFilesOfPreviousMonth()
3. Logging: Records thread start

SCHFS Expiry Notification Thread

public void CreateThreadForNotifySCHFSExpiry()
{
    try
    {
        NotifySCHFSCardExpiryThread = new Thread(() => CallNotifySCHFSExpiryAPI());
        NotifySCHFSCardExpiryThread.Name = "NotifySCHFSCardExpiryThread";
        NotifySCHFSCardExpiryThread.Start();
        WriteToFile("NotifySCHFSCardExpiryThread started " + DateTime.Now, "NotifySCHFSCardExpiry");
    }
    catch (Exception ex)
    {
        WriteToFile("Exception on creating thread " + DateTime.Now, "NotifySCHFSCardExpiry");
    }
}

What It Does:
1. Thread Creation: Creates thread for SCHFS expiry notifications
2. Method Execution: Calls CallNotifySCHFSExpiryAPI()
3. Logging: Records thread start

FCM Notification Thread

public void CreateThreadForSendFCMNotificationsAndReminders()
{
    try
    {
        if (SendFCMNotifications == null)
        {
            SendFCMNotifications = new Thread(() => GetPendingFCMNotificationsAndReminders());
            SendFCMNotifications.Name = "SendFCMNotificationAndReminderThread";
            SendFCMNotifications.Start();
            WriteToFile("SendFCMNotificationThread started " + DateTime.Now, "SendFCMNotificationAndReminders");
        }
        else
        {
            if (!SendFCMNotifications.IsAlive)
            {
                SendFCMNotifications = new Thread(() => GetPendingFCMNotificationsAndReminders());
                SendFCMNotifications.Name = "SendFCMNotificationAndReminderThread";
                SendFCMNotifications.Start();
                WriteToFile("SendFCMNotificationThread started " + DateTime.Now, "SendFCMNotificationAndReminders");
            }
            else
            {
                WriteToFile("Service thread is already in running state " + DateTime.Now, "SendFCMNotificationAndReminders");
            }
        }
    }
    catch (Exception ex)
    {
        WriteToFile("Exception on creating thread " + DateTime.Now, "SendFCMNotificationAndReminders");
    }
}

What It Does:
1. Thread Lifecycle Check: Checks if thread exists and is alive
2. Thread Creation: Creates new thread if previous completed
3. Method Execution: Calls GetPendingFCMNotificationsAndReminders()
4. Overlap Prevention: Prevents duplicate FCM processing


Logging System

WriteToFile Method

public void WriteToFile(string Message, string logType)
{
    string path = AppDomain.CurrentDomain.BaseDirectory + "\\Logs";
    if (!Directory.Exists(path))
    {
        Directory.CreateDirectory(path);
    }

    string filepath = AppDomain.CurrentDomain.BaseDirectory + "\\Logs\\ServiceLog_" + logType + "_" + 
                      DateTime.Now.Date.ToShortDateString().Replace('/', '_') + ".txt";

    if (!File.Exists(filepath))
    {
        using (StreamWriter sw = File.CreateText(filepath))
        {
            sw.WriteLine(Message);
        }
    }
    else
    {
        using (StreamWriter sw = File.AppendText(filepath))
        {
            sw.WriteLine(Message);
        }
    }
}

What It Does:
1. Log Directory: Creates \Logs folder in service executable directory if not exists
2. File Naming: Creates one log file per day per log type
- Format: ServiceLog_{logType}_{date}.txt
- Example: ServiceLog_Inquiry_01_15_2024.txt
3. File Creation: Creates new file if doesn’t exist
4. File Append: Appends messages to existing daily log file

Log Types:
- Inquiry: Payment inquiry processing logs
- Refund: Refund processing logs
- NotifySCHFSCardExpiry: SCHFS card expiry notification logs
- DeleteFileLog: Log file cleanup operation logs
- SendFCMNotificationAndReminders: FCM notification and reminder logs

Log Structure:
- Each line is a timestamped message
- Messages include operation type, transaction details, errors, and status updates

How It Connects:
- Called throughout service for all logging needs
- Creates audit trail for troubleshooting
- Enables monitoring of service operations


Data Access Layer (DAL) - Overview

The DAL folder contains database access logic using Microsoft Enterprise Library Data Access Application Block.

Architecture Pattern

Service Layer (PaymentInquiryService.cs)
    ↓
Data Access Layer (PaymentDataAccess.cs)
    ↓
Database Helper (DBHelper.cs)
    ↓
Enterprise Library (DatabaseFactory)
    ↓
SQL Server Database (psyter_v1)

Key Design Principles:
- Separation of Concerns: Database logic isolated from business logic
- Reusability: Generic helper methods for data mapping
- Stored Procedures: All database operations use stored procedures
- Connection Management: Enterprise Library handles connection pooling


DAL - DBConstants.cs

File: WindowsService/PsyterPaymentInquiry/DAL/DBConstants.cs

Purpose: Centralized storage of stored procedure names

Code Analysis

public static class DBConstants
{
    // Payment Inquiry Stored Procedures
    public const string GET_PENDING_PAYMENTS = "ws_GetPendingPaymentsList_AppConfigSetting";
    public const string GET_PENDING_REFUND_PAYMENTS = "ws_GetPendingPaymentsListForRefund_AppConfigSetting";
    public const string GET_PENDING_WALLET_PURCHASE_PAYMENTS = "ws_GetPendingWalletPurchasesList_AppConfigSetting";
    public const string GET_PENDING_PACKAGE_PURCHASE_PAYMENTS = "ws_GetPendingPackagePaymentsList_AppConfigSetting";

    // Payment Update Stored Procedures
    public const string UPDATE_PENDING_PAYMENTS_STATUS = "ws_UpdatePaymentInquiryStatus";
    public const string UPDATE_PENDING_REFUND_PAYMENTS_STATUS = "ws_UpdatePendingPaymentRefundStatus";
    public const string UPDATE_WALLET_PURCHASE_PENDING_PAYMENTS_STATUS = "ws_UpdateWalletPurchasePaymentInquiryStatus";
    public const string UPDATE_PACKAGE_PURCHASE_PENDING_PAYMENTS_STATUS = "ws_UpdatePackagePaymentInquiryStatus";

    // Configuration Stored Procedures
    public const string GET_APPLICATION_CONFIG_BY_GROUP_ID = "AppConfig_GetAppConfigSettingsByGroupId";

    // Notification Stored Procedures
    public const string GET_PENDING_NOTIFICATIONS_FOR_FCM = "Notification_GetNotificationsListToSentUsingFCM";
    public const string UPDATE_NOTIFICATIONS_SENT_STATUS = "Notification_UpdateNotificationSentStatus";
    public const string UPDATE_REMINDER_SENT_STATUS = "Reminder_UpdateReminderSentStatus";
    public const string GET_USER_REMINDERS_LIST = "Reminder_GetUserReminderList";
}

What It Does:
- Centralized Storage: All stored procedure names in one place
- Type Safety: Compile-time checking of procedure names
- Maintainability: Easy to update procedure names across entire service

Stored Procedure Categories:
1. Payment Inquiry: Retrieve pending payments of different types
2. Payment Update: Update payment statuses after inquiry/refund
3. Configuration: Get application configuration settings
4. Notification: Manage FCM notifications and reminders

How It Connects:
- Referenced by PaymentDataAccess.cs for all database operations
- Provides contract between service and database layer


DAL - DBHelper.cs

File: WindowsService/PsyterPaymentInquiry/DAL/DBHelper.cs

Purpose: Base class providing database connection and data mapping utilities

Code Analysis

Database Connection

public class DBHelper
{
    protected Database dataBase;

    public DBHelper()
    {
        try
        {
            dataBase = DatabaseFactory.CreateDatabase("PsyterDatabase");
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

What It Does:
1. Database Initialization: Creates Enterprise Library Database object
2. Connection String: Uses “PsyterDatabase” named connection from App.config
3. Connection Management: Enterprise Library handles connection pooling automatically

DataTable to List Mapping

public static IList<T> MapDataTableToList<T>(DataTable dt)
{
    if (dt == null) return null;

    IList<T> list = new List<T>();

    foreach (DataRow dr in dt.Rows)
    {
        T instance = Activator.CreateInstance<T>();
        dr.Fill<T>(ref instance);
        list.Add(instance);
    }

    return list;
}

What It Does:
1. Generic Mapping: Maps DataTable rows to strongly-typed objects
2. Dynamic Object Creation: Uses Activator.CreateInstance<T>() for object instantiation
3. Property Mapping: Calls extension method Fill<T>() to map columns to properties
4. Collection Building: Returns IList<T> of mapped objects

DataTable to Single Object

public static T MapDataTableToObject<T>(DataTable dt)
{
    T instance = default(T);

    if (dt == null) return instance;

    if (dt.Rows.Count > 0)
    {
        instance = Activator.CreateInstance<T>();
        DataRow dr = dt.Rows[0];
        dr.Fill<T>(ref instance);
    }

    return instance;
}

What It Does:
1. Single Row Mapping: Maps first row of DataTable to object
2. Null Handling: Returns default(T) if DataTable is null or empty
3. Property Mapping: Uses Fill<T>() extension method

Query String Parser

public NameValueCollection ParseQueryString(string s)
{
    NameValueCollection nvc = new NameValueCollection();

    if (s.Contains("?"))
    {
        s = s.Substring(s.IndexOf('?') + 1);
    }

    foreach (string vp in Regex.Split(s, "&"))
    {
        string[] singlePair = Regex.Split(vp, "=");
        if (singlePair.Length == 2)
        {
            nvc.Add(singlePair[0], singlePair[1]);
        }
        else
        {
            nvc.Add(singlePair[0], string.Empty);
        }
    }

    return nvc;
}

What It Does:
1. Query String Parsing: Parses payment gateway response query strings
2. URL Handling: Removes URL portion, keeps only query string
3. Key-Value Extraction: Splits by & and = to extract parameters
4. Collection Building: Returns NameValueCollection of parsed parameters

How It Connects:
- Used to parse payment gateway HTTP responses (e.g., Response.StatusCode=00000&Response.TransactionID=123)
- Enables easy access to response parameters

DataRow Extension Method

public static class DataRowExtention
{
    public static void Fill<T>(this DataRow oRow, ref T obj)
    {
        if (oRow == null) throw new ArgumentNullException("DataRow cannot be null");
        if (obj == null) throw new ArgumentNullException("Object cannot be null");

        PropertyInfo[] oProperties = typeof(T).GetProperties();

        for (int i = 0; i < oProperties.Length; i++)
        {
            PropertyInfo oProperty = oProperties[i];

            if (!oProperty.CanWrite) continue;

            if (oRow.Table.Columns[oProperty.Name] != null)
            {
                if (oRow[oProperty.Name] != System.DBNull.Value)
                    oProperty.SetValue(obj, oRow[oProperty.Name], null);
                else
                    oProperty.SetValue(obj, null, null);
            }
        }
    }
}

What It Does:
1. Reflection-Based Mapping: Uses reflection to get object properties
2. Column Matching: Matches DataTable column names to property names (case-sensitive)
3. Null Handling: Sets property to null if database value is DBNull
4. Type Safety: Only sets writable properties
5. Dynamic Mapping: Works with any object type

How It Connects:
- Extension method adds Fill<T>() to DataRow class
- Called by MapDataTableToList<T>() and MapDataTableToObject<T>()
- Enables automatic property mapping from database results


DAL - PaymentDataAccess.cs (Part 1)

File: WindowsService/PsyterPaymentInquiry/DAL/PaymentDataAccess.cs

Purpose: Concrete data access class for all payment inquiry, refund, and notification database operations

Inheritance:

public class PaymentDataAccess : DBHelper

- Inherits from DBHelper to access dataBase object
- All methods use Enterprise Library Database object from base class

1. Get Pending Payments (Booking Payments)

public GetPendingPaymentsListResponse GETPendingPayments()
{
    GetPendingPaymentsListResponse response = new GetPendingPaymentsListResponse();
    using (DbCommand dbCommand = dataBase.GetStoredProcCommand(DBConstants.GET_PENDING_PAYMENTS))
    {
        DataSet dataSet = dataBase.ExecuteDataSet(dbCommand);

        if (dataSet.Tables != null && dataSet.Tables.Count > 0)
        {
            response.PendingPaymentsList = MapDataTableToList<PendingPayment>(dataSet.Tables[0]).ToList();
            response.AppConfigSettingList = MapDataTableToList<ApplicationConfiguration>(dataSet.Tables[1]).ToList();
            response.ApplicationAPIToken = MapDataTableToObject<GetPendingPaymentsListResponse>(dataSet.Tables[2]).ApplicationAPIToken;
        }
    }

    return response;
}

What It Does:
1. Stored Procedure: Calls ws_GetPendingPaymentsList_AppConfigSetting
2. Result Sets:
- Table 0: List of pending payment records (PendingPayment objects)
- Table 1: Application configuration settings for payment gateway
- Table 2: API authentication token for SchedulingAPI
3. Data Mapping: Maps DataTables to strongly-typed objects
4. Return: GetPendingPaymentsListResponse containing all three result sets

Database Query Purpose:
- Retrieves booking payments with status “Pending” or “Processing”
- Includes booking details (OrderId, SlotBookingId, Transaction ID, amounts)
- Returns payment gateway configuration (merchant ID, secret key, URLs)
- Returns API token for subsequent API calls

How It Connects:
- Called by GetPendingPayment() in service layer
- Results are used to query payment gateway for transaction status
- Configuration is used to generate secure hashes for gateway API calls

2. Get Pending Refund Payments

public GetPendingPaymentsListResponse GETPendingForRefundPayments()
{
    GetPendingPaymentsListResponse response = new GetPendingPaymentsListResponse();
    using (DbCommand dbCommand = dataBase.GetStoredProcCommand(DBConstants.GET_PENDING_REFUND_PAYMENTS))
    {
        DataSet dataSet = dataBase.ExecuteDataSet(dbCommand);

        if (dataSet.Tables != null && dataSet.Tables.Count > 0)
        {
            response.PendingPaymentsList = MapDataTableToList<PendingPayment>(dataSet.Tables[0]).ToList();
            response.AppConfigSettingList = MapDataTableToList<ApplicationConfiguration>(dataSet.Tables[1]).ToList();
            response.ApplicationAPIToken = MapDataTableToObject<GetPendingPaymentsListResponse>(dataSet.Tables[2]).ApplicationAPIToken;
        }
    }

    return response;
}

What It Does:
1. Stored Procedure: Calls ws_GetPendingPaymentsListForRefund_AppConfigSetting
2. Result Sets:
- Table 0: Payments with status “Pending For Refund” or “Pending Refund”
- Table 1: Payment gateway configuration
- Table 2: PsyterAPI authentication token
3. Data Mapping: Same structure as GETPendingPayments()

Database Query Purpose:
- Retrieves bookings cancelled by users or physicians requiring refunds
- Retrieves bookings where refund request was initiated but not completed
- Returns same configuration data as inquiry method

How It Connects:
- Called by GetPendingRefundPayment() in service layer
- Results are used to process refund requests via payment gateway
- PsyterAPI token is used to send refund notifications

3. Get Pending Wallet Purchase Payments

public GetPendingPaymentsListResponse GETPendingWalletPurchasePayments()
{
    GetPendingPaymentsListResponse response = new GetPendingPaymentsListResponse();
    using (DbCommand dbCommand = dataBase.GetStoredProcCommand(DBConstants.GET_PENDING_WALLET_PURCHASE_PAYMENTS))
    {
        DataSet dataSet = dataBase.ExecuteDataSet(dbCommand);

        if (dataSet.Tables != null && dataSet.Tables.Count > 0)
        {
            response.PendingPaymentsList = MapDataTableToList<PendingPayment>(dataSet.Tables[0]).ToList();
            response.AppConfigSettingList = MapDataTableToList<ApplicationConfiguration>(dataSet.Tables[1]).ToList();
            response.ApplicationAPIToken = MapDataTableToObject<GetPendingPaymentsListResponse>(dataSet.Tables[2]).ApplicationAPIToken;
        }
    }

    return response;
}

What It Does:
1. Stored Procedure: Calls ws_GetPendingWalletPurchasesList_AppConfigSetting
2. Result Sets:
- Table 0: Wallet purchase payments with status “Pending”
- Table 1: Payment gateway configuration
- Table 2: API authentication token
3. Data Specific: PendingPayment.UserWalletId is populated instead of OrderId

Database Query Purpose:
- Retrieves pending wallet credit purchase transactions
- Users can add funds to their wallet for future bookings
- Separate from booking payments (no OrderId or SlotBookingId)

How It Connects:
- Called by GetPendingWalletPurchasedPayment() in service layer
- Wallet purchases are inquiry-only (no refund processing)

4. Get Pending Package Purchase Payments

public GetPendingPaymentsListResponse GETPendingPackagePurchasePayments()
{
    GetPendingPaymentsListResponse response = new GetPendingPaymentsListResponse();
    using (DbCommand dbCommand = dataBase.GetStoredProcCommand(DBConstants.GET_PENDING_PACKAGE_PURCHASE_PAYMENTS))
    {
        DataSet dataSet = dataBase.ExecuteDataSet(dbCommand);

        if (dataSet.Tables != null && dataSet.Tables.Count > 0)
        {
            response.PendingPaymentsList = MapDataTableToList<PendingPayment>(dataSet.Tables[0]).ToList();
            response.AppConfigSettingList = MapDataTableToList<ApplicationConfiguration>(dataSet.Tables[1]).ToList();
            response.ApplicationAPIToken = MapDataTableToObject<GetPendingPaymentsListResponse>(dataSet.Tables[2]).ApplicationAPIToken;
        }
    }

    return response;
}

What It Does:
1. Stored Procedure: Calls ws_GetPendingPackagePaymentsList_AppConfigSetting
2. Result Sets:
- Table 0: Package purchase payments with status “Pending”
- Table 1: Payment gateway configuration
- Table 2: API authentication token
3. Data Specific: PendingPayment.ClientPackageMainId is populated

Database Query Purpose:
- Retrieves pending session package purchases
- Clients can buy packages of multiple sessions at discounted rates
- Separate from individual booking payments

How It Connects:
- Called by GetPendingPackagePurchasedPayment() in service layer
- Package purchases are inquiry-only (no refund processing)


Summary of Part 1

This document has covered:

Project Structure and Configuration
- Solution and project files
- App.config with API URLs and database connection
- NuGet dependencies and framework references

Service Entry Points
- Program.cs (service startup)
- ProjectInstaller.cs (Windows Service registration)

Service Architecture
- Timer-based execution model (4 independent timers)
- Multi-threaded background processing
- Service lifecycle (OnStart, OnStop)

Timer and Thread Management
- Payment inquiry timer (10 minutes)
- Log cleanup timer (15 days)
- SCHFS expiry timer (24 hours)
- FCM notification timer (10 minutes)
- Thread lifecycle management and overlap prevention

Logging System
- File-based logging per operation type
- Daily log file creation
- Audit trail for troubleshooting

Data Access Layer (DAL) - Part 1
- DBConstants.cs (stored procedure names)
- DBHelper.cs (database connection and data mapping utilities)
- PaymentDataAccess.cs - Data retrieval methods:
* Get pending booking payments
* Get pending refund payments
* Get pending wallet purchases
* Get pending package purchases


Coming in Part 2

The next document will cover:

🔜 Data Access Layer (DAL) - Part 2
- Payment status update methods (4 methods)
- Notification and reminder methods (4 methods)
- Configuration retrieval
- XmlHelper utility class

🔜 Payment Processing Workflows
- GetPendingPayment() - Main booking inquiry
- GetPendingRefundPayment() - Refund processing
- GetPendingWalletPurchasedPayment() - Wallet inquiry
- GetPendingPackagePurchasedPayment() - Package inquiry

🔜 Payment Gateway Integration
- Secure hash generation (SHA256)
- ProcessInquiryOrRefund() - Gateway communication
- Payment gateway request/response handling
- Transaction status parsing


Coming in Part 3

The final document will cover:

🔜 Payment Status Update Methods
- UpdatePaymentInquiryStatus()
- UpdateBookingRefundRequestData()
- UpdateWalletPurchasePaymentInquiryStatus()
- UpdatePackagePurchasePaymentInquiryStatus()

🔜 API Integration
- PsyterAPI authentication
- SchedulingAPI authentication
- Booking status updates
- Refund notifications

🔜 FCM Notification System
- GetPendingFCMNotificationsAndReminders()
- Notification sending
- Reminder processing

🔜 Supporting Features
- Log file cleanup
- SCHFS expiry notifications

🔜 Data Transfer Objects (DTO)
- All DTO model classes (5 files)

🔜 Deployment
- Advanced Installer project


End of Part 1