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